/** Round `n` up to the next multiple of `alignment` (inclusive). */ // MAINTENANCE_TODO: Rename to `roundUp`
export function align(n: number, alignment: number): number { assert(Number.isInteger(n) && n >= 0, 'n must be a non-negative integer'); assert(Number.isInteger(alignment) && alignment > 0, 'alignment must be a positive integer'); return Math.ceil(n / alignment) * alignment;
}
/** Round `n` down to the next multiple of `alignment` (inclusive). */
export function roundDown(n: number, alignment: number): number { assert(Number.isInteger(n) && n >= 0, 'n must be a non-negative integer'); assert(Number.isInteger(alignment) && alignment > 0, 'alignment must be a positive integer'); return Math.floor(n / alignment) * alignment;
}
/** Clamp a number to the provided range. */
export function clamp(n: number, { min, max }: { min: number; max: number }): number { assert(max >= min); return Math.min(Math.max(n, min), max);
}
/** @returns 0 if |val| is a subnormal f64 number, otherwise returns |val| */
export function flushSubnormalNumberF64(val: number): number { return isSubnormalNumberF64(val) ? 0 : val;
}
/** @returns if number is within subnormal range of f64 */
export function isSubnormalNumberF64(n: number): boolean { return n > kValue.f64.negative.max && n < kValue.f64.positive.min;
}
/** @returns 0 if |val| is a subnormal f32 number, otherwise returns |val| */
export function flushSubnormalNumberF32(val: number): number { return isSubnormalNumberF32(val) ? 0 : val;
}
/** @returns if number is within subnormal range of f32 */
export function isSubnormalNumberF32(n: number): boolean { return n > kValue.f32.negative.max && n < kValue.f32.positive.min;
}
/** @returns if number is in the finite range of f32 */
export function isFiniteF32(n: number) { return n >= kValue.f32.negative.min && n <= kValue.f32.positive.max;
}
/** @returns 0 if |val| is a subnormal f16 number, otherwise returns |val| */
export function flushSubnormalNumberF16(val: number): number { return isSubnormalNumberF16(val) ? 0 : val;
}
/** @returns if number is within subnormal range of f16 */
export function isSubnormalNumberF16(n: number): boolean { return n > kValue.f16.negative.max && n < kValue.f16.positive.min;
}
/** @returns if number is in the finite range of f16 */
export function isFiniteF16(n: number) { return n >= kValue.f16.negative.min && n <= kValue.f16.positive.max;
}
/** Should FTZ occur during calculations or not */
export type FlushMode = 'flush' | 'no-flush';
/** Should nextAfter calculate towards positive infinity or negative infinity */
export type NextDirection = 'positive' | 'negative';
/** *Once-allocatedArrayBuffer/viewstoavoidoverheadofallocationwhen *convertingbetweennumericformats * *Usageofaonce-allocatedpatternlikethismakesnextAfterF64non-reentrant, *socannotcallitselfdirectlyorindirectly.
*/ const nextAfterF64Data = new ArrayBuffer(8); const nextAfterF64Int = new BigUint64Array(nextAfterF64Data); const nextAfterF64Float = new Float64Array(nextAfterF64Data);
nextAfterF32Float[0] = val; // This quantizes from number (f64) to f32 if (
(dir === 'positive' && nextAfterF32Float[0] <= val) ||
(dir === 'negative' && nextAfterF32Float[0] >= val)
) { // val is either f32 precise or quantizing rounded in the opposite direction // from what is needed, so need to calculate the value in the correct // direction. const is_positive = (nextAfterF32Int[0] & 0x80000000) === 0; if (is_positive === (dir === 'positive')) {
nextAfterF32Int[0] += 1;
} else {
nextAfterF32Int[0] -= 1;
}
}
// Checking for overflow if ((nextAfterF32Int[0] & 0x7f800000) === 0x7f800000) { if (dir === 'positive') { return kValue.f32.positive.infinity;
} else { return kValue.f32.negative.infinity;
}
}
nextAfterF16Float[0] = val; // This quantizes from number (f64) to f16 if (
(dir === 'positive' && nextAfterF16Float[0] <= val) ||
(dir === 'negative' && nextAfterF16Float[0] >= val)
) { // val is either f16 precise or quantizing rounded in the opposite direction // from what is needed, so need to calculate the value in the correct // direction. const is_positive = (nextAfterF16Hex[0] & 0x8000) === 0; if (is_positive === (dir === 'positive')) {
nextAfterF16Hex[0] += 1;
} else {
nextAfterF16Hex[0] -= 1;
}
}
// Checking for overflow if ((nextAfterF16Hex[0] & 0x7c00) === 0x7c00) { if (dir === 'positive') { return kValue.f16.positive.infinity;
} else { return kValue.f16.negative.infinity;
}
}
// For values out of bounds for f64 ulp(x) is defined as the // distance between the two nearest f64 representable numbers to the // appropriate edge, which also happens to be the maximum possible ULP. if (
target === Number.POSITIVE_INFINITY ||
target >= kValue.f64.positive.max ||
target === Number.NEGATIVE_INFINITY ||
target <= kValue.f64.negative.min
) { return kValue.f64.max_ulp;
}
// ulp(x) is min(after - before), where // before <= x <= after // before =/= after // before and after are f64 representable const before = nextAfterF64(target, 'negative', mode); const after = nextAfterF64(target, 'positive', mode); // Since number is internally a f64, |target| is always f64 representable, so // either before or after will be x return Math.min(target - before, after - target);
}
/** *@returnsulp(x),theunitofleastprecisionforaspecificnumberasa32-bitfloat * *ulp(x)isthedistancebetweenthetwofloatingpointnumbersnearestx. *Thisvalueisalsocalledunitoflastplace,ULP,and1ULP. *SeetheWGSLspecandhttp://www.ens-lyon.fr/LIP/Pub/Rapports/RR/RR2005/RR2005-09.pdf *foramoredetailed/nuanceddiscussionofthedefinitionofulp(x). * *@paramtargetnumbertocalculateULPfor *@parammodeshouldFTZoccurringduringcalculationornot
*/
export function oneULPF32(target: number, mode: FlushMode = 'flush'): number { if (Number.isNaN(target)) { return Number.NaN;
}
// For values out of bounds for f32 ulp(x) is defined as the // distance between the two nearest f32 representable numbers to the // appropriate edge, which also happens to be the maximum possible ULP. if (
target === Number.POSITIVE_INFINITY ||
target >= kValue.f32.positive.max ||
target === Number.NEGATIVE_INFINITY ||
target <= kValue.f32.negative.min
) { return kValue.f32.max_ulp;
}
// ulp(x) is min(after - before), where // before <= x <= after // before =/= after // before and after are f32 representable const before = nextAfterF32(target, 'negative', mode); const after = nextAfterF32(target, 'positive', mode); const converted: number = quantizeToF32(target); if (converted === target) { // |target| is f32 representable, so either before or after will be x return Math.min(target - before, after - target);
} else { // |target| is not f32 representable so taking distance of neighbouring f32s. return after - before;
}
}
/** *@returnsanintegervaluebetween0..0xffffffffusingasimplenon-cryptographichashfunction *@paramvaluesintegerstogeneratehashfrom.
*/
export function hashU32(...values: number[]) {
let n = 0x3504_f333; for (const v of values) {
n = v + (n << 7) + (n >>> 1);
n = (n * 0x29493) & 0xffff_ffff;
}
n ^= n >>> 8;
n += n << 15;
n = n & 0xffff_ffff; if (n < 0) {
n = ~n * 2 + 1;
} return n;
}
/** *@returnsulp(x),theunitofleastprecisionforaspecificnumberasa32-bitfloat * *ulp(x)isthedistancebetweenthetwofloatingpointnumbersnearestx. *Thisvalueisalsocalledunitoflastplace,ULP,and1ULP. *SeetheWGSLspecandhttp://www.ens-lyon.fr/LIP/Pub/Rapports/RR/RR2005/RR2005-09.pdf *foramoredetailed/nuanceddiscussionofthedefinitionofulp(x). * *@paramtargetnumbertocalculateULPfor *@parammodeshouldFTZoccurringduringcalculationornot
*/
export function oneULPF16(target: number, mode: FlushMode = 'flush'): number { if (Number.isNaN(target)) { return Number.NaN;
}
// For values out of bounds for f16 ulp(x) is defined as the // distance between the two nearest f16 representable numbers to the // appropriate edge, which also happens to be the maximum possible ULP. if (
target === Number.POSITIVE_INFINITY ||
target >= kValue.f16.positive.max ||
target === Number.NEGATIVE_INFINITY ||
target <= kValue.f16.negative.min
) { return kValue.f16.max_ulp;
}
// ulp(x) is min(after - before), where // before <= x <= after // before =/= after // before and after are f16 representable const before = nextAfterF16(target, 'negative', mode); const after = nextAfterF16(target, 'positive', mode); const converted: number = quantizeToF16(target); if (converted === target) { // |target| is f16 representable, so either before or after will be x return Math.min(target - before, after - target);
} else { // |target| is not f16 representable so taking distance of neighbouring f16s. return after - before;
}
}
/** *Calculatethevalidroundingswhenquantizingto64-bitfloats * *TS/JS'snumbertypeisinternallyaf64,sothesuppliedvaluewillbe *quanitizedbydefinition.Theonlycornercasesoccurifanon-finitevalue *isprovided,sincethevalidroundingsincludetheappropriateminormax *value. * *@paramnnumbertobequantized *@returnsalloftheacceptableroundingsforquantizingto64-bitsin *ascendingorder.
*/
export function correctlyRoundedF64(n: number): readonly number[] { assert(!Number.isNaN(n), `correctlyRoundedF32 not defined for NaN`); // Above f64 range if (n === Number.POSITIVE_INFINITY) { return [kValue.f64.positive.max, Number.POSITIVE_INFINITY];
}
// Below f64 range if (n === Number.NEGATIVE_INFINITY) { return [Number.NEGATIVE_INFINITY, kValue.f64.negative.min];
}
// Greater than or equal to the upper overflow boundry if (n >= 2 ** (kValue.f32.emax + 1)) { return [Number.POSITIVE_INFINITY];
}
// OOB, but less than the upper overflow boundary if (n > kValue.f32.positive.max) { return [kValue.f32.positive.max, Number.POSITIVE_INFINITY];
}
// f32 finite if (n <= kValue.f32.positive.max && n >= kValue.f32.negative.min) { const n_32 = quantizeToF32(n); if (n === n_32) { // n is precisely expressible as a f32, so should not be rounded return [n];
}
if (n_32 > n) { // n_32 rounded towards +inf, so is after n const other = nextAfterF32(n_32, 'negative', 'no-flush'); return [other, n_32];
} else { // n_32 rounded towards -inf, so is before n const other = nextAfterF32(n_32, 'positive', 'no-flush'); return [n_32, other];
}
}
// OOB, but greater the lower overflow boundary if (n > -(2 ** (kValue.f32.emax + 1))) { return [Number.NEGATIVE_INFINITY, kValue.f32.negative.min];
}
// Less than or equal to the lower overflow boundary return [Number.NEGATIVE_INFINITY];
}
// Greater than or equal to the upper overflow boundry if (n >= 2 ** (kValue.f16.emax + 1)) { return [Number.POSITIVE_INFINITY];
}
// OOB, but less than the upper overflow boundary if (n > kValue.f16.positive.max) { return [kValue.f16.positive.max, Number.POSITIVE_INFINITY];
}
// f16 finite if (n <= kValue.f16.positive.max && n >= kValue.f16.negative.min) { const n_16 = quantizeToF16(n); if (n === n_16) { // n is precisely expressible as a f16, so should not be rounded return [n];
}
if (n_16 > n) { // n_16 rounded towards +inf, so is after n const other = nextAfterF16(n_16, 'negative', 'no-flush'); return [other, n_16];
} else { // n_16 rounded towards -inf, so is before n const other = nextAfterF16(n_16, 'positive', 'no-flush'); return [n_16, other];
}
}
// OOB, but greater the lower overflow boundary if (n > -(2 ** (kValue.f16.emax + 1))) { return [Number.NEGATIVE_INFINITY, kValue.f16.negative.min];
}
// Less than or equal to the lower overflow boundary return [Number.NEGATIVE_INFINITY];
}
/** *CalculatesWGSLfrexp * *Splitsvalintoafractionandanexponentsothat *val=fraction*2^exponent. *Thefractionis0.0oritsmagnitudeisintherange[0.5,1.0). * *@paramvalthefloattosplit *@paramtraitthefloattype,f32orf16orf64 *@returnstheresultsofsplittingval
*/
export function frexp(val: number, trait: 'f32' | 'f16' | 'f64'): { fract: number; exp: number } { const buffer = new ArrayBuffer(8); const dataView = new DataView(buffer);
// expBitCount and fractBitCount is the bitwidth of exponent and fractional part of the given FP type. // expBias is the bias constant of exponent of the given FP type. // Biased exponent (unsigned integer, i.e. the exponent part of float) = unbiased exponent (signed integer) + expBias.
let expBitCount: number, fractBitCount: number, expBias: number; // To handle the exponent bits of given FP types (f16, f32, and f64), considering the highest 16 // bits is enough. // expMaskForHigh16Bits indicates the exponent bitfield in the highest 16 bits of the given FP // type, and targetExpBitsForHigh16Bits is the exponent bits that corresponding to unbiased // exponent -1, i.e. the exponent bits when the FP values is in range [0.5, 1.0).
let expMaskForHigh16Bits: number, targetExpBitsForHigh16Bits: number; // Helper function that store the given FP value into buffer as the given FP types
let setFloatToBuffer: (v: number) => void; // Helper function that read back FP value from buffer as the given FP types
let getFloatFromBuffer: () => number;
let isFinite: (v: number) => boolean;
let isSubnormal: (v: number) => boolean;
if (trait === 'f32') { // f32 bit pattern: s_eeeeeeee_fffffff_ffffffffffffffff
expBitCount = 8;
fractBitCount = 23;
expBias = 127; // The exponent bitmask for high 16 bits of f32.
expMaskForHigh16Bits = 0x7f80; // The target exponent bits is equal to those for f32 0.5 = 0x3f000000.
targetExpBitsForHigh16Bits = 0x3f00;
isFinite = isFiniteF32;
isSubnormal = isSubnormalNumberF32; // Enforce big-endian so that offset 0 is highest byte.
setFloatToBuffer = (v: number) => dataView.setFloat32(0, v, false);
getFloatFromBuffer = () => dataView.getFloat32(0, false);
} elseif (trait === 'f16') { // f16 bit pattern: s_eeeee_ffffffffff
expBitCount = 5;
fractBitCount = 10;
expBias = 15; // The exponent bitmask for 16 bits of f16.
expMaskForHigh16Bits = 0x7c00; // The target exponent bits is equal to those for f16 0.5 = 0x3800.
targetExpBitsForHigh16Bits = 0x3800;
isFinite = isFiniteF16;
isSubnormal = isSubnormalNumberF16; // Enforce big-endian so that offset 0 is highest byte.
setFloatToBuffer = (v: number) => setFloat16(dataView, 0, v, false);
getFloatFromBuffer = () => getFloat16(dataView, 0, false);
} else { assert(trait === 'f64'); // f64 bit pattern: s_eeeeeeeeeee_ffff_ffffffffffffffffffffffffffffffffffffffffffffffff
expBitCount = 11;
fractBitCount = 52;
expBias = 1023; // The exponent bitmask for 16 bits of f64.
expMaskForHigh16Bits = 0x7ff0; // The target exponent bits is equal to those for f64 0.5 = 0x3fe0_0000_0000_0000.
targetExpBitsForHigh16Bits = 0x3fe0;
isFinite = Number.isFinite;
isSubnormal = isSubnormalNumberF64; // Enforce big-endian so that offset 0 is highest byte.
setFloatToBuffer = (v: number) => dataView.setFloat64(0, v, false);
getFloatFromBuffer = () => dataView.getFloat64(0, false);
} // Helper function that extract the unbiased exponent of the float in buffer. const extractUnbiasedExpFromNormalFloatInBuffer = () => { // Assert the float in buffer is finite normal float. assert(isFinite(getFloatFromBuffer()) && !isSubnormal(getFloatFromBuffer())); // Get the highest 16 bits of float as uint16, which can contain the whole exponent part for both f16, f32, and f64. const high16BitsAsUint16 = dataView.getUint16(0, false); // Return the unbiased exp by masking, shifting and unbiasing. return ((high16BitsAsUint16 & expMaskForHigh16Bits) >> (16 - 1 - expBitCount)) - expBias;
}; // Helper function that modify the exponent of float in buffer to make it in range [0.5, 1.0). // By setting the unbiased exponent to -1, the fp value will be in range 2**-1 * [1.0, 2.0), i.e. [0.5, 1.0). const modifyExpOfNormalFloatInBuffer = () => { // Assert the float in buffer is finite normal float. assert(isFinite(getFloatFromBuffer()) && !isSubnormal(getFloatFromBuffer())); // Get the highest 16 bits of float as uint16, which contains the whole exponent part for both f16, f32, and f64. const high16BitsAsUint16 = dataView.getUint16(0, false); // Modify the exponent bits. const modifiedHigh16Bits =
(high16BitsAsUint16 & ~expMaskForHigh16Bits) | targetExpBitsForHigh16Bits; // Set back to buffer
dataView.setUint16(0, modifiedHigh16Bits, false);
};
// +/- 0.0 if (val === 0) { return { fract: val, exp: 0 };
} // NaN and Inf if (!isFinite(val)) { return { fract: val, exp: 0 };
}
setFloatToBuffer(val); // Don't use val below. Use helper functions working with buffer instead.
let exp = 0; // Normailze the value if it is subnormal. Increase the exponent by multiplying a subnormal value // with 2**fractBitCount will result in a finite normal FP value of the given FP type. if (isSubnormal(getFloatFromBuffer())) {
setFloatToBuffer(getFloatFromBuffer() * 2 ** fractBitCount);
exp = -fractBitCount;
} // A normal FP value v is represented as v = ((-1)**s)*(2**(unbiased exponent))*f, where f is in // range [1.0, 2.0). By moving a factor 2 from f to exponent, we have // v = ((-1)**s)*(2**(unbiased exponent + 1))*(f / 2), where (f / 2) is in range [0.5, 1.0), so // the exp = (unbiased exponent + 1) and fract = ((-1)**s)*(f / 2) is what we expect to get from // frexp function. Note that fract and v only differs in exponent bitfield as long as v is normal. // Calc the result exp by getting the unbiased float exponent and plus 1.
exp += extractUnbiasedExpFromNormalFloatInBuffer() + 1; // Modify the exponent of float in buffer to make it be in range [0.5, 1.0) to get fract.
modifyExpOfNormalFloatInBuffer();
return { fract: getFloatFromBuffer(), exp };
}
/** *Calculatesthelinearinterpolationbetweentwovaluesofagivenfractional. * *If|t|is0,|a|isreturned,if|t|is1,|b|isreturned,otherwise *interpolation/extrapolationequivalenttoa+t(b-a)isperformed. * *Numericalstableversionisadaptedfromhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0811r2.html
*/
export function lerp(a: number, b: number, t: number): number { if (!Number.isFinite(a) || !Number.isFinite(b)) { return Number.NaN;
}
if ((a <= 0.0 && b >= 0.0) || (a >= 0.0 && b <= 0.0)) { return t * b + (1 - t) * a;
}
if (t === 1.0) { return b;
}
const x = a + t * (b - a); return t > 1.0 === b > a ? Math.max(b, x) : Math.min(b, x);
}
// This constrains t to [0.0, 1.0] assert(idx >= 0); assert(steps > 0); assert(idx < steps);
if (steps === 1) { return a;
} if (idx === 0) { return a;
} if (idx === steps - 1) { return b;
}
const min = (x: bigint, y: bigint): bigint => { return x < y ? x : y;
}; const max = (x: bigint, y: bigint): bigint => { return x > y ? x : y;
};
// For number the variable t is used, there t = idx / (steps - 1), // but that is a fraction on [0, 1], so becomes either 0 or 1 when converted // to bigint, so need to expand things out. const big_idx = BigInt(idx); const big_steps = BigInt(steps); if ((a <= 0n && b >= 0n) || (a >= 0n && b <= 0n)) { return (b * big_idx) / (big_steps - 1n) + (a - (a * big_idx) / (big_steps - 1n));
}
const x = a + (b * big_idx) / (big_steps - 1n) - (a * big_idx) / (big_steps - 1n); return !(b > a) ? max(b, x) : min(b, x);
}
/** @returns a linear increasing range of numbers. */
export function linearRange(a: number, b: number, num_steps: number): readonly number[] { if (num_steps <= 0) { return [];
}
// Avoid division by 0 if (num_steps === 1) { return [a];
}
let special_pos: number[] = []; // The first interior point for 'pos_norm' is at 3. Because we have two special values we start allowing these // special values as soon as they will fit as interior values. if (counts.pos_norm >= 4) {
special_pos = [ // Largest float as signed integer 0x4effffff, // Largest float as unsigned integer 0x4f7fffff,
];
} // Generating bit fields first and then converting to f32, so that the spread across the possible f32 values is more // even. Generating against the bounds of f32 values directly results in the values being extremely biased towards the // extremes, since they are so much larger. const bit_fields = [
...linearRange(kBit.f32.negative.min, kBit.f32.negative.max, counts.neg_norm),
...linearRange(
kBit.f32.negative.subnormal.min,
kBit.f32.negative.subnormal.max,
counts.neg_sub
), // -0.0 0x80000000, // +0.0 0,
...linearRange(
kBit.f32.positive.subnormal.min,
kBit.f32.positive.subnormal.max,
counts.pos_sub
),
...[
...linearRange(
kBit.f32.positive.min,
kBit.f32.positive.max,
counts.pos_norm - special_pos.length
),
...special_pos,
].sort((n1, n2) => n1 - n2),
].map(Math.trunc); return bit_fields.map(reinterpretU32AsF32);
}
// Generating bit fields first and then converting to f16, so that the spread across the possible f16 values is more // even. Generating against the bounds of f16 values directly results in the values being extremely biased towards the // extremes, since they are so much larger. const bit_fields = [
...linearRange(kBit.f16.negative.min, kBit.f16.negative.max, counts.neg_norm),
...linearRange(
kBit.f16.negative.subnormal.min,
kBit.f16.negative.subnormal.max,
counts.neg_sub
), // -0.0 0x8000, // +0.0 0,
...linearRange(
kBit.f16.positive.subnormal.min,
kBit.f16.positive.subnormal.max,
counts.pos_sub
),
...linearRange(kBit.f16.positive.min, kBit.f16.positive.max, counts.pos_norm),
].map(Math.trunc); return bit_fields.map(reinterpretU16AsF16);
}
// Generating bit fields first and then converting to f64, so that the spread across the possible f64 values is more // even. Generating against the bounds of f64 values directly results in the values being extremely biased towards the // extremes, since they are so much larger. const bit_fields = [
...linearRangeBigInt(kBit.f64.negative.min, kBit.f64.negative.max, counts.neg_norm),
...linearRangeBigInt(
kBit.f64.negative.subnormal.min,
kBit.f64.negative.subnormal.max,
counts.neg_sub
), // -0.0 0x8000_0000_0000_0000n, // +0.0 0n,
...linearRangeBigInt(
kBit.f64.positive.subnormal.min,
kBit.f64.positive.subnormal.max,
counts.pos_sub
),
...linearRangeBigInt(kBit.f64.positive.min, kBit.f64.positive.max, counts.pos_norm),
]; return bit_fields.map(reinterpretU64AsF64);
}
/** *@returnsanascendingsortedarrayoff64valuesspreadoverspecificrangeoff64normalfloats * *Numbersaredividedinto4regions:negative64-bitnormals,negative64-bitsubnormals,positive64-bitsubnormals& *positive64-bitnormals. *Zeroisincluded. * *Numbersaregeneratedviatakingalinearspreadofthebitfieldrepresentationsofthevaluesineachregion.This *meansthatnumberofprecisef64valuesbetweeneachreturnedvalueinaregionshouldbeaboutthesame.Thisallows *forawiderangeofmagnitudestobegenerated,insteadofbeingextremelybiasedtowardstheedgesoftherange. * *@parambeginanegativef64normalfloatvalue *@paramendapositivef64normalfloatvalue *@paramcountsstructureparamwith4entriesindicatingthenumberofentries *tobegeneratedeachregion,entriesmustbe0orgreater.
*/
export function limitedScalarF64Range(
begin: number,
end: number,
counts: { neg_norm?: number; neg_sub?: number; pos_sub: number; pos_norm: number } = {
pos_sub: 10,
pos_norm: 50,
}
): Array<number> { assert(
begin <= kValue.f64.negative.max,
`Beginning of range ${begin} must be negative f64 normal`
); assert(end >= kValue.f64.positive.min, `Ending of range ${end} must be positive f64 normal`);
const u64_begin = reinterpretF64AsU64(begin); const u64_end = reinterpretF64AsU64(end); // Generating bit fields first and then converting to f64, so that the spread across the possible f64 values is more // even. Generating against the bounds of f64 values directly results in the values being extremely biased towards the // extremes, since they are so much larger. const bit_fields = [
...linearRangeBigInt(u64_begin, kBit.f64.negative.max, counts.neg_norm),
...linearRangeBigInt(
kBit.f64.negative.subnormal.min,
kBit.f64.negative.subnormal.max,
counts.neg_sub
), // -0.0 0x8000_0000_0000_0000n, // +0.0 0n,
...linearRangeBigInt(
kBit.f64.positive.subnormal.min,
kBit.f64.positive.subnormal.max,
counts.pos_sub
),
...linearRangeBigInt(kBit.f64.positive.min, u64_end, counts.pos_norm),
]; return bit_fields.map(reinterpretU64AsF64);
}
/** Short list of i32 values of interest to test against */ const kInterestingI32Values: readonly number[] = [
kValue.i32.negative.max,
Math.trunc(kValue.i32.negative.max / 2),
-256,
-10,
-1, 0, 1, 10, 256,
Math.trunc(kValue.i32.positive.max / 2),
kValue.i32.positive.max,
];
/** @returns minimal i32 values that cover the entire range of i32 behaviours * *ThisisusedinsteadoffullI32Rangewhenthenumberoftestcasesbeing *generatedisasuperlinearfunctionofthelengthofi32valueswhichis *leadingtotimeouts.
*/
export function sparseI32Range(): readonly number[] { return kInterestingI32Values;
}
/** Short list of u32 values of interest to test against */ const kInterestingU32Values: readonly number[] = [ 0, 1, 10, 256,
Math.trunc(kValue.u32.max / 2),
kValue.u32.max,
];
/** @returns minimal u32 values that cover the entire range of u32 behaviours * *ThisisusedinsteadoffullU32Rangewhenthenumberoftestcasesbeing *generatedisasuperlinearfunctionofthelengthofu32valueswhichis *leadingtotimeouts.
*/
export function sparseU32Range(): readonly number[] { return kInterestingU32Values;
}
/** Short list of i64 values of interest to test against */ const kInterestingI64Values: readonly bigint[] = [
kValue.i64.negative.max,
kValue.i64.negative.max / 2n,
-256n,
-10n,
-1n, 0n, 1n, 10n, 256n,
kValue.i64.positive.max / 2n,
kValue.i64.positive.max,
];
/** @returns minimal i64 values that cover the entire range of i64 behaviours * *ThisisusedinsteadoffullI64Rangewhenthenumberoftestcasesbeing *generatedisasuperlinearfunctionofthelengthofi64valueswhichis *leadingtotimeouts.
*/
export function sparseI64Range(): readonly bigint[] { return kInterestingI64Values;
}
/** Short list of f32 values of interest to test against */ const kInterestingF32Values: readonly number[] = [
kValue.f32.negative.min,
-10.0,
-1.0,
-0.125,
kValue.f32.negative.max,
kValue.f32.negative.subnormal.min,
kValue.f32.negative.subnormal.max,
-0.0, 0.0,
kValue.f32.positive.subnormal.min,
kValue.f32.positive.subnormal.max,
kValue.f32.positive.min, 0.125, 1.0, 10.0,
kValue.f32.positive.max,
];
/** @returns minimal f32 values that cover the entire range of f32 behaviours * *Hasspeciallyselectedvaluesthatcoveredgecases,normals,andsubnormals. *ThisisusedinsteadoffullF32Rangewhenthenumberoftestcasesbeing *generatedisasuperlinearfunctionofthelengthoff32valueswhichis *leadingtotimeouts. * *Thesevalueshavebeenchosentoattempttotestthewidestrangeoff32 *behavioursinthelowestnumberofentries,somaypotentiallymissfunction *specificvaluesofinterest.Ifthereareknownvaluesofinterestthey *shouldbeappendedtothislistinthetestgenerationcode.
*/
export function sparseScalarF32Range(): readonly number[] { return kInterestingF32Values;
}
/** *Returnsaminimalsetofmatrices,indexedbydimensioncontaining *interestingfloatvalues. * *Thisisthematrixanalogueof`sparseVectorF32Range`,soitisproducinga *minimalcoveragesetofmatricesthattestalloftheinterestingf32values. *Thereisnotamoreexpansivesetofmatrices,sincematricesareevenmore *expensivethanvectorsforincreasingruntimewithcoverage. * *AlloftheinterestingfloatsfromsparseScalarF32areguaranteedtobe *tested,butnotineveryposition.
*/
export function sparseMatrixF32Range(c: number, r: number): ROArrayArrayArray<number> { assert(
c === 2 || c === 3 || c === 4, 'sparseMatrixF32Range only accepts column counts of 2, 3, and 4'
); assert(
r === 2 || r === 3 || r === 4, 'sparseMatrixF32Range only accepts row counts of 2, 3, and 4'
); return kSparseMatrixF32Values[c][r];
}
/** Short list of f16 values of interest to test against */ const kInterestingF16Values: readonly number[] = [
kValue.f16.negative.min,
-10.0,
-1.0,
-0.125,
kValue.f16.negative.max,
kValue.f16.negative.subnormal.min,
kValue.f16.negative.subnormal.max,
-0.0, 0.0,
kValue.f16.positive.subnormal.min,
kValue.f16.positive.subnormal.max,
kValue.f16.positive.min, 0.125, 1.0, 10.0,
kValue.f16.positive.max,
];
/** @returns minimal f16 values that cover the entire range of f16 behaviours * *Hasspeciallyselectedvaluesthatcoveredgecases,normals,andsubnormals. *ThisisusedinsteadoffullF16Rangewhenthenumberoftestcasesbeing *generatedisasuperlinearfunctionofthelengthoff16valueswhichis *leadingtotimeouts. * *Thesevalueshavebeenchosentoattempttotestthewidestrangeoff16 *behavioursinthelowestnumberofentries,somaypotentiallymissfunction *specificvaluesofinterest.Ifthereareknownvaluesofinterestthey *shouldbeappendedtothislistinthetestgenerationcode.
*/
export function sparseScalarF16Range(): readonly number[] { return kInterestingF16Values;
}
/** *Returnsaminimalsetofmatrices,indexedbydimensioncontaininginteresting *f16values. * *Thisisthematrixanalogueof`sparseVectorF16Range`,soitisproducinga *minimalcoveragesetofmatricesthattestalloftheinterestingf16values. *Thereisnotamoreexpansivesetofmatrices,sincematricesareevenmore *expensivethanvectorsforincreasingruntimewithcoverage. * *AlloftheinterestingfloatsfromsparseScalarF16areguaranteedtobetested,but *notineveryposition.
*/
export function sparseMatrixF16Range(c: number, r: number): ROArrayArray<number>[] { assert(
c === 2 || c === 3 || c === 4, 'sparseMatrixF16Range only accepts column counts of 2, 3, and 4'
); assert(
r === 2 || r === 3 || r === 4, 'sparseMatrixF16Range only accepts row counts of 2, 3, and 4'
); return kSparseMatrixF16Values[c][r];
}
/** Short list of f64 values of interest to test against */ const kInterestingF64Values: readonly number[] = [
kValue.f64.negative.min,
-10.0,
-1.0,
-0.125,
kValue.f64.negative.max,
kValue.f64.negative.subnormal.min,
kValue.f64.negative.subnormal.max,
-0.0, 0.0,
kValue.f64.positive.subnormal.min,
kValue.f64.positive.subnormal.max,
kValue.f64.positive.min, 0.125, 1.0, 10.0,
kValue.f64.positive.max,
];
/** @returns minimal F64 values that cover the entire range of F64 behaviours * *Hasspeciallyselectedvaluesthatcoveredgecases,normals,andsubnormals. *ThisisusedinsteadoffullF64Rangewhenthenumberoftestcasesbeing *generatedisasuperlinearfunctionofthelengthofF64valueswhichis *leadingtotimeouts. * *ThesevalueshavebeenchosentoattempttotestthewidestrangeofF64 *behavioursinthelowestnumberofentries,somaypotentiallymissfunction *specificvaluesofinterest.Ifthereareknownvaluesofinterestthey *shouldbeappendedtothislistinthetestgenerationcode.
*/
export function sparseScalarF64Range(): readonly number[] { return kInterestingF64Values;
}
/** *Returnsaminimalsetofmatrices,indexedbydimensioncontaininginteresting *floatvalues. * *Thisisthematrixanalogueof`sparseVectorF64Range`,soitisproducinga *minimalcoveragesetofmatricesthattestalltheinterestingf64values. *Thereisnotamoreexpansivesetofmatrices,sincematricesareevenmore *expensivethanvectorsforincreasingruntimewithcoverage. * *AlltheinterestingfloatsfromsparseScalarF64areguaranteedtobetested,but *notineveryposition.
*/
export function sparseMatrixF64Range(cols: number, rows: number): ROArrayArrayArray<number> { assert(
cols === 2 || cols === 3 || cols === 4, 'sparseMatrixF64Range only accepts column counts of 2, 3, and 4'
); assert(
rows === 2 || rows === 3 || rows === 4, 'sparseMatrixF64Range only accepts row counts of 2, 3, and 4'
); return kSparseMatrixF64Values[cols][rows];
}
/** *@returnstheresultmatrixinArray<Array<number>>type. * *Matrixmultiplication.AismxnandBisnxp.Returns *mxpresult.
*/ // A is m x n. B is n x p. product is m x p.
export function multiplyMatrices(
A: Array<Array<number>>,
B: Array<Array<number>>
): Array<Array<number>> { assert(A.length > 0 && B.length > 0 && B[0].length > 0 && A[0].length === B.length); const product = new Array<Array<number>>(A.length); for (let i = 0; i < product.length; ++i) {
product[i] = new Array<number>(B[0].length).fill(0);
}
for (let m = 0; m < A.length; ++m) { for (let p = 0; p < B[0].length; ++p) { for (let n = 0; n < B.length; ++n) {
product[m][p] += A[m][n] * B[n][p];
}
}
}
return product;
}
/** Sign-extend the `bits`-bit number `n` to a 32-bit signed integer. */
export function signExtend(n: number, bits: number): number { const shift = 32 - bits; return (n << shift) >> shift;
}
export interface QuantizeFunc<T> {
(num: T): T;
}
/** @returns the closest 32-bit floating point value to the input */
export function quantizeToF32(num: number): number { return Math.fround(num);
}
/** @returns the closest 16-bit floating point value to the input */
export function quantizeToF16(num: number): number { return hfround(num);
}
/** *@returnstheclosest32-bitsignedintegervaluetotheinput,rounding *towards0,ifnotalreadyaninteger
*/
export function quantizeToI32(num: number): number { if (num >= kValue.i32.positive.max) { return kValue.i32.positive.max;
} if (num <= kValue.i32.negative.min) { return kValue.i32.negative.min;
} return Math.trunc(num);
}
/** *@returnstheclosest32-bitunsignedintegervaluetotheinput,rounding *towards0,ifnotalreadyaninteger
*/
export function quantizeToU32(num: number): number { if (num >= kValue.u32.max) { return kValue.u32.max;
} if (num <= 0) { return0;
} return Math.trunc(num);
}
/** *@returnstheclosest64-bitsignedintegervaluetotheinput.
*/
export function quantizeToI64(num: bigint): bigint { if (num >= kValue.i64.positive.max) { return kValue.i64.positive.max;
} if (num <= kValue.i64.negative.min) { return kValue.i64.negative.min;
} return num;
}
/** @returns whether the number is an integer and a power of two */
export function isPowerOfTwo(n: number): boolean { if (!Number.isInteger(n)) { returnfalse;
} assert((n | 0) === n, 'isPowerOfTwo only supports 32-bit numbers'); return n !== 0 && (n & (n - 1)) === 0;
}
/** @returns the Greatest Common Divisor (GCD) of the inputs */
export function gcd(a: number, b: number): number { assert(Number.isInteger(a) && a > 0); assert(Number.isInteger(b) && b > 0);
while (b !== 0) { const bTemp = b;
b = a % b;
a = bTemp;
}
return a;
}
/** @returns the Least Common Multiplier (LCM) of the inputs */
export function lcm(a: number, b: number): number { return (a * b) / gcd(a, b);
}
/** @returns the cross of an array with the intermediate result of cartesianProduct * *@paramelementsarrayofvaluestocrosswiththeintermediateresultof *cartesianProduct *@paramintermediatearraysofvaluesrepresentingthepartialresultof *cartesianProduct
*/ function cartesianProductImpl<T>(
elements: readonly T[],
intermediate: ROArrayArray<T>
): ROArrayArray<T> { const result: T[][] = [];
elements.forEach((e: T) => { if (intermediate.length > 0) {
intermediate.forEach((i: readonly T[]) => {
result.push([...i, e]);
});
} else {
result.push([e]);
}
}); return result;
}
/** @returns the cartesian product (NxMx...) of a set of arrays * *Thisisimplementedbycalculatingthecrossofasingleinputagainstan *intermediateresultforeachinputtobuildupthefinalarrayofarrays. * *Thereareexamplesofdoingthismoresuccinctlyusingmap&reduceonline, *buttheyareabitmoreopaquetoread. * *@paraminputsarraysofnumberstocalculatecartesianproductover
*/
export function cartesianProduct<T>(...inputs: ROArrayArray<T>): ROArrayArray<T> {
let result: ROArrayArray<T> = [];
inputs.forEach((i: readonly T[]) => {
result = cartesianProductImpl<T>(i, result);
});
return result;
}
/** @returns all of the permutations of an array * *Recursivelycalculatesallofthepermutations,doesnotcullduplicate *entries. * *Onlyfeasibleforinputsoflengths5orso,sincethenumberofpermutations *is(input.length)!,sowillcausethestacktoexplodeforlongerinputs. * *Thiscodecouldbemadeiterativeusingsomethinglike *Steinhaus–Johnson–Trotterandadditionallyturnedintoageneratortoreduce *thestacksize,butthereisstillafundamentalcombinatorialexplosion *herethatwillaffectruntime. * *@paraminputthearraytogetpermutationsof
*/
export function calculatePermutations<T>(input: readonly T[]): ROArrayArray<T> { if (input.length === 0) { return [];
}
if (input.length === 1) { return [input];
}
if (input.length === 2) { return [input, [input[1], input[0]]];
}
/** *Subtracts2vectors
*/
export function subtractVectors(v1: readonly number[], v2: readonly number[]) { return v1.map((v, i) => v - v2[i]);
}
/** *Computesthedotproductof2vectors
*/
export function dotProduct(v1: readonly number[], v2: readonly number[]) { return v1.reduce((a, v, i) => a + v * v2[i], 0);
}
/** @returns the absolute value of a bigint */
export function absBigInt(v: bigint): bigint { return v < 0n ? -v : v;
}
/** @returns the maximum from a list of bigints */
export function maxBigInt(...vals: bigint[]): bigint { return vals.reduce((prev, cur) => (cur > prev ? cur : prev));
}
/** @returns the minimum from a list of bigints */
export function minBigInt(...vals: bigint[]): bigint { return vals.reduce((prev, cur) => (cur < prev ? cur : prev));
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.62 Sekunden
(vorverarbeitet am 2026-06-10)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.