Bidirectional numeric (floating point) sequence iterator
Counter intuitive?

Infinite loops.

The function is an iterator so this may not be undesired or potentially fatal for the application. However some inputs would make one wonder about the callers intent.

numrange(0, 100, 0); // will endlessly return 0
numrange(0, 100, -1); // will return 0, -1, -2, ... Infinity
numrange(0, -100, 1); // will return 0, 1, 2, ... Infinity


Not part of sequence.

There are situation where the last value can be out of sequence

numrange(0, 4, 2); // will return 0, 2.5, 4.


Or two values are returned rather than one.

numrange(4, 4, 1); // will return 4, 4.


Names


The function name should be numRange to be consistent with JS camelCase convention.
The argument ival (should be iVal) in shouldLoop is excessive, you could just have used i.
The function name shouldLoop can also be simplified as it expresses a question "should loop?" which can be shortened considering the context of its use to "loop?"


Logic and flow

The function shouldLoop is called 2 times each iteration, there is no need to do this, on top of this there are cases where the first clause of the inner statement is redundant. eg numRange(0, 20, 1.01) BTW use strict inequality !== rather than !=

You can improve efficiency if you removed the inner statement and yielded end after the loop. You would also need to change the return condition inside the function shouldLoop to start &gt; end ? ival &gt; end : ival &lt; end

Each time you iterate you test if start &gt; end This is known from the very start of the function and is thus repeating the same logic for no need. Use the condition to assign one  of two functions and avoid the repeated logic.

Rewrite

Thus you can have the same behavior as your original code with

function* numRange(start = 0, end = 100, step = 1) {
    const loop = start &gt; end ? (i) =&gt; i &gt; end : (i) =&gt; i &lt; end;
    if (start &gt; end) { step = -1 * step }
    for (let i = start; loop(i); i += step) { 
        yield i;
    }
    yield end;
}


Example


Fixing what I see as problems (as outlined at the start of the answer) 


Ensure that step is in the correct direction no matter what its sign.
Check if step is 0 and just return the start if so.
Check if end is part of the sequence and ignore it if not.

Using the var as argument start to count.


.

function* numRange(start = 0, end = 100, step = 1) {
    step = Math.sign(end - start) * Math.abs(step);
    if (step) {
        const loop = step &gt; 0 ? () =&gt; start &lt; end : () =&gt; end &lt; start;
        while (loop()) {
            yield start;
            start += step;
        }
        if (start !== end) { return }
    }
    yield start; 
}