types_FloatNorm.mjs

import { create, copy, compare, size } from "./helpers/common.mjs";


/** Gets next floating point number in a particular direction.
 * Minorly adapted from: https://stackoverflow.com/a/72185420/379572
 * @param {number} start number to modify
 * @param {number} direction modify `start` in the direction of this number; e.g. you can set this
 * 	to +/-Infinity to increment/decrement respectively.
 * @returns {number}
 * @private
 */
function next_after(start, direction){
	const _f64 = new Float64Array(1)
	const _b64 = new BigInt64Array(_f64.buffer);
	// Branch to descending case first as it is more costly than ascending
	// case due to start != 0.0d conditional.
	if (start > direction) {
		// descending
		if (start !== 0) {
			_f64[0] = start;
			const transducer = _b64[0];
			_b64[0] = transducer + (transducer > 0n ? -1n : 1n);
			return _f64[0];
		} else {
			// start == 0.0d && direction < 0.0d
			return -Number.MIN_VALUE;
		}
	} else if (start < direction) {
		// ascending
		// Add +0.0 to get rid of a -0.0 (+0.0 + -0.0 => +0.0)
		// then bitwise convert start to integer.
		_f64[0] = start + 0;
		const transducer = _b64[0];
		_b64[0] = transducer + (transducer >= 0n ? 1n : -1n);
		return _f64[0];
	} else if (start == direction) {
		return direction;
	} else {
		// isNaN(start) || isNaN(direction)
		return start + direction;
	}
}

/** This is similar to {@link RealType}, but assuming a discrete, floating point representation. The
 * bounds are normalized to always be inclusive. This can be easier to work with, and omits the
 * extra logic needed to handle exclusive bounds.
 * 
 * True real numbers cannot be normalized: e.g. for the half-open interval `[.25,4.75)`, you cannot
 * represent the value that is infinitely close, but not equal to 4.75. However, for finite
 * representations like floating point, you can normalize to the next closest representable number:
 * e.g. 4.74999952316 for a 32 bit float. This is the kind of normalization this type performs.
 * 
 * Normalization uses typed arrays and BigInt to get the binary representation of a number as a
 * floating point, meaning possibly less browser support than {@link RealType}. While the logic for
 * handling exclusive bounds is ommitted, the {@link RangeType.compare} method needs to perform
 * extra calculations to determine floating point adjacency; so the type is likely slower for
 * typical use cases.
 * 
 * Unlike {@link RealType}, an {@link RangeType.iterate} implementation is provided, which iterates
 * through every representable floating point number in the range. The {@link RangeType.sample}
 * implementation bears the same caveat however, in that we cannot map `[0,1)` to any floating point
 * range.
 * @implements {RangeType}
 */
const FloatNormType = {
	create,
	copy,
	size,
	compare(mode, a, b){
		const out = compare(mode, a, b);
		if (out.side){
			if (next_after(a, -out.side*Infinity) === b)
				out.distance = 0;
		}
		return out;
	},
	setStart(range, start, startExcl){
		if (startExcl)
			start = next_after(start, Infinity);
		range.start = start;
		return range;
	},
	setEnd(range, end, endExcl){
		if (endExcl)
			end = next_after(end, -Infinity);
		range.end = end;
		return range;
	},
	*iterate(range, reverse){
		let s, e;
		if (reverse){
			s = range.end;
			e = range.start;
		}
		else{
			s = range.start;
			e = range.end;
		}
		while (true){
			yield s;
			if (s === e)
				return;
			s = next_after(s, e);
		}
	},
	sample(range, i){
		return i*size(range) + range.start;
	}
};

export default FloatNormType;