All files range.ts

100% Statements 43/43
100% Branches 36/36
100% Functions 3/3
100% Lines 39/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 741x             36x 35x 35x 4x     1x 21x 21x 21x 20x 20x 20x 20x     20x 8x 8x 8x 8x 8x   8x 8x   8x       32x 31x   7x       12x         12x   12x 12x 12x 12x 12x 12x   12x   12x       49x 48x     1x 1x        
import { Interval, IInterval, NumericValue } from './interval';
 
/**
 * Returns an iterable generator for numbers or bigints in the interval, stepping by `step`.
 * Uses integer math for decimals, and native bigint math for bigints.
 */
function getDecimalPlaces(x: number): number {
    if (typeof x !== 'number' || !isFinite(x)) return 0;
    const s = x.toString();
    if (!s.includes('.')) return 0;
    return s.split('.')[1].length;
}
 
export function range(interval: IInterval | string, step: NumericValue = 1): Iterable<NumericValue> {
    return (function* () {
        try {
            const intvl = new Interval(interval);
            const start = intvl.a.number;
            const end = intvl.b.number;
            const startClosed = intvl.a.isClosed;
            const endClosed = intvl.b.isClosed;
 
            // If any value is bigint, use bigint math
            if (typeof start === 'bigint' || typeof end === 'bigint' || typeof step === 'bigint') {
                const startBig = BigInt(start);
                const endBig = end !== Infinity ? BigInt(end) : end;
                const absStepBig = BigInt(step) < 0n ? -BigInt(step) : BigInt(step);
                const ascending = endBig > startBig;
                const actualStep = ascending ? absStepBig : -absStepBig;
 
                let current = startBig;
                if (!startClosed) current += actualStep;
 
                while (
                    (ascending && ((current < endBig) || (endClosed && current === endBig))) ||
                    (!ascending && ((current > endBig) || (endClosed && current === endBig)))
                ) {
                    yield current;
                    current += actualStep;
                }
                return;
            }
 
            // Otherwise, use number math with integer scaling for decimals
            const decimals = Math.max(
                getDecimalPlaces(Number(start)),
                getDecimalPlaces(Number(end)),
                getDecimalPlaces(Number(step))
            );
            const factor = Math.pow(10, decimals);
 
            let current = Math.round(Number(start) * factor);
            const endInt = Math.round(Number(end) * factor);
            const absStep = Math.abs(Number(step));
            const stepInt = Math.round(absStep * factor);
            const ascending = end > start;
            const actualStep = ascending ? stepInt : -stepInt;
 
            if (!startClosed) current += actualStep;
 
            while (
                (ascending && ((current < endInt) || (endClosed && current === endInt))) ||
                (!ascending && ((current > endInt) || (endClosed && current === endInt)))
            ) {
                yield current / factor;
                current += actualStep;
            }
        } catch (error: Error | unknown) {
            if (error instanceof Error) {
                throw new Error(`Invalid interval: ${error.message}`);
            }
        }
    })();
}