From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../checkout/src/unittests/f32_interval.spec.ts | 3418 ++++++++++++++++++++ 1 file changed, 3418 insertions(+) create mode 100644 dom/webgpu/tests/cts/checkout/src/unittests/f32_interval.spec.ts (limited to 'dom/webgpu/tests/cts/checkout/src/unittests/f32_interval.spec.ts') diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/f32_interval.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/f32_interval.spec.ts new file mode 100644 index 0000000000..435d78ecd6 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/unittests/f32_interval.spec.ts @@ -0,0 +1,3418 @@ +export const description = ` +F32Interval unit tests. +`; + +import { makeTestGroup } from '../common/framework/test_group.js'; +import { objectEquals } from '../common/util/util.js'; +import { kValue } from '../webgpu/util/constants.js'; +import { + absInterval, + absoluteErrorInterval, + acosInterval, + acoshAlternativeInterval, + acoshPrimaryInterval, + additionInterval, + asinInterval, + asinhInterval, + atanInterval, + atan2Interval, + atanhInterval, + ceilInterval, + clampMedianInterval, + clampMinMaxInterval, + correctlyRoundedInterval, + cosInterval, + coshInterval, + crossInterval, + degreesInterval, + distanceInterval, + divisionInterval, + dotInterval, + expInterval, + exp2Interval, + F32Interval, + faceForwardIntervals, + floorInterval, + fmaInterval, + fractInterval, + IntervalBounds, + inverseSqrtInterval, + ldexpInterval, + lengthInterval, + logInterval, + log2Interval, + maxInterval, + minInterval, + mixImpreciseInterval, + mixPreciseInterval, + multiplicationInterval, + negationInterval, + normalizeInterval, + powInterval, + quantizeToF16Interval, + radiansInterval, + reflectInterval, + refractInterval, + remainderInterval, + roundInterval, + saturateInterval, + signInterval, + sinInterval, + sinhInterval, + smoothStepInterval, + sqrtInterval, + stepInterval, + subtractionInterval, + tanInterval, + tanhInterval, + toF32Vector, + truncInterval, + ulpInterval, + unpack2x16floatInterval, + unpack2x16snormInterval, + unpack2x16unormInterval, + unpack4x8snormInterval, + unpack4x8unormInterval, + modfInterval, + toF32Interval, +} from '../webgpu/util/f32_interval.js'; +import { hexToF32, hexToF64, oneULP } from '../webgpu/util/math.js'; + +import { UnitTest } from './unit_test.js'; + +export const g = makeTestGroup(UnitTest); + +/** Bounds indicating an expectation of an interval of all possible values */ +const kAny: IntervalBounds = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]; + +/** @returns a number N * ULP greater than the provided number */ +function plusNULP(x: number, n: number): number { + return x + n * oneULP(x); +} + +/** @returns a number one ULP greater than the provided number */ +function plusOneULP(x: number): number { + return plusNULP(x, 1); +} + +/** @returns a number N * ULP less than the provided number */ +function minusNULP(x: number, n: number): number { + return x - n * oneULP(x); +} + +/** @returns a number one ULP less than the provided number */ +function minusOneULP(x: number): number { + return minusNULP(x, 1); +} + +/** @returns the expected IntervalBounds adjusted by the given error function + * + * @param expected the bounds to be adjusted + * @param error error function to adjust the bounds via + */ +function applyError(expected: IntervalBounds, error: (n: number) => number): IntervalBounds { + if (expected !== kAny) { + const begin = expected[0]; + const end = expected.length === 2 ? expected[1] : begin; + expected = [begin - error(begin), end + error(end)]; + } + + return expected; +} + +interface ConstructorCase { + input: IntervalBounds; + expected: IntervalBounds; +} + +g.test('constructor') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Common cases + { input: [0, 10], expected: [0, 10]}, + { input: [-5, 0], expected: [-5, 0]}, + { input: [-5, 10], expected: [-5, 10]}, + { input: [0], expected: [0]}, + { input: [10], expected: [10]}, + { input: [-5], expected: [-5]}, + + // Edges + { input: [0, kValue.f32.positive.max], expected: [0, kValue.f32.positive.max]}, + { input: [kValue.f32.negative.min, 0], expected: [kValue.f32.negative.min, 0]}, + { input: [kValue.f32.negative.min, kValue.f32.positive.max], expected: [kValue.f32.negative.min, kValue.f32.positive.max]}, + + // Out of range + { input: [0, 2 * kValue.f32.positive.max], expected: [0, 2 * kValue.f32.positive.max]}, + { input: [2 * kValue.f32.negative.min, 0], expected: [2 * kValue.f32.negative.min, 0]}, + { input: [2 * kValue.f32.negative.min, 2 * kValue.f32.positive.max], expected: [2 * kValue.f32.negative.min, 2 * kValue.f32.positive.max]}, + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: [0, Number.POSITIVE_INFINITY]}, + { input: [kValue.f32.infinity.negative, 0], expected: [Number.NEGATIVE_INFINITY, 0]}, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny}, + ] + ) + .fn(t => { + const i = new F32Interval(...t.params.input); + t.expect( + objectEquals(i.bounds(), t.params.expected), + `F32Interval([${t.params.input}]) returned ${i}. Expected [${t.params.expected}]` + ); + }); + +interface ContainsNumberCase { + bounds: IntervalBounds; + value: number; + expected: boolean; +} + +g.test('contains_number') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Common usage + { bounds: [0, 10], value: 0, expected: true }, + { bounds: [0, 10], value: 10, expected: true }, + { bounds: [0, 10], value: 5, expected: true }, + { bounds: [0, 10], value: -5, expected: false }, + { bounds: [0, 10], value: 50, expected: false }, + { bounds: [0, 10], value: Number.NaN, expected: false }, + { bounds: [-5, 10], value: 0, expected: true }, + { bounds: [-5, 10], value: 10, expected: true }, + { bounds: [-5, 10], value: 5, expected: true }, + { bounds: [-5, 10], value: -5, expected: true }, + { bounds: [-5, 10], value: -6, expected: false }, + { bounds: [-5, 10], value: 50, expected: false }, + { bounds: [-5, 10], value: -10, expected: false }, + + // Point + { bounds: [0], value: 0, expected: true }, + { bounds: [0], value: 10, expected: false }, + { bounds: [0], value: -1000, expected: false }, + { bounds: [10], value: 10, expected: true }, + { bounds: [10], value: 0, expected: false }, + { bounds: [10], value: -10, expected: false }, + { bounds: [10], value: 11, expected: false }, + + // Upper infinity + { bounds: [0, kValue.f32.infinity.positive], value: kValue.f32.positive.min, expected: true }, + { bounds: [0, kValue.f32.infinity.positive], value: kValue.f32.positive.max, expected: true }, + { bounds: [0, kValue.f32.infinity.positive], value: kValue.f32.infinity.positive, expected: true }, + { bounds: [0, kValue.f32.infinity.positive], value: kValue.f32.negative.min, expected: false }, + { bounds: [0, kValue.f32.infinity.positive], value: kValue.f32.negative.max, expected: false }, + { bounds: [0, kValue.f32.infinity.positive], value: kValue.f32.infinity.negative, expected: false }, + + // Lower infinity + { bounds: [kValue.f32.infinity.negative, 0], value: kValue.f32.positive.min, expected: false }, + { bounds: [kValue.f32.infinity.negative, 0], value: kValue.f32.positive.max, expected: false }, + { bounds: [kValue.f32.infinity.negative, 0], value: kValue.f32.infinity.positive, expected: false }, + { bounds: [kValue.f32.infinity.negative, 0], value: kValue.f32.negative.min, expected: true }, + { bounds: [kValue.f32.infinity.negative, 0], value: kValue.f32.negative.max, expected: true }, + { bounds: [kValue.f32.infinity.negative, 0], value: kValue.f32.infinity.negative, expected: true }, + + // Full infinity + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: kValue.f32.positive.min, expected: true }, + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: kValue.f32.positive.max, expected: true }, + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: kValue.f32.infinity.positive, expected: true }, + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: kValue.f32.negative.min, expected: true }, + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: kValue.f32.negative.max, expected: true }, + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: kValue.f32.infinity.negative, expected: true }, + { bounds: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], value: Number.NaN, expected: true }, + + // Maximum f32 boundary + { bounds: [0, kValue.f32.positive.max], value: kValue.f32.positive.min, expected: true }, + { bounds: [0, kValue.f32.positive.max], value: kValue.f32.positive.max, expected: true }, + { bounds: [0, kValue.f32.positive.max], value: kValue.f32.infinity.positive, expected: false }, + { bounds: [0, kValue.f32.positive.max], value: kValue.f32.negative.min, expected: false }, + { bounds: [0, kValue.f32.positive.max], value: kValue.f32.negative.max, expected: false }, + { bounds: [0, kValue.f32.positive.max], value: kValue.f32.infinity.negative, expected: false }, + + // Minimum f32 boundary + { bounds: [kValue.f32.negative.min, 0], value: kValue.f32.positive.min, expected: false }, + { bounds: [kValue.f32.negative.min, 0], value: kValue.f32.positive.max, expected: false }, + { bounds: [kValue.f32.negative.min, 0], value: kValue.f32.infinity.positive, expected: false }, + { bounds: [kValue.f32.negative.min, 0], value: kValue.f32.negative.min, expected: true }, + { bounds: [kValue.f32.negative.min, 0], value: kValue.f32.negative.max, expected: true }, + { bounds: [kValue.f32.negative.min, 0], value: kValue.f32.infinity.negative, expected: false }, + + // Out of range high + { bounds: [0, 2 * kValue.f32.positive.max], value: kValue.f32.positive.min, expected: true }, + { bounds: [0, 2 * kValue.f32.positive.max], value: kValue.f32.positive.max, expected: true }, + { bounds: [0, 2 * kValue.f32.positive.max], value: kValue.f32.infinity.positive, expected: false }, + { bounds: [0, 2 * kValue.f32.positive.max], value: kValue.f32.negative.min, expected: false }, + { bounds: [0, 2 * kValue.f32.positive.max], value: kValue.f32.negative.max, expected: false }, + { bounds: [0, 2 * kValue.f32.positive.max], value: kValue.f32.infinity.negative, expected: false }, + + // Out of range low + { bounds: [2 * kValue.f32.negative.min, 0], value: kValue.f32.positive.min, expected: false }, + { bounds: [2 * kValue.f32.negative.min, 0], value: kValue.f32.positive.max, expected: false }, + { bounds: [2 * kValue.f32.negative.min, 0], value: kValue.f32.infinity.positive, expected: false }, + { bounds: [2 * kValue.f32.negative.min, 0], value: kValue.f32.negative.min, expected: true }, + { bounds: [2 * kValue.f32.negative.min, 0], value: kValue.f32.negative.max, expected: true }, + { bounds: [2 * kValue.f32.negative.min, 0], value: kValue.f32.infinity.negative, expected: false }, + + // Subnormals + { bounds: [0, kValue.f32.positive.min], value: kValue.f32.subnormal.positive.min, expected: true }, + { bounds: [0, kValue.f32.positive.min], value: kValue.f32.subnormal.positive.max, expected: true }, + { bounds: [0, kValue.f32.positive.min], value: kValue.f32.subnormal.negative.min, expected: false }, + { bounds: [0, kValue.f32.positive.min], value: kValue.f32.subnormal.negative.max, expected: false }, + { bounds: [kValue.f32.negative.max, 0], value: kValue.f32.subnormal.positive.min, expected: false }, + { bounds: [kValue.f32.negative.max, 0], value: kValue.f32.subnormal.positive.max, expected: false }, + { bounds: [kValue.f32.negative.max, 0], value: kValue.f32.subnormal.negative.min, expected: true }, + { bounds: [kValue.f32.negative.max, 0], value: kValue.f32.subnormal.negative.max, expected: true }, + { bounds: [0, kValue.f32.subnormal.positive.min], value: kValue.f32.subnormal.positive.min, expected: true }, + { bounds: [0, kValue.f32.subnormal.positive.min], value: kValue.f32.subnormal.positive.max, expected: false }, + { bounds: [0, kValue.f32.subnormal.positive.min], value: kValue.f32.subnormal.negative.min, expected: false }, + { bounds: [0, kValue.f32.subnormal.positive.min], value: kValue.f32.subnormal.negative.max, expected: false }, + { bounds: [kValue.f32.subnormal.negative.max, 0], value: kValue.f32.subnormal.positive.min, expected: false }, + { bounds: [kValue.f32.subnormal.negative.max, 0], value: kValue.f32.subnormal.positive.max, expected: false }, + { bounds: [kValue.f32.subnormal.negative.max, 0], value: kValue.f32.subnormal.negative.min, expected: false }, + { bounds: [kValue.f32.subnormal.negative.max, 0], value: kValue.f32.subnormal.negative.max, expected: true }, + ] + ) + .fn(t => { + const i = new F32Interval(...t.params.bounds); + const value = t.params.value; + const expected = t.params.expected; + + const got = i.contains(value); + t.expect(expected === got, `${i}.contains(${value}) returned ${got}. Expected ${expected}`); + }); + +interface ContainsIntervalCase { + lhs: IntervalBounds; + rhs: IntervalBounds; + expected: boolean; +} + +g.test('contains_interval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Common usage + { lhs: [-10, 10], rhs: [0], expected: true}, + { lhs: [-10, 10], rhs: [-1, 0], expected: true}, + { lhs: [-10, 10], rhs: [0, 2], expected: true}, + { lhs: [-10, 10], rhs: [-1, 2], expected: true}, + { lhs: [-10, 10], rhs: [0, 10], expected: true}, + { lhs: [-10, 10], rhs: [-10, 2], expected: true}, + { lhs: [-10, 10], rhs: [-10, 10], expected: true}, + { lhs: [-10, 10], rhs: [-100, 10], expected: false}, + + // Upper infinity + { lhs: [0, kValue.f32.infinity.positive], rhs: [0], expected: true}, + { lhs: [0, kValue.f32.infinity.positive], rhs: [-1, 0], expected: false}, + { lhs: [0, kValue.f32.infinity.positive], rhs: [0, 1], expected: true}, + { lhs: [0, kValue.f32.infinity.positive], rhs: [0, kValue.f32.positive.max], expected: true}, + { lhs: [0, kValue.f32.infinity.positive], rhs: [0, kValue.f32.infinity.positive], expected: true}, + { lhs: [0, kValue.f32.infinity.positive], rhs: [100, kValue.f32.infinity.positive], expected: true}, + { lhs: [0, kValue.f32.infinity.positive], rhs: [Number.NEGATIVE_INFINITY, kValue.f32.infinity.positive], expected: false}, + + // Lower infinity + { lhs: [kValue.f32.infinity.negative, 0], rhs: [0], expected: true}, + { lhs: [kValue.f32.infinity.negative, 0], rhs: [-1, 0], expected: true}, + { lhs: [kValue.f32.infinity.negative, 0], rhs: [kValue.f32.negative.min, 0], expected: true}, + { lhs: [kValue.f32.infinity.negative, 0], rhs: [0, 1], expected: false}, + { lhs: [kValue.f32.infinity.negative, 0], rhs: [kValue.f32.infinity.negative, 0], expected: true}, + { lhs: [kValue.f32.infinity.negative, 0], rhs: [kValue.f32.infinity.negative, -100 ], expected: true}, + { lhs: [kValue.f32.infinity.negative, 0], rhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: false}, + + // Full infinity + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [0], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [-1, 0], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [0, 1], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [0, kValue.f32.infinity.positive], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [100, kValue.f32.infinity.positive], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [kValue.f32.infinity.negative, 0], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [kValue.f32.infinity.negative, -100 ], expected: true}, + { lhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], rhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: true}, + + // Maximum f32 boundary + { lhs: [0, kValue.f32.positive.max], rhs: [0], expected: true}, + { lhs: [0, kValue.f32.positive.max], rhs: [-1, 0], expected: false}, + { lhs: [0, kValue.f32.positive.max], rhs: [0, 1], expected: true}, + { lhs: [0, kValue.f32.positive.max], rhs: [0, kValue.f32.positive.max], expected: true}, + { lhs: [0, kValue.f32.positive.max], rhs: [0, kValue.f32.infinity.positive], expected: false}, + { lhs: [0, kValue.f32.positive.max], rhs: [100, kValue.f32.infinity.positive], expected: false}, + { lhs: [0, kValue.f32.positive.max], rhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: false}, + + // Minimum f32 boundary + { lhs: [kValue.f32.negative.min, 0], rhs: [0, 0], expected: true}, + { lhs: [kValue.f32.negative.min, 0], rhs: [-1, 0], expected: true}, + { lhs: [kValue.f32.negative.min, 0], rhs: [kValue.f32.negative.min, 0], expected: true}, + { lhs: [kValue.f32.negative.min, 0], rhs: [0, 1], expected: false}, + { lhs: [kValue.f32.negative.min, 0], rhs: [kValue.f32.infinity.negative, 0], expected: false}, + { lhs: [kValue.f32.negative.min, 0], rhs: [kValue.f32.infinity.negative, -100 ], expected: false}, + { lhs: [kValue.f32.negative.min, 0], rhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: false}, + + // Out of range high + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [0], expected: true}, + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [-1, 0], expected: false}, + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [0, 1], expected: true}, + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [0, kValue.f32.positive.max], expected: true}, + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [0, kValue.f32.infinity.positive], expected: false}, + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [100, kValue.f32.infinity.positive], expected: false}, + { lhs: [0, 2 * kValue.f32.positive.max], rhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: false}, + + // Out of range low + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [0], expected: true}, + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [-1, 0], expected: true}, + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [kValue.f32.negative.min, 0], expected: true}, + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [0, 1], expected: false}, + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [kValue.f32.infinity.negative, 0], expected: false}, + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [kValue.f32.infinity.negative, -100 ], expected: false}, + { lhs: [2 * kValue.f32.negative.min, 0], rhs: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: false}, + ] + ) + .fn(t => { + const lhs = new F32Interval(...t.params.lhs); + const rhs = new F32Interval(...t.params.rhs); + const expected = t.params.expected; + + const got = lhs.contains(rhs); + t.expect(expected === got, `${lhs}.contains(${rhs}) returned ${got}. Expected ${expected}`); + }); + +interface SpanCase { + intervals: IntervalBounds[]; + expected: IntervalBounds; +} + +g.test('span') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Single Intervals + { intervals: [[0, 10]], expected: [0, 10]}, + { intervals: [[0, kValue.f32.positive.max]], expected: [0, kValue.f32.positive.max]}, + { intervals: [[0, kValue.f32.positive.nearest_max]], expected: [0, kValue.f32.positive.nearest_max]}, + { intervals: [[0, kValue.f32.infinity.positive]], expected: [0, Number.POSITIVE_INFINITY]}, + { intervals: [[kValue.f32.negative.min, 0]], expected: [kValue.f32.negative.min, 0]}, + { intervals: [[kValue.f32.negative.nearest_min, 0]], expected: [kValue.f32.negative.nearest_min, 0]}, + { intervals: [[kValue.f32.infinity.negative, 0]], expected: [Number.NEGATIVE_INFINITY, 0]}, + + // Double Intervals + { intervals: [[0, 1], [2, 5]], expected: [0, 5]}, + { intervals: [[2, 5], [0, 1]], expected: [0, 5]}, + { intervals: [[0, 2], [1, 5]], expected: [0, 5]}, + { intervals: [[0, 5], [1, 2]], expected: [0, 5]}, + { intervals: [[kValue.f32.infinity.negative, 0], [0, kValue.f32.infinity.positive]], expected: kAny}, + + // Multiple Intervals + { intervals: [[0, 1], [2, 3], [4, 5]], expected: [0, 5]}, + { intervals: [[0, 1], [4, 5], [2, 3]], expected: [0, 5]}, + { intervals: [[0, 1], [0, 1], [0, 1]], expected: [0, 1]}, + ] + ) + .fn(t => { + const intervals = t.params.intervals.map(toF32Interval); + const expected = toF32Interval(t.params.expected); + + const got = F32Interval.span(...intervals); + t.expect( + objectEquals(got, expected), + `span({${intervals}}) returned ${got}. Expected ${expected}` + ); + }); + +interface CorrectlyRoundedCase { + value: number; + expected: IntervalBounds; +} + +g.test('correctlyRoundedInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Edge Cases + { value: kValue.f32.infinity.positive, expected: kAny }, + { value: kValue.f32.infinity.negative, expected: kAny }, + { value: kValue.f32.positive.max, expected: [kValue.f32.positive.max] }, + { value: kValue.f32.negative.min, expected: [kValue.f32.negative.min] }, + { value: kValue.f32.positive.min, expected: [kValue.f32.positive.min] }, + { value: kValue.f32.negative.max, expected: [kValue.f32.negative.max] }, + + // 32-bit subnormals + { value: kValue.f32.subnormal.positive.min, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: kValue.f32.subnormal.positive.max, expected: [0, kValue.f32.subnormal.positive.max] }, + { value: kValue.f32.subnormal.negative.min, expected: [kValue.f32.subnormal.negative.min, 0] }, + { value: kValue.f32.subnormal.negative.max, expected: [kValue.f32.subnormal.negative.max, 0] }, + + // 64-bit subnormals + { value: hexToF64(0x00000000, 0x00000001), expected: [0, kValue.f32.subnormal.positive.min] }, + { value: hexToF64(0x00000000, 0x00000002), expected: [0, kValue.f32.subnormal.positive.min] }, + { value: hexToF64(0x800fffff, 0xffffffff), expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: hexToF64(0x800fffff, 0xfffffffe), expected: [kValue.f32.subnormal.negative.max, 0] }, + + // 32-bit normals + { value: 0, expected: [0, 0] }, + { value: hexToF32(0x03800000), expected: [hexToF32(0x03800000)] }, + { value: hexToF32(0x03800001), expected: [hexToF32(0x03800001)] }, + { value: hexToF32(0x83800000), expected: [hexToF32(0x83800000)] }, + { value: hexToF32(0x83800001), expected: [hexToF32(0x83800001)] }, + + // 64-bit normals + { value: hexToF64(0x3ff00000, 0x00000001), expected: [hexToF32(0x3f800000), hexToF32(0x3f800001)] }, + { value: hexToF64(0x3ff00000, 0x00000002), expected: [hexToF32(0x3f800000), hexToF32(0x3f800001)] }, + { value: hexToF64(0x3ff00010, 0x00000010), expected: [hexToF32(0x3f800080), hexToF32(0x3f800081)] }, + { value: hexToF64(0x3ff00020, 0x00000020), expected: [hexToF32(0x3f800100), hexToF32(0x3f800101)] }, + { value: hexToF64(0xbff00000, 0x00000001), expected: [hexToF32(0xbf800001), hexToF32(0xbf800000)] }, + { value: hexToF64(0xbff00000, 0x00000002), expected: [hexToF32(0xbf800001), hexToF32(0xbf800000)] }, + { value: hexToF64(0xbff00010, 0x00000010), expected: [hexToF32(0xbf800081), hexToF32(0xbf800080)] }, + { value: hexToF64(0xbff00020, 0x00000020), expected: [hexToF32(0xbf800101), hexToF32(0xbf800100)] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = correctlyRoundedInterval(t.params.value); + t.expect( + objectEquals(expected, got), + `correctlyRoundedInterval(${t.params.value}) returned ${got}. Expected ${expected}` + ); + }); + +interface AbsoluteErrorCase { + value: number; + error: number; + expected: IntervalBounds; +} + +g.test('absoluteErrorInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Edge Cases + { value: kValue.f32.infinity.positive, error: 0, expected: kAny }, + { value: kValue.f32.infinity.positive, error: 2 ** -11, expected: kAny }, + { value: kValue.f32.infinity.positive, error: 1, expected: kAny }, + { value: kValue.f32.infinity.negative, error: 0, expected: kAny }, + { value: kValue.f32.infinity.negative, error: 2 ** -11, expected: kAny }, + { value: kValue.f32.infinity.negative, error: 1, expected: kAny }, + { value: kValue.f32.positive.max, error: 0, expected: [kValue.f32.positive.max] }, + { value: kValue.f32.positive.max, error: 2 ** -11, expected: [kValue.f32.positive.max] }, + { value: kValue.f32.positive.max, error: kValue.f32.positive.max, expected: kAny }, + { value: kValue.f32.positive.min, error: 0, expected: [kValue.f32.positive.min] }, + { value: kValue.f32.positive.min, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: kValue.f32.positive.min, error: 1, expected: [-1, 1] }, + { value: kValue.f32.negative.min, error: 0, expected: [kValue.f32.negative.min] }, + { value: kValue.f32.negative.min, error: 2 ** -11, expected: [kValue.f32.negative.min] }, + { value: kValue.f32.negative.min, error: kValue.f32.positive.max, expected: kAny }, + { value: kValue.f32.negative.max, error: 0, expected: [kValue.f32.negative.max] }, + { value: kValue.f32.negative.max, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: kValue.f32.negative.max, error: 1, expected: [-1, 1] }, + + // 32-bit subnormals + { value: kValue.f32.subnormal.positive.max, error: 0, expected: [0, kValue.f32.subnormal.positive.max] }, + { value: kValue.f32.subnormal.positive.max, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: kValue.f32.subnormal.positive.max, error: 1, expected: [-1, 1] }, + { value: kValue.f32.subnormal.positive.min, error: 0, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: kValue.f32.subnormal.positive.min, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: kValue.f32.subnormal.positive.min, error: 1, expected: [-1, 1] }, + { value: kValue.f32.subnormal.negative.min, error: 0, expected: [kValue.f32.subnormal.negative.min, 0] }, + { value: kValue.f32.subnormal.negative.min, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: kValue.f32.subnormal.negative.min, error: 1, expected: [-1, 1] }, + { value: kValue.f32.subnormal.negative.max, error: 0, expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: kValue.f32.subnormal.negative.max, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: kValue.f32.subnormal.negative.max, error: 1, expected: [-1, 1] }, + + // 64-bit subnormals + { value: hexToF64(0x00000000, 0x00000001), error: 0, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: hexToF64(0x00000000, 0x00000001), error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: hexToF64(0x00000000, 0x00000001), error: 1, expected: [-1, 1] }, + { value: hexToF64(0x00000000, 0x00000002), error: 0, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: hexToF64(0x00000000, 0x00000002), error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: hexToF64(0x00000000, 0x00000002), error: 1, expected: [-1, 1] }, + { value: hexToF64(0x800fffff, 0xffffffff), error: 0, expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: hexToF64(0x800fffff, 0xffffffff), error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: hexToF64(0x800fffff, 0xffffffff), error: 1, expected: [-1, 1] }, + { value: hexToF64(0x800fffff, 0xfffffffe), error: 0, expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: hexToF64(0x800fffff, 0xfffffffe), error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: hexToF64(0x800fffff, 0xfffffffe), error: 1, expected: [-1, 1] }, + + // Zero + { value: 0, error: 0, expected: [0] }, + { value: 0, error: 2 ** -11, expected: [-(2 ** -11), 2 ** -11] }, + { value: 0, error: 1, expected: [-1, 1] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = absoluteErrorInterval(t.params.value, t.params.error); + t.expect( + objectEquals(expected, got), + `absoluteErrorInterval(${t.params.value}, ${t.params.error}) returned ${got}. Expected ${expected}` + ); + }); + +interface ULPCase { + value: number; + num_ulp: number; + expected: IntervalBounds; +} + +g.test('ulpInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Edge Cases + { value: kValue.f32.infinity.positive, num_ulp: 0, expected: kAny }, + { value: kValue.f32.infinity.positive, num_ulp: 1, expected: kAny }, + { value: kValue.f32.infinity.positive, num_ulp: 4096, expected: kAny }, + { value: kValue.f32.infinity.negative, num_ulp: 0, expected: kAny }, + { value: kValue.f32.infinity.negative, num_ulp: 1, expected: kAny }, + { value: kValue.f32.infinity.negative, num_ulp: 4096, expected: kAny }, + { value: kValue.f32.positive.max, num_ulp: 0, expected: [kValue.f32.positive.max] }, + { value: kValue.f32.positive.max, num_ulp: 1, expected: kAny }, + { value: kValue.f32.positive.max, num_ulp: 4096, expected: kAny }, + { value: kValue.f32.positive.min, num_ulp: 0, expected: [kValue.f32.positive.min] }, + { value: kValue.f32.positive.min, num_ulp: 1, expected: [0, plusOneULP(kValue.f32.positive.min)] }, + { value: kValue.f32.positive.min, num_ulp: 4096, expected: [0, plusNULP(kValue.f32.positive.min, 4096)] }, + { value: kValue.f32.negative.min, num_ulp: 0, expected: [kValue.f32.negative.min] }, + { value: kValue.f32.negative.min, num_ulp: 1, expected: kAny }, + { value: kValue.f32.negative.min, num_ulp: 4096, expected: kAny }, + { value: kValue.f32.negative.max, num_ulp: 0, expected: [kValue.f32.negative.max] }, + { value: kValue.f32.negative.max, num_ulp: 1, expected: [minusOneULP(kValue.f32.negative.max), 0] }, + { value: kValue.f32.negative.max, num_ulp: 4096, expected: [minusNULP(kValue.f32.negative.max, 4096), 0] }, + + // 32-bit subnormals + { value: kValue.f32.subnormal.positive.max, num_ulp: 0, expected: [0, kValue.f32.subnormal.positive.max] }, + { value: kValue.f32.subnormal.positive.max, num_ulp: 1, expected: [minusOneULP(0), plusOneULP(kValue.f32.subnormal.positive.max)] }, + { value: kValue.f32.subnormal.positive.max, num_ulp: 4096, expected: [minusNULP(0, 4096), plusNULP(kValue.f32.subnormal.positive.max, 4096)] }, + { value: kValue.f32.subnormal.positive.min, num_ulp: 0, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: kValue.f32.subnormal.positive.min, num_ulp: 1, expected: [minusOneULP(0), plusOneULP(kValue.f32.subnormal.positive.min)] }, + { value: kValue.f32.subnormal.positive.min, num_ulp: 4096, expected: [minusNULP(0, 4096), plusNULP(kValue.f32.subnormal.positive.min, 4096)] }, + { value: kValue.f32.subnormal.negative.min, num_ulp: 0, expected: [kValue.f32.subnormal.negative.min, 0] }, + { value: kValue.f32.subnormal.negative.min, num_ulp: 1, expected: [minusOneULP(kValue.f32.subnormal.negative.min), plusOneULP(0)] }, + { value: kValue.f32.subnormal.negative.min, num_ulp: 4096, expected: [minusNULP(kValue.f32.subnormal.negative.min, 4096), plusNULP(0, 4096)] }, + { value: kValue.f32.subnormal.negative.max, num_ulp: 0, expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: kValue.f32.subnormal.negative.max, num_ulp: 1, expected: [minusOneULP(kValue.f32.subnormal.negative.max), plusOneULP(0)] }, + { value: kValue.f32.subnormal.negative.max, num_ulp: 4096, expected: [minusNULP(kValue.f32.subnormal.negative.max, 4096), plusNULP(0, 4096)] }, + + // 64-bit subnormals + { value: hexToF64(0x00000000, 0x00000001), num_ulp: 0, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: hexToF64(0x00000000, 0x00000001), num_ulp: 1, expected: [minusOneULP(0), plusOneULP(kValue.f32.subnormal.positive.min)] }, + { value: hexToF64(0x00000000, 0x00000001), num_ulp: 4096, expected: [minusNULP(0, 4096), plusNULP(kValue.f32.subnormal.positive.min, 4096)] }, + { value: hexToF64(0x00000000, 0x00000002), num_ulp: 0, expected: [0, kValue.f32.subnormal.positive.min] }, + { value: hexToF64(0x00000000, 0x00000002), num_ulp: 1, expected: [minusOneULP(0), plusOneULP(kValue.f32.subnormal.positive.min)] }, + { value: hexToF64(0x00000000, 0x00000002), num_ulp: 4096, expected: [minusNULP(0, 4096), plusNULP(kValue.f32.subnormal.positive.min, 4096)] }, + { value: hexToF64(0x800fffff, 0xffffffff), num_ulp: 0, expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: hexToF64(0x800fffff, 0xffffffff), num_ulp: 1, expected: [minusOneULP(kValue.f32.subnormal.negative.max), plusOneULP(0)] }, + { value: hexToF64(0x800fffff, 0xffffffff), num_ulp: 4096, expected: [minusNULP(kValue.f32.subnormal.negative.max, 4096), plusNULP(0, 4096)] }, + { value: hexToF64(0x800fffff, 0xfffffffe), num_ulp: 0, expected: [kValue.f32.subnormal.negative.max, 0] }, + { value: hexToF64(0x800fffff, 0xfffffffe), num_ulp: 1, expected: [minusOneULP(kValue.f32.subnormal.negative.max), plusOneULP(0)] }, + { value: hexToF64(0x800fffff, 0xfffffffe), num_ulp: 4096, expected: [minusNULP(kValue.f32.subnormal.negative.max, 4096), plusNULP(0, 4096)] }, + + // Zero + { value: 0, num_ulp: 0, expected: [0] }, + { value: 0, num_ulp: 1, expected: [minusOneULP(0), plusOneULP(0)] }, + { value: 0, num_ulp: 4096, expected: [minusNULP(0, 4096), plusNULP(0, 4096)] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = ulpInterval(t.params.value, t.params.num_ulp); + t.expect( + objectEquals(expected, got), + `ulpInterval(${t.params.value}, ${t.params.num_ulp}) returned ${got}. Expected ${expected}` + ); + }); + +interface PointToIntervalCase { + input: number; + expected: IntervalBounds; +} + +g.test('absInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Common usages + { input: 1, expected: [1] }, + { input: -1, expected: [1] }, + { input: 0.1, expected: [hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)] }, + { input: -0.1, expected: [hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)] }, + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.positive.min, expected: [kValue.f32.positive.min] }, + { input: kValue.f32.negative.min, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.negative.max, expected: [kValue.f32.positive.min] }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.positive.min, expected: [0, kValue.f32.subnormal.positive.min] }, + { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, + + // 64-bit subnormals + { input: hexToF64(0x00000000, 0x00000001), expected: [0, kValue.f32.subnormal.positive.min] }, + { input: hexToF64(0x800fffff, 0xffffffff), expected: [0, kValue.f32.subnormal.positive.min] }, + + // Zero + { input: 0, expected: [0]}, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = absInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `absInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('acosInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // + // The acceptance interval @ x = -1 and 1 is kAny, because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of inverseqrt + // The acceptance interval @ x = 0 is kAny, because atan2 is not well defined/implemented at 0. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: kAny }, + { input: -1/2, expected: [hexToF32(0x4005fa91), hexToF32(0x40061a94)] }, // ~2π/3 + { input: 0, expected: kAny }, + { input: 1/2, expected: [hexToF32(0x3f85fa8f), hexToF32(0x3f861a94)] }, // ~π/3 + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = acosInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `acosInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('acoshAlternativeInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: kAny }, + { input: 0, expected: kAny }, + { input: 1, expected: kAny }, // 1/0 occurs in inverseSqrt in this formulation + { input: 1.1, expected: [hexToF64(0x3fdc6368, 0x80000000), hexToF64(0x3fdc636f, 0x20000000)] }, // ~0.443..., differs from the primary in the later digits + { input: 10, expected: [hexToF64(0x4007f21e, 0x40000000), hexToF64(0x4007f21f, 0x60000000)] }, // ~2.993... + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = acoshAlternativeInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `acoshInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('acoshPrimaryInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: kAny }, + { input: 0, expected: kAny }, + { input: 1, expected: kAny }, // 1/0 occurs in inverseSqrt in this formulation + { input: 1.1, expected: [hexToF64(0x3fdc6368, 0x20000000), hexToF64(0x3fdc636f, 0x80000000)] }, // ~0.443..., differs from the alternative in the later digits + { input: 10, expected: [hexToF64(0x4007f21e, 0x40000000), hexToF64(0x4007f21f, 0x60000000)] }, // ~2.993... + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = acoshPrimaryInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `acoshInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('asinInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // + // The acceptance interval @ x = -1 and 1 is kAny, because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of inversqrt + // The acceptance interval @ x = 0 is kAny, because atan2 is not well defined/implemented at 0. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: kAny }, + { input: -1/2, expected: [hexToF32(0xbf061a96), hexToF32(0xbf05fa8e)] }, // ~-π/6 + { input: 0, expected: kAny }, + { input: 1/2, expected: [hexToF32(0x3f05fa8e), hexToF32(0x3f061a96)] }, // ~π/6 + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = asinInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `asinInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('asinhInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: [hexToF64(0xbfec343a, 0x80000000), hexToF64(0xbfec3432, 0x80000000)] }, // ~-0.88137... + { input: 0, expected: [hexToF64(0xbeaa0000, 0x20000000), hexToF64(0x3eb1ffff, 0xd0000000)] }, // ~0 + { input: 1, expected: [hexToF64(0x3fec3435, 0x40000000), hexToF64(0x3fec3437, 0x80000000)] }, // ~0.88137... + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = asinhInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `asinhInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('atanInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: hexToF32(0xbfddb3d7), expected: [kValue.f32.negative.pi.third, plusOneULP(kValue.f32.negative.pi.third)] }, // x = -√3 + { input: -1, expected: [kValue.f32.negative.pi.quarter, plusOneULP(kValue.f32.negative.pi.quarter)] }, + { input: hexToF32(0xbf13cd3a), expected: [kValue.f32.negative.pi.sixth, plusOneULP(kValue.f32.negative.pi.sixth)] }, // x = -1/√3 + { input: 0, expected: [0] }, + { input: hexToF32(0x3f13cd3a), expected: [minusOneULP(kValue.f32.positive.pi.sixth), kValue.f32.positive.pi.sixth] }, // x = 1/√3 + { input: 1, expected: [minusOneULP(kValue.f32.positive.pi.quarter), kValue.f32.positive.pi.quarter] }, + { input: hexToF32(0x3fddb3d7), expected: [minusOneULP(kValue.f32.positive.pi.third), kValue.f32.positive.pi.third] }, // x = √3 + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const error = (n: number): number => { + return 4096 * oneULP(n); + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = atanInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `atanInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('atanhInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: kAny }, + { input: -0.1, expected: [hexToF64(0xbfb9af9a, 0x60000000), hexToF64(0xbfb9af8c, 0xc0000000)] }, // ~-0.1003... + { input: 0, expected: [hexToF64(0xbe960000, 0x20000000), hexToF64(0x3e980000, 0x00000000)] }, // ~0 + { input: 0.1, expected: [hexToF64(0x3fb9af8b, 0x80000000), hexToF64(0x3fb9af9b, 0x00000000)] }, // ~0.1003... + { input: 1, expected: kAny }, + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = atanhInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `atanhInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('ceilInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: [0] }, + { input: 0.1, expected: [1] }, + { input: 0.9, expected: [1] }, + { input: 1.0, expected: [1] }, + { input: 1.1, expected: [2] }, + { input: 1.9, expected: [2] }, + { input: -0.1, expected: [0] }, + { input: -0.9, expected: [0] }, + { input: -1.0, expected: [-1] }, + { input: -1.1, expected: [-1] }, + { input: -1.9, expected: [-1] }, + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.positive.min, expected: [1] }, + { input: kValue.f32.negative.min, expected: [kValue.f32.negative.min] }, + { input: kValue.f32.negative.max, expected: [0] }, + { input: kValue.powTwo.to30, expected: [kValue.powTwo.to30] }, + { input: -kValue.powTwo.to30, expected: [-kValue.powTwo.to30] }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [0, 1] }, + { input: kValue.f32.subnormal.positive.min, expected: [0, 1] }, + { input: kValue.f32.subnormal.negative.min, expected: [0] }, + { input: kValue.f32.subnormal.negative.max, expected: [0] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = ceilInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `ceilInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('cosInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // This test does not include some common cases. i.e. f(x = π/2) = 0, because the difference between true x + // and x as a f32 is sufficiently large, such that the high slope of f @ x causes the results to be substantially + // different, so instead of getting 0 you get a value on the order of 10^-8 away from 0, thus difficult to express + // in a human readable manner. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: kValue.f32.negative.pi.whole, expected: [-1, plusOneULP(-1)] }, + { input: kValue.f32.negative.pi.third, expected: [minusOneULP(1/2), 1/2] }, + { input: 0, expected: [1, 1] }, + { input: kValue.f32.positive.pi.third, expected: [minusOneULP(1/2), 1/2] }, + { input: kValue.f32.positive.pi.whole, expected: [-1, plusOneULP(-1)] }, + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const error = (_: number): number => { + return 2 ** -11; + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = cosInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `cosInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('coshInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: [ hexToF32(0x3fc583a4), hexToF32(0x3fc583b1)] }, // ~1.1543... + { input: 0, expected: [hexToF32(0x3f7ffffd), hexToF32(0x3f800002)] }, // ~1 + { input: 1, expected: [ hexToF32(0x3fc583a4), hexToF32(0x3fc583b1)] }, // ~1.1543... + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = coshInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `coshInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('degreesInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: kValue.f32.negative.pi.whole, expected: [minusOneULP(-180), plusOneULP(-180)] }, + { input: kValue.f32.negative.pi.three_quarters, expected: [minusOneULP(-135), plusOneULP(-135)] }, + { input: kValue.f32.negative.pi.half, expected: [minusOneULP(-90), plusOneULP(-90)] }, + { input: kValue.f32.negative.pi.third, expected: [minusOneULP(-60), plusOneULP(-60)] }, + { input: kValue.f32.negative.pi.quarter, expected: [minusOneULP(-45), plusOneULP(-45)] }, + { input: kValue.f32.negative.pi.sixth, expected: [minusOneULP(-30), plusOneULP(-30)] }, + { input: 0, expected: [0] }, + { input: kValue.f32.positive.pi.sixth, expected: [minusOneULP(30), plusOneULP(30)] }, + { input: kValue.f32.positive.pi.quarter, expected: [minusOneULP(45), plusOneULP(45)] }, + { input: kValue.f32.positive.pi.third, expected: [minusOneULP(60), plusOneULP(60)] }, + { input: kValue.f32.positive.pi.half, expected: [minusOneULP(90), plusOneULP(90)] }, + { input: kValue.f32.positive.pi.three_quarters, expected: [minusOneULP(135), plusOneULP(135)] }, + { input: kValue.f32.positive.pi.whole, expected: [minusOneULP(180), plusOneULP(180)] }, + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = degreesInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `degreesInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('expInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: 0, expected: [1] }, + { input: 1, expected: [kValue.f32.positive.e, plusOneULP(kValue.f32.positive.e)] }, + { input: 89, expected: kAny }, + ] + ) + .fn(t => { + const error = (x: number): number => { + const n = 3 + 2 * Math.abs(t.params.input); + return n * oneULP(x); + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = expInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `expInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('exp2Interval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: 0, expected: [1] }, + { input: 1, expected: [2] }, + { input: 128, expected: kAny }, + ] + ) + .fn(t => { + const error = (x: number): number => { + const n = 3 + 2 * Math.abs(t.params.input); + return n * oneULP(x); + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = exp2Interval(t.params.input); + t.expect( + objectEquals(expected, got), + `exp2Interval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('floorInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: [0] }, + { input: 0.1, expected: [0] }, + { input: 0.9, expected: [0] }, + { input: 1.0, expected: [1] }, + { input: 1.1, expected: [1] }, + { input: 1.9, expected: [1] }, + { input: -0.1, expected: [-1] }, + { input: -0.9, expected: [-1] }, + { input: -1.0, expected: [-1] }, + { input: -1.1, expected: [-2] }, + { input: -1.9, expected: [-2] }, + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.positive.min, expected: [0] }, + { input: kValue.f32.negative.min, expected: [kValue.f32.negative.min] }, + { input: kValue.f32.negative.max, expected: [-1] }, + { input: kValue.powTwo.to30, expected: [kValue.powTwo.to30] }, + { input: -kValue.powTwo.to30, expected: [-kValue.powTwo.to30] }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [0] }, + { input: kValue.f32.subnormal.positive.min, expected: [0] }, + { input: kValue.f32.subnormal.negative.min, expected: [-1, 0] }, + { input: kValue.f32.subnormal.negative.max, expected: [-1, 0] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = floorInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `floorInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('fractInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: [0] }, + { input: 0.1, expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: 0.9, expected: [hexToF32(0x3f666666), plusOneULP(hexToF32(0x3f666666))] }, // ~0.9 + { input: 1.0, expected: [0] }, + { input: 1.1, expected: [hexToF64(0x3fb99998, 0x00000000), hexToF64(0x3fb9999a, 0x00000000)] }, // ~0.1 + { input: -0.1, expected: [hexToF32(0x3f666666), plusOneULP(hexToF32(0x3f666666))] }, // ~0.9 + { input: -0.9, expected: [hexToF64(0x3fb99999, 0x00000000), hexToF64(0x3fb9999a, 0x00000000)] }, // ~0.1 + { input: -1.0, expected: [0] }, + { input: -1.1, expected: [hexToF64(0x3feccccc, 0xc0000000), hexToF64(0x3feccccd, 0x00000000), ] }, // ~0.9 + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [0] }, + { input: kValue.f32.positive.min, expected: [kValue.f32.positive.min, kValue.f32.positive.min] }, + { input: kValue.f32.negative.min, expected: [0] }, + { input: kValue.f32.negative.max, expected: [kValue.f32.positive.less_than_one, 1.0] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = fractInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `fractInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('inverseSqrtInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: -1, expected: kAny }, + { input: 0, expected: kAny }, + { input: 0.04, expected: [minusOneULP(5), plusOneULP(5)] }, + { input: 1, expected: [1] }, + { input: 100, expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: kValue.f32.positive.max, expected: [hexToF32(0x1f800000), plusNULP(hexToF32(0x1f800000), 2)] }, // ~5.421...e-20, i.e. 1/√max f32 + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const error = (n: number): number => { + return 2 * oneULP(n); + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = inverseSqrtInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `inverseSqrtInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('lengthIntervalScalar') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // + // length(0) = kAny, because length uses sqrt, which is defined as 1/inversesqrt + {input: 0, expected: kAny }, + {input: 1.0, expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: -1.0, expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: 0.1, expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + {input: -0.1, expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + {input: 10.0, expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + {input: -10.0, expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + + // Subnormal Cases + { input: kValue.f32.subnormal.negative.min, expected: kAny }, + { input: kValue.f32.subnormal.negative.max, expected: kAny }, + { input: kValue.f32.subnormal.positive.min, expected: kAny }, + { input: kValue.f32.subnormal.positive.max, expected: kAny }, + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: kValue.f32.negative.max, expected: kAny }, + { input: kValue.f32.positive.min, expected: kAny }, + { input: kValue.f32.positive.max, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = lengthInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `lengthInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('logInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: -1, expected: kAny }, + { input: 0, expected: kAny }, + { input: 1, expected: [0] }, + { input: kValue.f32.positive.e, expected: [minusOneULP(1), 1] }, + { input: kValue.f32.positive.max, expected: [minusOneULP(hexToF32(0x42b17218)), hexToF32(0x42b17218)] }, // ~88.72... + ] + ) + .fn(t => { + const error = (n: number): number => { + if (t.params.input >= 0.5 && t.params.input <= 2.0) { + return 2 ** -21; + } + return 3 * oneULP(n); + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = logInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `logInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('log2Interval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: -1, expected: kAny }, + { input: 0, expected: kAny }, + { input: 1, expected: [0] }, + { input: 2, expected: [1] }, + { input: kValue.f32.positive.max, expected: [minusOneULP(128), 128] }, + ] + ) + .fn(t => { + const error = (n: number): number => { + if (t.params.input >= 0.5 && t.params.input <= 2.0) { + return 2 ** -21; + } + return 3 * oneULP(n); + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = log2Interval(t.params.input); + t.expect( + objectEquals(expected, got), + `log2Interval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('negationInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: [0] }, + { input: 0.1, expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: 1.0, expected: [-1.0] }, + { input: 1.9, expected: [hexToF32(0xbff33334), plusOneULP(hexToF32(0xbff33334))] }, // ~-1.9 + { input: -0.1, expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: -1.0, expected: [1] }, + { input: -1.9, expected: [minusOneULP(hexToF32(0x3ff33334)), hexToF32(0x3ff33334)] }, // ~1.9 + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [kValue.f32.negative.min] }, + { input: kValue.f32.positive.min, expected: [kValue.f32.negative.max] }, + { input: kValue.f32.negative.min, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.negative.max, expected: [kValue.f32.positive.min] }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: kValue.f32.subnormal.positive.min, expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: kValue.f32.subnormal.negative.min, expected: [0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.negative.max, expected: [0, kValue.f32.subnormal.positive.min] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = negationInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `negationInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('quantizeToF16Interval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: kValue.f16.negative.min, expected: [kValue.f16.negative.min] }, + { input: -1, expected: [-1] }, + { input: -0.1, expected: [hexToF32(0xbdcce000), hexToF32(0xbdccc000)] }, // ~-0.1 + { input: kValue.f16.negative.max, expected: [kValue.f16.negative.max] }, + { input: kValue.f16.subnormal.negative.min, expected: [kValue.f16.subnormal.negative.min, 0] }, + { input: kValue.f16.subnormal.negative.max, expected: [kValue.f16.subnormal.negative.max, 0] }, + { input: kValue.f32.subnormal.negative.max, expected: [kValue.f16.subnormal.negative.max, 0] }, + { input: 0, expected: [0] }, + { input: kValue.f32.subnormal.positive.min, expected: [0, kValue.f16.subnormal.positive.min] }, + { input: kValue.f16.subnormal.positive.min, expected: [0, kValue.f16.subnormal.positive.min] }, + { input: kValue.f16.subnormal.positive.max, expected: [0, kValue.f16.subnormal.positive.max] }, + { input: kValue.f16.positive.min, expected: [kValue.f16.positive.min] }, + { input: 0.1, expected: [hexToF32(0x3dccc000), hexToF32(0x3dcce000)] }, // ~0.1 + { input: 1, expected: [1] }, + { input: kValue.f16.positive.max, expected: [kValue.f16.positive.max] }, + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = quantizeToF16Interval(t.params.input); + t.expect( + objectEquals(expected, got), + `quantizeToF16Interval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('radiansInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: -180, expected: [minusOneULP(kValue.f32.negative.pi.whole), plusOneULP(kValue.f32.negative.pi.whole)] }, + { input: -135, expected: [minusOneULP(kValue.f32.negative.pi.three_quarters), plusOneULP(kValue.f32.negative.pi.three_quarters)] }, + { input: -90, expected: [minusOneULP(kValue.f32.negative.pi.half), plusOneULP(kValue.f32.negative.pi.half)] }, + { input: -60, expected: [minusOneULP(kValue.f32.negative.pi.third), plusOneULP(kValue.f32.negative.pi.third)] }, + { input: -45, expected: [minusOneULP(kValue.f32.negative.pi.quarter), plusOneULP(kValue.f32.negative.pi.quarter)] }, + { input: -30, expected: [minusOneULP(kValue.f32.negative.pi.sixth), plusOneULP(kValue.f32.negative.pi.sixth)] }, + { input: 0, expected: [0] }, + { input: 30, expected: [minusOneULP(kValue.f32.positive.pi.sixth), plusOneULP(kValue.f32.positive.pi.sixth)] }, + { input: 45, expected: [minusOneULP(kValue.f32.positive.pi.quarter), plusOneULP(kValue.f32.positive.pi.quarter)] }, + { input: 60, expected: [minusOneULP(kValue.f32.positive.pi.third), plusOneULP(kValue.f32.positive.pi.third)] }, + { input: 90, expected: [minusOneULP(kValue.f32.positive.pi.half), plusOneULP(kValue.f32.positive.pi.half)] }, + { input: 135, expected: [minusOneULP(kValue.f32.positive.pi.three_quarters), plusOneULP(kValue.f32.positive.pi.three_quarters)] }, + { input: 180, expected: [minusOneULP(kValue.f32.positive.pi.whole), plusOneULP(kValue.f32.positive.pi.whole)] }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = radiansInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `radiansInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('roundInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: [0] }, + { input: 0.1, expected: [0] }, + { input: 0.5, expected: [0] }, // Testing tie breaking + { input: 0.9, expected: [1] }, + { input: 1.0, expected: [1] }, + { input: 1.1, expected: [1] }, + { input: 1.5, expected: [2] }, // Testing tie breaking + { input: 1.9, expected: [2] }, + { input: -0.1, expected: [0] }, + { input: -0.5, expected: [0] }, // Testing tie breaking + { input: -0.9, expected: [-1] }, + { input: -1.0, expected: [-1] }, + { input: -1.1, expected: [-1] }, + { input: -1.5, expected: [-2] }, // Testing tie breaking + { input: -1.9, expected: [-2] }, + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.positive.min, expected: [0] }, + { input: kValue.f32.negative.min, expected: [kValue.f32.negative.min] }, + { input: kValue.f32.negative.max, expected: [0] }, + { input: kValue.powTwo.to30, expected: [kValue.powTwo.to30] }, + { input: -kValue.powTwo.to30, expected: [-kValue.powTwo.to30] }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [0] }, + { input: kValue.f32.subnormal.positive.min, expected: [0] }, + { input: kValue.f32.subnormal.negative.min, expected: [0] }, + { input: kValue.f32.subnormal.negative.max, expected: [0] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = roundInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `roundInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('saturateInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Normals + { input: 0, expected: [0] }, + { input: 1, expected: [1.0] }, + { input: -0.1, expected: [0] }, + { input: -1, expected: [0] }, + { input: -10, expected: [0] }, + { input: 0.1, expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: 10, expected: [1.0] }, + { input: 11.1, expected: [1.0] }, + { input: kValue.f32.positive.max, expected: [1.0] }, + { input: kValue.f32.positive.min, expected: [kValue.f32.positive.min] }, + { input: kValue.f32.negative.max, expected: [0.0] }, + { input: kValue.f32.negative.min, expected: [0.0] }, + + // Subnormals + { input: kValue.f32.subnormal.positive.max, expected: [0.0, kValue.f32.subnormal.positive.max] }, + { input: kValue.f32.subnormal.positive.min, expected: [0.0, kValue.f32.subnormal.positive.min] }, + { input: kValue.f32.subnormal.negative.min, expected: [0.0] }, + { input: kValue.f32.subnormal.negative.max, expected: [0.0] }, + + // Infinities + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = saturateInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `saturationInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('signInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: [-1] }, + { input: -10, expected: [-1] }, + { input: -1, expected: [-1] }, + { input: -0.1, expected: [-1] }, + { input: kValue.f32.negative.max, expected: [-1] }, + { input: kValue.f32.subnormal.negative.min, expected: [-1, 0] }, + { input: kValue.f32.subnormal.negative.max, expected: [-1, 0] }, + { input: 0, expected: [0] }, + { input: kValue.f32.subnormal.positive.max, expected: [0, 1] }, + { input: kValue.f32.subnormal.positive.min, expected: [0, 1] }, + { input: kValue.f32.positive.min, expected: [1] }, + { input: 0.1, expected: [1] }, + { input: 1, expected: [1] }, + { input: 10, expected: [1] }, + { input: kValue.f32.positive.max, expected: [1] }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = signInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `signInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('sinInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // This test does not include some common cases, i.e. f(x = -π|π) = 0, because the difference between true x and x + // as a f32 is sufficiently large, such that the high slope of f @ x causes the results to be substantially + // different, so instead of getting 0 you get a value on the order of 10^-8 away from it, thus difficult to + // express in a human readable manner. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: kValue.f32.negative.pi.half, expected: [-1, plusOneULP(-1)] }, + { input: 0, expected: [0] }, + { input: kValue.f32.positive.pi.half, expected: [minusOneULP(1), 1] }, + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const error = (_: number): number => { + return 2 ** -11; + }; + + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = sinInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `sinInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('sinhInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: [ hexToF32(0xbf966d05), hexToF32(0xbf966cf8)] }, // ~-1.175... + { input: 0, expected: [hexToF32(0xb4600000), hexToF32(0x34600000)] }, // ~0 + { input: 1, expected: [ hexToF32(0x3f966cf8), hexToF32(0x3f966d05)] }, // ~1.175... + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = sinhInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `sinhInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('sqrtInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: -1, expected: kAny }, + { input: 0, expected: kAny }, + { input: 0.01, expected: [hexToF64(0x3fb99998, 0xb0000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: 1, expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: 4, expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + { input: 100, expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = sqrtInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `sqrtInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('tanInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // All of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form. Some easy looking cases like f(x = -π|π) = 0 are actually quite difficult. This is because the interval + // is calculated from the results of sin(x)/cos(x), which becomes very messy at x = -π|π, since π is irrational, + // thus does not have an exact representation as a f32. + // Even at 0, which has a precise f32 value, there is still the problem that result of sin(0) and cos(0) will be + // intervals due to the inherited nature of errors, so the proper interval will be an interval calculated from + // dividing an interval by another interval and applying an error function to that. This complexity is why the + // entire interval framework was developed. + // The examples here have been manually traced to confirm the expectation values are correct. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: kValue.f32.negative.pi.whole, expected: [hexToF64(0xbf4002bc, 0x90000000), hexToF64(0x3f400144, 0xf0000000)] }, // ~0.0 + { input: kValue.f32.negative.pi.half, expected: kAny }, + { input: 0, expected: [hexToF64(0xbf400200, 0xb0000000), hexToF64(0x3f400200, 0xb0000000)] }, // ~0.0 + { input: kValue.f32.positive.pi.half, expected: kAny }, + { input: kValue.f32.positive.pi.whole, expected: [hexToF64(0xbf400144, 0xf0000000), hexToF64(0x3f4002bc, 0x90000000)] }, // ~0.0 + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = tanInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `tanInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('tanhInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.negative.min, expected: kAny }, + { input: -1, expected: [hexToF64(0xbfe85efd, 0x10000000), hexToF64(0xbfe85ef8, 0x90000000)] }, // ~-0.7615... + { input: 0, expected: [hexToF64(0xbe8c0000, 0xb0000000), hexToF64(0x3e8c0000, 0xb0000000)] }, // ~0 + { input: 1, expected: [hexToF64(0x3fe85ef8, 0x90000000), hexToF64(0x3fe85efd, 0x10000000)] }, // ~0.7615... + { input: kValue.f32.positive.max, expected: kAny }, + { input: kValue.f32.infinity.positive, expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = tanhInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `tanhInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('truncInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + { input: 0, expected: [0] }, + { input: 0.1, expected: [0] }, + { input: 0.9, expected: [0] }, + { input: 1.0, expected: [1] }, + { input: 1.1, expected: [1] }, + { input: 1.9, expected: [1] }, + { input: -0.1, expected: [0] }, + { input: -0.9, expected: [0] }, + { input: -1.0, expected: [-1] }, + { input: -1.1, expected: [-1] }, + { input: -1.9, expected: [-1] }, + + // Edge cases + { input: kValue.f32.infinity.positive, expected: kAny }, + { input: kValue.f32.infinity.negative, expected: kAny }, + { input: kValue.f32.positive.max, expected: [kValue.f32.positive.max] }, + { input: kValue.f32.positive.min, expected: [0] }, + { input: kValue.f32.negative.min, expected: [kValue.f32.negative.min] }, + { input: kValue.f32.negative.max, expected: [0] }, + + // 32-bit subnormals + { input: kValue.f32.subnormal.positive.max, expected: [0] }, + { input: kValue.f32.subnormal.positive.min, expected: [0] }, + { input: kValue.f32.subnormal.negative.min, expected: [0] }, + { input: kValue.f32.subnormal.negative.max, expected: [0] }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = truncInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `truncInterval(${t.params.input}) returned ${got}. Expected ${expected}` + ); + }); + +interface BinaryToIntervalCase { + // input is a pair of independent values, not an range, so should not be + // converted to a F32Interval. + input: [number, number]; + expected: IntervalBounds; +} + +g.test('additionInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [0] }, + { input: [1, 0], expected: [1] }, + { input: [0, 1], expected: [1] }, + { input: [-1, 0], expected: [-1] }, + { input: [0, -1], expected: [-1] }, + { input: [1, 1], expected: [2] }, + { input: [1, -1], expected: [0] }, + { input: [-1, 1], expected: [0] }, + { input: [-1, -1], expected: [-2] }, + + // 64-bit normals + { input: [0.1, 0], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [0, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [-0.1, 0], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [0, -0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [0.1, 0.1], expected: [minusOneULP(hexToF32(0x3e4ccccd)), hexToF32(0x3e4ccccd)] }, // ~0.2 + { input: [0.1, -0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)) - hexToF32(0x3dcccccd), hexToF32(0x3dcccccd) - minusOneULP(hexToF32(0x3dcccccd))] }, // ~0 + { input: [-0.1, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)) - hexToF32(0x3dcccccd), hexToF32(0x3dcccccd) - minusOneULP(hexToF32(0x3dcccccd))] }, // ~0 + { input: [-0.1, -0.1], expected: [hexToF32(0xbe4ccccd), plusOneULP(hexToF32(0xbe4ccccd))] }, // ~-0.2 + + // 32-bit subnormals + { input: [kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.min, 0], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, kValue.f32.subnormal.positive.min], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [kValue.f32.subnormal.negative.max, 0], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [0, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [kValue.f32.subnormal.negative.min, 0], expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: [0, kValue.f32.subnormal.negative.min], expected: [kValue.f32.subnormal.negative.min, 0] }, + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0], expected: kAny}, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, 0], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = additionInterval(x, y); + t.expect( + objectEquals(expected, got), + `additionInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +// Note: atan2's parameters are labelled (y, x) instead of (x, y) +g.test('atan2Interval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + + // The positive x & y quadrant is tested in more detail, and the other quadrants are spot checked that values are + // pointing in the right direction. + + // Some of the intervals appear slightly asymmetric, i.e. [π/4 - 4097 * ULP(π/4), π/4 + 4096 * ULP(π/4)], this is + // because π/4 is not precisely expressible as a f32, so the higher precision value can be rounded up or down when + // converting to f32. Thus one option will be 1 ULP off of the constant value being used. + + // positive y, positive x + { input: [1, hexToF32(0x3fddb3d7)], expected: [minusNULP(kValue.f32.positive.pi.sixth, 4097), plusNULP(kValue.f32.positive.pi.sixth, 4096)] }, // x = √3 + { input: [1, 1], expected: [minusNULP(kValue.f32.positive.pi.quarter, 4097), plusNULP(kValue.f32.positive.pi.quarter, 4096)] }, + // { input: [hexToF32(0x3fddb3d7), 1], expected: [hexToF64(0x3ff0bf52, 0x00000000), hexToF64(0x3ff0c352, 0x60000000)] }, // y = √3 + { input: [Number.POSITIVE_INFINITY, 1], expected: kAny }, + + // positive y, negative x + { input: [1, -1], expected: [minusNULP(kValue.f32.positive.pi.three_quarters, 4096), plusNULP(kValue.f32.positive.pi.three_quarters, 4097)] }, + { input: [Number.POSITIVE_INFINITY, -1], expected: kAny }, + + // negative y, negative x + { input: [-1, -1], expected: [minusNULP(kValue.f32.negative.pi.three_quarters, 4097), plusNULP(kValue.f32.negative.pi.three_quarters, 4096)] }, + { input: [Number.NEGATIVE_INFINITY, -1], expected: kAny }, + + // negative y, positive x + { input: [-1, 1], expected: [minusNULP(kValue.f32.negative.pi.quarter, 4096), plusNULP(kValue.f32.negative.pi.quarter, 4097)] }, + { input: [Number.NEGATIVE_INFINITY, 1], expected: kAny }, + + // Discontinuity @ origin (0,0) + { input: [0, 0], expected: kAny }, + { input: [0, kValue.f32.subnormal.positive.max], expected: kAny }, + { input: [0, kValue.f32.subnormal.negative.min], expected: kAny }, + { input: [0, kValue.f32.positive.min], expected: kAny }, + { input: [0, kValue.f32.negative.max], expected: kAny }, + { input: [0, kValue.f32.positive.max], expected: kAny }, + { input: [0, kValue.f32.negative.min], expected: kAny }, + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [0, 1], expected: kAny }, + { input: [kValue.f32.subnormal.positive.max, 1], expected: kAny }, + { input: [kValue.f32.subnormal.negative.min, 1], expected: kAny }, + + // When atan(y/x) ~ 0, test that ULP applied to result of atan2, not the intermediate atan(y/x) value + {input: [hexToF32(0x80800000), hexToF32(0xbf800000)], expected: [minusNULP(kValue.f32.negative.pi.whole, 4096), plusNULP(kValue.f32.negative.pi.whole, 4096)] }, + {input: [hexToF32(0x00800000), hexToF32(0xbf800000)], expected: [minusNULP(kValue.f32.positive.pi.whole, 4096), plusNULP(kValue.f32.positive.pi.whole, 4096)] }, + + // Very large |x| values should cause kAny to be returned, due to the restrictions on division + { input: [1, kValue.f32.positive.max], expected: kAny }, + { input: [1, kValue.f32.positive.nearest_max], expected: kAny }, + { input: [1, kValue.f32.negative.min], expected: kAny }, + { input: [1, kValue.f32.negative.nearest_min], expected: kAny }, + ] + ) + .fn(t => { + const [y, x] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = atan2Interval(y, x); + t.expect( + objectEquals(expected, got), + `atan2Interval(${y}, ${x}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('distanceIntervalScalar') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult + // to express in a closed human readable form due to the inherited nature + // of the errors. + // + // distance(x, y), where x - y = 0 has an acceptance interval of kAny, + // because distance(x, y) = length(x - y), and length(0) = kAny + { input: [0, 0], expected: kAny }, + { input: [1.0, 0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [0.0, 1.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [1.0, 1.0], expected: kAny }, + { input: [-0.0, -1.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [0.0, -1.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [-1.0, -1.0], expected: kAny }, + { input: [0.1, 0], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: [0, 0.1], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: [-0.1, 0], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: [0, -0.1], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: [10.0, 0], expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + { input: [0, 10.0], expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + { input: [-10.0, 0], expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + { input: [0, -10.0], expected: [hexToF64(0x4023ffff, 0x70000000), hexToF64(0x40240000, 0xb0000000)] }, // ~10 + + // Subnormal Cases + { input: [kValue.f32.subnormal.negative.min, 0], expected: kAny }, + { input: [kValue.f32.subnormal.negative.max, 0], expected: kAny }, + { input: [kValue.f32.subnormal.positive.min, 0], expected: kAny }, + { input: [kValue.f32.subnormal.positive.max, 0], expected: kAny }, + + // Edge cases + { input: [kValue.f32.infinity.positive, 0], expected: kAny }, + { input: [kValue.f32.infinity.negative, 0], expected: kAny }, + { input: [kValue.f32.negative.min, 0], expected: kAny }, + { input: [kValue.f32.negative.max, 0], expected: kAny }, + { input: [kValue.f32.positive.min, 0], expected: kAny }, + { input: [kValue.f32.positive.max, 0], expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = distanceInterval(...t.params.input); + t.expect( + objectEquals(expected, got), + `distanceInterval(${t.params.input[0]}, ${t.params.input[1]}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('divisionInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 1], expected: [0] }, + { input: [0, -1], expected: [0] }, + { input: [1, 1], expected: [1] }, + { input: [1, -1], expected: [-1] }, + { input: [-1, 1], expected: [-1] }, + { input: [-1, -1], expected: [1] }, + { input: [4, 2], expected: [2] }, + { input: [-4, 2], expected: [-2] }, + { input: [4, -2], expected: [-2] }, + { input: [-4, -2], expected: [2] }, + + // 64-bit normals + { input: [0, 0.1], expected: [0] }, + { input: [0, -0.1], expected: [0] }, + { input: [1, 0.1], expected: [minusOneULP(10), plusOneULP(10)] }, + { input: [-1, 0.1], expected: [minusOneULP(-10), plusOneULP(-10)] }, + { input: [1, -0.1], expected: [minusOneULP(-10), plusOneULP(-10)] }, + { input: [-1, -0.1], expected: [minusOneULP(10), plusOneULP(10)] }, + + // Denominator out of range + { input: [1, kValue.f32.infinity.positive], expected: kAny }, + { input: [1, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + { input: [1, kValue.f32.positive.max], expected: kAny }, + { input: [1, kValue.f32.negative.min], expected: kAny }, + { input: [1, 0], expected: kAny }, + { input: [1, kValue.f32.subnormal.positive.max], expected: kAny }, + ] + ) + .fn(t => { + const error = (n: number): number => { + return 2.5 * oneULP(n); + }; + + const [x, y] = t.params.input; + t.params.expected = applyError(t.params.expected, error); + const expected = toF32Interval(t.params.expected); + + const got = divisionInterval(x, y); + t.expect( + objectEquals(expected, got), + `divisionInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('ldexpInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [0] }, + { input: [0, 1], expected: [0] }, + { input: [0, -1], expected: [0] }, + { input: [1, 1], expected: [2] }, + { input: [1, -1], expected: [0.5] }, + { input: [-1, 1], expected: [-2] }, + { input: [-1, -1], expected: [-0.5] }, + + // 64-bit normals + { input: [0, 0.1], expected: [0] }, + { input: [0, -0.1], expected: [0] }, + { input: [1.0000000001, 1], expected: [2, plusNULP(2, 2)] }, // ~2, additional ULP error due to first param not being f32 precise + { input: [-1.0000000001, 1], expected: [minusNULP(-2, 2), -2] }, // ~-2, additional ULP error due to first param not being f32 precise + + // Edge Cases + { input: [1.9999998807907104, 127], expected: [kValue.f32.positive.max] }, + { input: [1, -126], expected: [kValue.f32.positive.min] }, + { input: [0.9999998807907104, -126], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [1.1920928955078125e-07, -126], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [-1.1920928955078125e-07, -126], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [-0.9999998807907104, -126], expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: [-1, -126], expected: [kValue.f32.negative.max] }, + { input: [-1.9999998807907104, 127], expected: [kValue.f32.negative.min] }, + + // Out of Bounds + { input: [1, 128], expected: kAny }, + { input: [-1, 128], expected: kAny }, + { input: [100, 126], expected: kAny }, + { input: [-100, 126], expected: kAny }, + { input: [kValue.f32.positive.max, kValue.i32.positive.max], expected: kAny }, + { input: [kValue.f32.negative.min, kValue.i32.positive.max], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = ldexpInterval(x, y); + t.expect( + objectEquals(expected, got), + `divisionInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('maxInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [0] }, + { input: [1, 0], expected: [1] }, + { input: [0, 1], expected: [1] }, + { input: [-1, 0], expected: [0] }, + { input: [0, -1], expected: [0] }, + { input: [1, 1], expected: [1] }, + { input: [1, -1], expected: [1] }, + { input: [-1, 1], expected: [1] }, + { input: [-1, -1], expected: [-1] }, + + // 64-bit normals + { input: [0.1, 0], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [0, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [-0.1, 0], expected: [0] }, + { input: [0, -0.1], expected: [0] }, + { input: [0.1, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [0.1, -0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [-0.1, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [-0.1, -0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + + // 32-bit normals + { input: [kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.min, 0], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, kValue.f32.subnormal.positive.min], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [kValue.f32.subnormal.negative.max, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.negative.max], expected: [0] }, + { input: [kValue.f32.subnormal.negative.min, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.negative.min], expected: [0] }, + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, 0], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = maxInterval(x, y); + t.expect( + objectEquals(expected, got), + `maxInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('minInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [0] }, + { input: [1, 0], expected: [0] }, + { input: [0, 1], expected: [0] }, + { input: [-1, 0], expected: [-1] }, + { input: [0, -1], expected: [-1] }, + { input: [1, 1], expected: [1] }, + { input: [1, -1], expected: [-1] }, + { input: [-1, 1], expected: [-1] }, + { input: [-1, -1], expected: [-1] }, + + // 64-bit normals + { input: [0.1, 0], expected: [0] }, + { input: [0, 0.1], expected: [0] }, + { input: [-0.1, 0], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [0, -0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [0.1, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [0.1, -0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [-0.1, 0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [-0.1, -0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + + // 32-bit normals + { input: [kValue.f32.subnormal.positive.max, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.positive.max], expected: [0] }, + { input: [kValue.f32.subnormal.positive.min, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.positive.min], expected: [0] }, + { input: [kValue.f32.subnormal.negative.max, 0], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [0, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [kValue.f32.subnormal.negative.min, 0], expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: [0, kValue.f32.subnormal.negative.min], expected: [kValue.f32.subnormal.negative.min, 0] }, + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, 0], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = minInterval(x, y); + t.expect( + objectEquals(expected, got), + `minInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('multiplicationInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [0] }, + { input: [1, 0], expected: [0] }, + { input: [0, 1], expected: [0] }, + { input: [-1, 0], expected: [0] }, + { input: [0, -1], expected: [0] }, + { input: [1, 1], expected: [1] }, + { input: [1, -1], expected: [-1] }, + { input: [-1, 1], expected: [-1] }, + { input: [-1, -1], expected: [1] }, + { input: [2, 1], expected: [2] }, + { input: [1, -2], expected: [-2] }, + { input: [-2, 1], expected: [-2] }, + { input: [-2, -1], expected: [2] }, + { input: [2, 2], expected: [4] }, + { input: [2, -2], expected: [-4] }, + { input: [-2, 2], expected: [-4] }, + { input: [-2, -2], expected: [4] }, + + // 64-bit normals + { input: [0.1, 0], expected: [0] }, + { input: [0, 0.1], expected: [0] }, + { input: [-0.1, 0], expected: [0] }, + { input: [0, -0.1], expected: [0] }, + { input: [0.1, 0.1], expected: [minusNULP(hexToF32(0x3c23d70a), 2), plusOneULP(hexToF32(0x3c23d70a))] }, // ~0.01 + { input: [0.1, -0.1], expected: [minusOneULP(hexToF32(0xbc23d70a)), plusNULP(hexToF32(0xbc23d70a), 2)] }, // ~-0.01 + { input: [-0.1, 0.1], expected: [minusOneULP(hexToF32(0xbc23d70a)), plusNULP(hexToF32(0xbc23d70a), 2)] }, // ~-0.01 + { input: [-0.1, -0.1], expected: [minusNULP(hexToF32(0x3c23d70a), 2), plusOneULP(hexToF32(0x3c23d70a))] }, // ~0.01 + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [1, kValue.f32.infinity.positive], expected: kAny }, + { input: [-1, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [1, kValue.f32.infinity.negative], expected: kAny }, + { input: [-1, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + + // Edge of f32 + { input: [kValue.f32.positive.max, kValue.f32.positive.max], expected: kAny }, + { input: [kValue.f32.negative.min, kValue.f32.negative.min], expected: kAny }, + { input: [kValue.f32.positive.max, kValue.f32.negative.min], expected: kAny }, + { input: [kValue.f32.negative.min, kValue.f32.positive.max], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = multiplicationInterval(x, y); + t.expect( + objectEquals(expected, got), + `multiplicationInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('remainderInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 1], expected: [0, 0] }, + { input: [0, -1], expected: [0, 0] }, + { input: [1, 1], expected: [0, 1] }, + { input: [1, -1], expected: [0, 1] }, + { input: [-1, 1], expected: [-1, 0] }, + { input: [-1, -1], expected: [-1, 0] }, + { input: [4, 2], expected: [0, 2] }, + { input: [-4, 2], expected: [-2, 0] }, + { input: [4, -2], expected: [0, 2] }, + { input: [-4, -2], expected: [-2, 0] }, + { input: [2, 4], expected: [2, 2] }, + { input: [-2, 4], expected: [-2, -2] }, + { input: [2, -4], expected: [2, 2] }, + { input: [-2, -4], expected: [-2, -2] }, + + // 64-bit normals + { input: [0, 0.1], expected: [0, 0] }, + { input: [0, -0.1], expected: [0, 0] }, + { input: [1, 0.1], expected: [hexToF32(0xb4000000), hexToF32(0x3dccccd8)] }, // ~[0, 0.1] + { input: [-1, 0.1], expected: [hexToF32(0xbdccccd8), hexToF32(0x34000000)] }, // ~[-0.1, 0] + { input: [1, -0.1], expected: [hexToF32(0xb4000000), hexToF32(0x3dccccd8)] }, // ~[0, 0.1] + { input: [-1, -0.1], expected: [hexToF32(0xbdccccd8), hexToF32(0x34000000)] }, // ~[-0.1, 0] + + // Denominator out of range + { input: [1, kValue.f32.infinity.positive], expected: kAny }, + { input: [1, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + { input: [1, kValue.f32.positive.max], expected: kAny }, + { input: [1, kValue.f32.negative.min], expected: kAny }, + { input: [1, 0], expected: kAny }, + { input: [1, kValue.f32.subnormal.positive.max], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = remainderInterval(x, y); + t.expect( + objectEquals(expected, got), + `remainderInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('powInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: [-1, 0], expected: kAny }, + { input: [0, 0], expected: kAny }, + { input: [1, 0], expected: [minusNULP(1, 3), hexToF64(0x3ff00000, 0x30000000)] }, // ~1 + { input: [2, 0], expected: [minusNULP(1, 3), hexToF64(0x3ff00000, 0x30000000)] }, // ~1 + { input: [kValue.f32.positive.max, 0], expected: [minusNULP(1, 3), hexToF64(0x3ff00000, 0x30000000)] }, // ~1 + { input: [0, 1], expected: kAny }, + { input: [1, 1], expected: [hexToF64(0x3feffffe, 0xdffffe00), hexToF64(0x3ff00000, 0xc0000200)] }, // ~1 + { input: [1, 100], expected: [hexToF64(0x3fefffba, 0x3fff3800), hexToF64(0x3ff00023, 0x2000c800)] }, // ~1 + { input: [1, kValue.f32.positive.max], expected: kAny }, + { input: [2, 1], expected: [hexToF64(0x3ffffffe, 0xa0000200), hexToF64(0x40000001, 0x00000200)] }, // ~2 + { input: [2, 2], expected: [hexToF64(0x400ffffd, 0xa0000400), hexToF64(0x40100001, 0xa0000400)] }, // ~4 + { input: [10, 10], expected: [hexToF64(0x4202a04f, 0x51f77000), hexToF64(0x4202a070, 0xee08e000)] }, // ~10000000000 + { input: [10, 1], expected: [hexToF64(0x4023fffe, 0x0b658b00), hexToF64(0x40240002, 0x149a7c00)] }, // ~10 + { input: [kValue.f32.positive.max, 1], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = powInterval(x, y); + t.expect( + objectEquals(expected, got), + `powInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('stepInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [1] }, + { input: [1, 1], expected: [1] }, + { input: [0, 1], expected: [1] }, + { input: [1, 0], expected: [0] }, + { input: [-1, -1], expected: [1] }, + { input: [0, -1], expected: [0] }, + { input: [-1, 0], expected: [1] }, + { input: [-1, 1], expected: [1] }, + { input: [1, -1], expected: [0] }, + + // 64-bit normals + { input: [0.1, 0.1], expected: [0, 1] }, + { input: [0, 0.1], expected: [1] }, + { input: [0.1, 0], expected: [0] }, + { input: [0.1, 1], expected: [1] }, + { input: [1, 0.1], expected: [0] }, + { input: [-0.1, -0.1], expected: [0, 1] }, + { input: [0, -0.1], expected: [0] }, + { input: [-0.1, 0], expected: [1] }, + { input: [-0.1, -1], expected: [0] }, + { input: [-1, -0.1], expected: [1] }, + + // Subnormals + { input: [0, kValue.f32.subnormal.positive.max], expected: [1] }, + { input: [0, kValue.f32.subnormal.positive.min], expected: [1] }, + { input: [0, kValue.f32.subnormal.negative.max], expected: [0, 1] }, + { input: [0, kValue.f32.subnormal.negative.min], expected: [0, 1] }, + { input: [1, kValue.f32.subnormal.positive.max], expected: [0] }, + { input: [1, kValue.f32.subnormal.positive.min], expected: [0] }, + { input: [1, kValue.f32.subnormal.negative.max], expected: [0] }, + { input: [1, kValue.f32.subnormal.negative.min], expected: [0] }, + { input: [-1, kValue.f32.subnormal.positive.max], expected: [1] }, + { input: [-1, kValue.f32.subnormal.positive.min], expected: [1] }, + { input: [-1, kValue.f32.subnormal.negative.max], expected: [1] }, + { input: [-1, kValue.f32.subnormal.negative.min], expected: [1] }, + { input: [kValue.f32.subnormal.positive.max, 0], expected: [0, 1] }, + { input: [kValue.f32.subnormal.positive.min, 0], expected: [0, 1] }, + { input: [kValue.f32.subnormal.negative.max, 0], expected: [1] }, + { input: [kValue.f32.subnormal.negative.min, 0], expected: [1] }, + { input: [kValue.f32.subnormal.positive.max, 1], expected: [1] }, + { input: [kValue.f32.subnormal.positive.min, 1], expected: [1] }, + { input: [kValue.f32.subnormal.negative.max, 1], expected: [1] }, + { input: [kValue.f32.subnormal.negative.min, 1], expected: [1] }, + { input: [kValue.f32.subnormal.positive.max, -1], expected: [0] }, + { input: [kValue.f32.subnormal.positive.min, -1], expected: [0] }, + { input: [kValue.f32.subnormal.negative.max, -1], expected: [0] }, + { input: [kValue.f32.subnormal.negative.min, -1], expected: [0] }, + { input: [kValue.f32.subnormal.negative.min, kValue.f32.subnormal.positive.max], expected: [1] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min], expected: [0, 1] }, + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, 0], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [edge, x] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = stepInterval(edge, x); + t.expect( + objectEquals(expected, got), + `stepInterval(${edge}, ${x}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('subtractionInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // 32-bit normals + { input: [0, 0], expected: [0] }, + { input: [1, 0], expected: [1] }, + { input: [0, 1], expected: [-1] }, + { input: [-1, 0], expected: [-1] }, + { input: [0, -1], expected: [1] }, + { input: [1, 1], expected: [0] }, + { input: [1, -1], expected: [2] }, + { input: [-1, 1], expected: [-2] }, + { input: [-1, -1], expected: [0] }, + + // 64-bit normals + { input: [0.1, 0], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [0, 0.1], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [-0.1, 0], expected: [hexToF32(0xbdcccccd), plusOneULP(hexToF32(0xbdcccccd))] }, // ~-0.1 + { input: [0, -0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)), hexToF32(0x3dcccccd)] }, // ~0.1 + { input: [0.1, 0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)) - hexToF32(0x3dcccccd), hexToF32(0x3dcccccd) - minusOneULP(hexToF32(0x3dcccccd))] }, // ~0.0 + { input: [0.1, -0.1], expected: [minusOneULP(hexToF32(0x3e4ccccd)), hexToF32(0x3e4ccccd)] }, // ~0.2 + { input: [-0.1, 0.1], expected: [hexToF32(0xbe4ccccd), plusOneULP(hexToF32(0xbe4ccccd))] }, // ~-0.2 + { input: [-0.1, -0.1], expected: [minusOneULP(hexToF32(0x3dcccccd)) - hexToF32(0x3dcccccd), hexToF32(0x3dcccccd) - minusOneULP(hexToF32(0x3dcccccd))] }, // ~0 + + // // 32-bit normals + { input: [kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [0, kValue.f32.subnormal.positive.max], expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: [kValue.f32.subnormal.positive.min, 0], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, kValue.f32.subnormal.positive.min], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [kValue.f32.subnormal.negative.max, 0], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [0, kValue.f32.subnormal.negative.max], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [kValue.f32.subnormal.negative.min, 0], expected: [kValue.f32.subnormal.negative.min, 0] }, + { input: [0, kValue.f32.subnormal.negative.min], expected: [0, kValue.f32.subnormal.positive.max] }, + + // Infinities + { input: [0, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, 0], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = subtractionInterval(x, y); + t.expect( + objectEquals(expected, got), + `subtractionInterval(${x}, ${y}) returned ${got}. Expected ${expected}` + ); + }); + +interface TernaryToIntervalCase { + input: [number, number, number]; + expected: IntervalBounds; +} + +g.test('clampMedianInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Normals + { input: [0, 0, 0], expected: [0] }, + { input: [1, 0, 0], expected: [0] }, + { input: [0, 1, 0], expected: [0] }, + { input: [0, 0, 1], expected: [0] }, + { input: [1, 0, 1], expected: [1] }, + { input: [1, 1, 0], expected: [1] }, + { input: [0, 1, 1], expected: [1] }, + { input: [1, 1, 1], expected: [1] }, + { input: [1, 10, 100], expected: [10] }, + { input: [10, 1, 100], expected: [10] }, + { input: [100, 1, 10], expected: [10] }, + { input: [-10, 1, 100], expected: [1] }, + { input: [10, 1, -100], expected: [1] }, + { input: [-10, 1, -100], expected: [-10] }, + { input: [-10, -10, -10], expected: [-10] }, + + // Subnormals + { input: [kValue.f32.subnormal.positive.max, 0, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.positive.max, 0], expected: [0] }, + { input: [0, 0, kValue.f32.subnormal.positive.max], expected: [0] }, + { input: [kValue.f32.subnormal.positive.max, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [0, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.negative.max], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [kValue.f32.positive.max, kValue.f32.positive.max, kValue.f32.subnormal.positive.min], expected: [kValue.f32.positive.max] }, + + // Infinities + { input: [0, 1, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [x, y, z] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = clampMedianInterval(x, y, z); + t.expect( + objectEquals(expected, got), + `clampMedianInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('clampMinMaxInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Normals + { input: [0, 0, 0], expected: [0] }, + { input: [1, 0, 0], expected: [0] }, + { input: [0, 1, 0], expected: [0] }, + { input: [0, 0, 1], expected: [0] }, + { input: [1, 0, 1], expected: [1] }, + { input: [1, 1, 0], expected: [0] }, + { input: [0, 1, 1], expected: [1] }, + { input: [1, 1, 1], expected: [1] }, + { input: [1, 10, 100], expected: [10] }, + { input: [10, 1, 100], expected: [10] }, + { input: [100, 1, 10], expected: [10] }, + { input: [-10, 1, 100], expected: [1] }, + { input: [10, 1, -100], expected: [-100] }, + { input: [-10, 1, -100], expected: [-100] }, + { input: [-10, -10, -10], expected: [-10] }, + + // Subnormals + { input: [kValue.f32.subnormal.positive.max, 0, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.positive.max, 0], expected: [0] }, + { input: [0, 0, kValue.f32.subnormal.positive.max], expected: [0] }, + { input: [kValue.f32.subnormal.positive.max, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, 0] }, + { input: [kValue.f32.positive.max, kValue.f32.positive.max, kValue.f32.subnormal.positive.min], expected: [0, kValue.f32.subnormal.positive.min] }, + + // Infinities + { input: [0, 1, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + ] + ) + .fn(t => { + const [x, y, z] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = clampMinMaxInterval(x, y, z); + t.expect( + objectEquals(expected, got), + `clampMinMaxInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('fmaInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Normals + { input: [0, 0, 0], expected: [0] }, + { input: [1, 0, 0], expected: [0] }, + { input: [0, 1, 0], expected: [0] }, + { input: [0, 0, 1], expected: [1] }, + { input: [1, 0, 1], expected: [1] }, + { input: [1, 1, 0], expected: [1] }, + { input: [0, 1, 1], expected: [1] }, + { input: [1, 1, 1], expected: [2] }, + { input: [1, 10, 100], expected: [110] }, + { input: [10, 1, 100], expected: [110] }, + { input: [100, 1, 10], expected: [110] }, + { input: [-10, 1, 100], expected: [90] }, + { input: [10, 1, -100], expected: [-90] }, + { input: [-10, 1, -100], expected: [-110] }, + { input: [-10, -10, -10], expected: [90] }, + + // Subnormals + { input: [kValue.f32.subnormal.positive.max, 0, 0], expected: [0] }, + { input: [0, kValue.f32.subnormal.positive.max, 0], expected: [0] }, + { input: [0, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, 0, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, 0], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.max] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.positive.min] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.negative.max], expected: [kValue.f32.subnormal.negative.max, kValue.f32.subnormal.positive.min] }, + { input: [kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.min, kValue.f32.subnormal.negative.max], expected: [hexToF32(0x80000002), 0] }, + + // Infinities + { input: [0, 1, kValue.f32.infinity.positive], expected: kAny }, + { input: [0, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.positive], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, kValue.f32.infinity.negative], expected: kAny }, + { input: [kValue.f32.positive.max, kValue.f32.positive.max, kValue.f32.subnormal.positive.min], expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = fmaInterval(...t.params.input); + t.expect( + objectEquals(expected, got), + `fmaInterval(${t.params.input.join(',')}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('mixImpreciseInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // [0.0, 1.0] cases + { input: [0.0, 1.0, -1.0], expected: [-1.0] }, + { input: [0.0, 1.0, 0.0], expected: [0.0] }, + { input: [0.0, 1.0, 0.1], expected: [hexToF64(0x3fb99999,0x80000000), hexToF64(0x3fb99999,0xa0000000)] }, // ~0.1 + { input: [0.0, 1.0, 0.5], expected: [0.5] }, + { input: [0.0, 1.0, 0.9], expected: [hexToF64(0x3feccccc,0xc0000000), hexToF64(0x3feccccc,0xe0000000)] }, // ~0.9 + { input: [0.0, 1.0, 1.0], expected: [1.0] }, + { input: [0.0, 1.0, 2.0], expected: [2.0] }, + + // [1.0, 0.0] cases + { input: [1.0, 0.0, -1.0], expected: [2.0] }, + { input: [1.0, 0.0, 0.0], expected: [1.0] }, + { input: [1.0, 0.0, 0.1], expected: [hexToF64(0x3feccccc,0xc0000000), hexToF64(0x3feccccc,0xe0000000)] }, // ~0.9 + { input: [1.0, 0.0, 0.5], expected: [0.5] }, + { input: [1.0, 0.0, 0.9], expected: [hexToF64(0x3fb99999,0x00000000), hexToF64(0x3fb9999a,0x00000000)] }, // ~0.1 + { input: [1.0, 0.0, 1.0], expected: [0.0] }, + { input: [1.0, 0.0, 2.0], expected: [-1.0] }, + + // [0.0, 10.0] cases + { input: [0.0, 10.0, -1.0], expected: [-10.0] }, + { input: [0.0, 10.0, 0.0], expected: [0.0] }, + { input: [0.0, 10.0, 0.1], expected: [hexToF64(0x3fefffff,0xe0000000), hexToF64(0x3ff00000,0x20000000)] }, // ~1 + { input: [0.0, 10.0, 0.5], expected: [5.0] }, + { input: [0.0, 10.0, 0.9], expected: [hexToF64(0x4021ffff,0xe0000000), hexToF64(0x40220000,0x20000000)] }, // ~9 + { input: [0.0, 10.0, 1.0], expected: [10.0] }, + { input: [0.0, 10.0, 2.0], expected: [20.0] }, + + // [2.0, 10.0] cases + { input: [2.0, 10.0, -1.0], expected: [-6.0] }, + { input: [2.0, 10.0, 0.0], expected: [2.0] }, + { input: [2.0, 10.0, 0.1], expected: [hexToF64(0x40066666,0x60000000), hexToF64(0x40066666,0x80000000)] }, // ~2.8 + { input: [2.0, 10.0, 0.5], expected: [6.0] }, + { input: [2.0, 10.0, 0.9], expected: [hexToF64(0x40226666,0x60000000), hexToF64(0x40226666,0x80000000)] }, // ~9.2 + { input: [2.0, 10.0, 1.0], expected: [10.0] }, + { input: [2.0, 10.0, 2.0], expected: [18.0] }, + + // [-1.0, 1.0] cases + { input: [-1.0, 1.0, -2.0], expected: [-5.0] }, + { input: [-1.0, 1.0, 0.0], expected: [-1.0] }, + { input: [-1.0, 1.0, 0.1], expected: [hexToF64(0xbfe99999,0xa0000000), hexToF64(0xbfe99999,0x80000000)] }, // ~-0.8 + { input: [-1.0, 1.0, 0.5], expected: [0.0] }, + { input: [-1.0, 1.0, 0.9], expected: [hexToF64(0x3fe99999,0x80000000), hexToF64(0x3fe99999,0xc0000000)] }, // ~0.8 + { input: [-1.0, 1.0, 1.0], expected: [1.0] }, + { input: [-1.0, 1.0, 2.0], expected: [3.0] }, + + // Infinities + { input: [0.0, kValue.f32.infinity.positive, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0.0, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.negative, 1.0, 0.5], expected: kAny }, + { input: [1.0, kValue.f32.infinity.negative, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative, 0.5], expected: kAny }, + { input: [0.0, 1.0, kValue.f32.infinity.negative], expected: kAny }, + { input: [1.0, 0.0, kValue.f32.infinity.negative], expected: kAny }, + { input: [0.0, 1.0, kValue.f32.infinity.positive], expected: kAny }, + { input: [1.0, 0.0, kValue.f32.infinity.positive], expected: kAny }, + + // Showing how precise and imprecise versions diff + { input: [kValue.f32.negative.min, 10.0, 1.0], expected: [0.0]}, + ] + ) + .fn(t => { + const [x, y, z] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = mixImpreciseInterval(x, y, z); + t.expect( + objectEquals(expected, got), + `mixImpreciseInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('mixPreciseInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // [0.0, 1.0] cases + { input: [0.0, 1.0, -1.0], expected: [-1.0] }, + { input: [0.0, 1.0, 0.0], expected: [0.0] }, + { input: [0.0, 1.0, 0.1], expected: [hexToF64(0x3fb99999,0x80000000), hexToF64(0x3fb99999,0xa0000000)] }, // ~0.1 + { input: [0.0, 1.0, 0.5], expected: [0.5] }, + { input: [0.0, 1.0, 0.9], expected: [hexToF64(0x3feccccc,0xc0000000), hexToF64(0x3feccccc,0xe0000000)] }, // ~0.9 + { input: [0.0, 1.0, 1.0], expected: [1.0] }, + { input: [0.0, 1.0, 2.0], expected: [2.0] }, + + // [1.0, 0.0] cases + { input: [1.0, 0.0, -1.0], expected: [2.0] }, + { input: [1.0, 0.0, 0.0], expected: [1.0] }, + { input: [1.0, 0.0, 0.1], expected: [hexToF64(0x3feccccc,0xc0000000), hexToF64(0x3feccccc,0xe0000000)] }, // ~0.9 + { input: [1.0, 0.0, 0.5], expected: [0.5] }, + { input: [1.0, 0.0, 0.9], expected: [hexToF64(0x3fb99999,0x00000000), hexToF64(0x3fb9999a,0x00000000)] }, // ~0.1 + { input: [1.0, 0.0, 1.0], expected: [0.0] }, + { input: [1.0, 0.0, 2.0], expected: [-1.0] }, + + // [0.0, 10.0] cases + { input: [0.0, 10.0, -1.0], expected: [-10.0] }, + { input: [0.0, 10.0, 0.0], expected: [0.0] }, + { input: [0.0, 10.0, 0.1], expected: [hexToF64(0x3fefffff,0xe0000000), hexToF64(0x3ff00000,0x20000000)] }, // ~1 + { input: [0.0, 10.0, 0.5], expected: [5.0] }, + { input: [0.0, 10.0, 0.9], expected: [hexToF64(0x4021ffff,0xe0000000), hexToF64(0x40220000,0x20000000)] }, // ~9 + { input: [0.0, 10.0, 1.0], expected: [10.0] }, + { input: [0.0, 10.0, 2.0], expected: [20.0] }, + + // [2.0, 10.0] cases + { input: [2.0, 10.0, -1.0], expected: [-6.0] }, + { input: [2.0, 10.0, 0.0], expected: [2.0] }, + { input: [2.0, 10.0, 0.1], expected: [hexToF64(0x40066666,0x40000000), hexToF64(0x40066666,0x80000000)] }, // ~2.8 + { input: [2.0, 10.0, 0.5], expected: [6.0] }, + { input: [2.0, 10.0, 0.9], expected: [hexToF64(0x40226666,0x40000000), hexToF64(0x40226666,0xa0000000)] }, // ~9.2 + { input: [2.0, 10.0, 1.0], expected: [10.0] }, + { input: [2.0, 10.0, 2.0], expected: [18.0] }, + + // [-1.0, 1.0] cases + { input: [-1.0, 1.0, -2.0], expected: [-5.0] }, + { input: [-1.0, 1.0, 0.0], expected: [-1.0] }, + { input: [-1.0, 1.0, 0.1], expected: [hexToF64(0xbfe99999,0xc0000000), hexToF64(0xbfe99999,0x80000000)] }, // ~-0.8 + { input: [-1.0, 1.0, 0.5], expected: [0.0] }, + { input: [-1.0, 1.0, 0.9], expected: [hexToF64(0x3fe99999,0x80000000), hexToF64(0x3fe99999,0xc0000000)] }, // ~0.8 + { input: [-1.0, 1.0, 1.0], expected: [1.0] }, + { input: [-1.0, 1.0, 2.0], expected: [3.0] }, + + // Infinities + { input: [0.0, kValue.f32.infinity.positive, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.positive, 0.0, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.negative, 1.0, 0.5], expected: kAny }, + { input: [1.0, kValue.f32.infinity.negative, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.negative, kValue.f32.infinity.positive, 0.5], expected: kAny }, + { input: [kValue.f32.infinity.positive, kValue.f32.infinity.negative, 0.5], expected: kAny }, + { input: [0.0, 1.0, kValue.f32.infinity.negative], expected: kAny }, + { input: [1.0, 0.0, kValue.f32.infinity.negative], expected: kAny }, + { input: [0.0, 1.0, kValue.f32.infinity.positive], expected: kAny }, + { input: [1.0, 0.0, kValue.f32.infinity.positive], expected: kAny }, + + // Showing how precise and imprecise versions diff + { input: [kValue.f32.negative.min, 10.0, 1.0], expected: [10.0]}, + ] + ) + .fn(t => { + const [x, y, z] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = mixPreciseInterval(x, y, z); + t.expect( + objectEquals(expected, got), + `mixPreciseInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}` + ); + }); + +g.test('smoothStepInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // Normals + { input: [0, 1, 0], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, 1, 1], expected: [hexToF32(0x3f7ffffa), hexToF32(0x3f800003)] }, // ~1 + { input: [0, 1, 10], expected: [1] }, + { input: [0, 1, -10], expected: [0] }, + { input: [0, 2, 1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [0, 2, 0.5], expected: [hexToF32(0x3e1ffffb), hexToF32(0x3e200007)] }, // ~0.15625... + { input: [2, 0, 1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [2, 0, 1.5], expected: [hexToF32(0x3e1ffffb), hexToF32(0x3e200007)] }, // ~0.15625... + { input: [0, 100, 50], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [0, 100, 25], expected: [hexToF32(0x3e1ffffb), hexToF32(0x3e200007)] }, // ~0.15625... + { input: [0, -2, -1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [0, -2, -0.5], expected: [hexToF32(0x3e1ffffb), hexToF32(0x3e200007)] }, // ~0.15625... + + // Subnormals + { input: [0, 2, kValue.f32.subnormal.positive.max], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, 2, kValue.f32.subnormal.positive.min], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, 2, kValue.f32.subnormal.negative.max], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [0, 2, kValue.f32.subnormal.negative.min], expected: [0, kValue.f32.subnormal.positive.min] }, + { input: [kValue.f32.subnormal.positive.max, 2, 1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [kValue.f32.subnormal.positive.min, 2, 1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [kValue.f32.subnormal.negative.max, 2, 1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [kValue.f32.subnormal.negative.min, 2, 1], expected: [hexToF32(0x3efffff8), hexToF32(0x3f000007)] }, // ~0.5 + { input: [0, kValue.f32.subnormal.positive.max, 1], expected: kAny }, + { input: [0, kValue.f32.subnormal.positive.min, 1], expected: kAny }, + { input: [0, kValue.f32.subnormal.negative.max, 1], expected: kAny }, + { input: [0, kValue.f32.subnormal.negative.min, 1], expected: kAny }, + + // Infinities + { input: [0, 2, Number.POSITIVE_INFINITY], expected: kAny }, + { input: [0, 2, Number.NEGATIVE_INFINITY], expected: kAny }, + { input: [Number.POSITIVE_INFINITY, 2, 1], expected: kAny }, + { input: [Number.NEGATIVE_INFINITY, 2, 1], expected: kAny }, + { input: [0, Number.POSITIVE_INFINITY, 1], expected: kAny }, + { input: [0, Number.NEGATIVE_INFINITY, 1], expected: kAny }, + ] + ) + .fn(t => { + const [low, high, x] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = smoothStepInterval(low, high, x); + t.expect( + objectEquals(expected, got), + `smoothStepInterval(${low}, ${high}, ${x}) returned ${got}. Expected ${expected}` + ); + }); + +interface PointToVectorCase { + input: number; + expected: IntervalBounds[]; +} + +// Scope for unpack* tests so that they can have constants for magic numbers +// that don't pollute the global namespace or have unwieldy long names. +{ + const kZeroBounds: IntervalBounds = [hexToF32(0x81200000), hexToF32(0x01200000)]; + const kOneBoundsSnorm: IntervalBounds = [ + hexToF64(0x3fefffff, 0xa0000000), + hexToF64(0x3ff00000, 0x40000000), + ]; + const kOneBoundsUnorm: IntervalBounds = [ + hexToF64(0x3fefffff, 0xb0000000), + hexToF64(0x3ff00000, 0x28000000), + ]; + const kNegOneBoundsSnorm: IntervalBounds = [ + hexToF64(0xbff00000, 0x00000000), + hexToF64(0xbfefffff, 0xa0000000), + ]; + + const kHalfBounds2x16snorm: IntervalBounds = [ + hexToF64(0x3fe0001f, 0xa0000000), + hexToF64(0x3fe00020, 0x80000000), + ]; // ~0.5..., due to lack of precision in i16 + const kNegHalfBounds2x16snorm: IntervalBounds = [ + hexToF64(0xbfdfffc0, 0x60000000), + hexToF64(0xbfdfffbf, 0x80000000), + ]; // ~-0.5..., due to lack of precision in i16 + + g.test('unpack2x16snormInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: 0x00000000, expected: [kZeroBounds, kZeroBounds] }, + { input: 0x00007fff, expected: [kOneBoundsSnorm, kZeroBounds] }, + { input: 0x7fff0000, expected: [kZeroBounds, kOneBoundsSnorm] }, + { input: 0x7fff7fff, expected: [kOneBoundsSnorm, kOneBoundsSnorm] }, + { input: 0x80018001, expected: [kNegOneBoundsSnorm, kNegOneBoundsSnorm] }, + { input: 0x40004000, expected: [kHalfBounds2x16snorm, kHalfBounds2x16snorm] }, + { input: 0xc001c001, expected: [kNegHalfBounds2x16snorm, kNegHalfBounds2x16snorm] }, + ] + ) + .fn(t => { + const expected = toF32Vector(t.params.expected); + + const got = unpack2x16snormInterval(t.params.input); + + t.expect( + objectEquals(expected, got), + `unpack2x16snormInterval(${t.params.input}) returned [${got}]. Expected [${expected}]` + ); + }); + + g.test('unpack2x16floatInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // f16 normals + { input: 0x00000000, expected: [[0], [0]] }, + { input: 0x80000000, expected: [[0], [0]] }, + { input: 0x00008000, expected: [[0], [0]] }, + { input: 0x80008000, expected: [[0], [0]] }, + { input: 0x00003c00, expected: [[1], [0]] }, + { input: 0x3c000000, expected: [[0], [1]] }, + { input: 0x3c003c00, expected: [[1], [1]] }, + { input: 0xbc00bc00, expected: [[-1], [-1]] }, + { input: 0x49004900, expected: [[10], [10]] }, + { input: 0xc900c900, expected: [[-10], [-10]] }, + + // f16 subnormals + { input: 0x000003ff, expected: [[0, kValue.f16.subnormal.positive.max], [0]] }, + { input: 0x000083ff, expected: [[kValue.f16.subnormal.negative.min, 0], [0]] }, + + // f16 out of bounds + { input: 0x7c000000, expected: [kAny, kAny] }, + { input: 0xffff0000, expected: [kAny, kAny] }, + ] + ) + .fn(t => { + const expected = toF32Vector(t.params.expected); + + const got = unpack2x16floatInterval(t.params.input); + + t.expect( + objectEquals(expected, got), + `unpack2x16floatInterval(${t.params.input}) returned [${got}]. Expected [${expected}]` + ); + }); + + const kHalfBounds2x16unorm: IntervalBounds = [ + hexToF64(0x3fe0000f, 0xb0000000), + hexToF64(0x3fe00010, 0x70000000), + ]; // ~0.5..., due to lack of precision in u16 + + g.test('unpack2x16unormInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: 0x00000000, expected: [kZeroBounds, kZeroBounds] }, + { input: 0x0000ffff, expected: [kOneBoundsUnorm, kZeroBounds] }, + { input: 0xffff0000, expected: [kZeroBounds, kOneBoundsUnorm] }, + { input: 0xffffffff, expected: [kOneBoundsUnorm, kOneBoundsUnorm] }, + { input: 0x80008000, expected: [kHalfBounds2x16unorm, kHalfBounds2x16unorm] }, + ] + ) + .fn(t => { + const expected = toF32Vector(t.params.expected); + + const got = unpack2x16unormInterval(t.params.input); + + t.expect( + objectEquals(expected, got), + `unpack2x16unormInterval(${t.params.input}) returned [${got}]. Expected [${expected}]` + ); + }); + + const kHalfBounds4x8snorm: IntervalBounds = [ + hexToF64(0x3fe02040, 0x20000000), + hexToF64(0x3fe02041, 0x00000000), + ]; // ~0.50196..., due to lack of precision in i8 + const kNegHalfBounds4x8snorm: IntervalBounds = [ + hexToF64(0xbfdfbf7f, 0x60000000), + hexToF64(0xbfdfbf7e, 0x80000000), + ]; // ~-0.49606..., due to lack of precision in i8 + + g.test('unpack4x8snormInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: 0x00000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kZeroBounds] }, + { input: 0x0000007f, expected: [kOneBoundsSnorm, kZeroBounds, kZeroBounds, kZeroBounds] }, + { input: 0x00007f00, expected: [kZeroBounds, kOneBoundsSnorm, kZeroBounds, kZeroBounds] }, + { input: 0x007f0000, expected: [kZeroBounds, kZeroBounds, kOneBoundsSnorm, kZeroBounds] }, + { input: 0x7f000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kOneBoundsSnorm] }, + { input: 0x00007f7f, expected: [kOneBoundsSnorm, kOneBoundsSnorm, kZeroBounds, kZeroBounds] }, + { input: 0x7f7f0000, expected: [kZeroBounds, kZeroBounds, kOneBoundsSnorm, kOneBoundsSnorm] }, + { input: 0x7f007f00, expected: [kZeroBounds, kOneBoundsSnorm, kZeroBounds, kOneBoundsSnorm] }, + { input: 0x007f007f, expected: [kOneBoundsSnorm, kZeroBounds, kOneBoundsSnorm, kZeroBounds] }, + { input: 0x7f7f7f7f, expected: [kOneBoundsSnorm, kOneBoundsSnorm, kOneBoundsSnorm, kOneBoundsSnorm] }, + { input: 0x81818181, expected: [kNegOneBoundsSnorm, kNegOneBoundsSnorm, kNegOneBoundsSnorm, kNegOneBoundsSnorm] }, + { input: 0x40404040, expected: [kHalfBounds4x8snorm, kHalfBounds4x8snorm, kHalfBounds4x8snorm, kHalfBounds4x8snorm] }, + { input: 0xc1c1c1c1, expected: [kNegHalfBounds4x8snorm, kNegHalfBounds4x8snorm, kNegHalfBounds4x8snorm, kNegHalfBounds4x8snorm] }, + ] + ) + .fn(t => { + const expected = toF32Vector(t.params.expected); + + const got = unpack4x8snormInterval(t.params.input); + + t.expect( + objectEquals(expected, got), + `unpack4x8snormInterval(${t.params.input}) returned [${got}]. Expected [${expected}]` + ); + }); + + const kHalfBounds4x8unorm: IntervalBounds = [ + hexToF64(0x3fe0100f, 0xb0000000), + hexToF64(0x3fe01010, 0x70000000), + ]; // ~0.50196..., due to lack of precision in u8 + + g.test('unpack4x8unormInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + { input: 0x00000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kZeroBounds] }, + { input: 0x000000ff, expected: [kOneBoundsUnorm, kZeroBounds, kZeroBounds, kZeroBounds] }, + { input: 0x0000ff00, expected: [kZeroBounds, kOneBoundsUnorm, kZeroBounds, kZeroBounds] }, + { input: 0x00ff0000, expected: [kZeroBounds, kZeroBounds, kOneBoundsUnorm, kZeroBounds] }, + { input: 0xff000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kOneBoundsUnorm] }, + { input: 0x0000ffff, expected: [kOneBoundsUnorm, kOneBoundsUnorm, kZeroBounds, kZeroBounds] }, + { input: 0xffff0000, expected: [kZeroBounds, kZeroBounds, kOneBoundsUnorm, kOneBoundsUnorm] }, + { input: 0xff00ff00, expected: [kZeroBounds, kOneBoundsUnorm, kZeroBounds, kOneBoundsUnorm] }, + { input: 0x00ff00ff, expected: [kOneBoundsUnorm, kZeroBounds, kOneBoundsUnorm, kZeroBounds] }, + { input: 0xffffffff, expected: [kOneBoundsUnorm, kOneBoundsUnorm, kOneBoundsUnorm, kOneBoundsUnorm] }, + { input: 0x80808080, expected: [kHalfBounds4x8unorm, kHalfBounds4x8unorm, kHalfBounds4x8unorm, kHalfBounds4x8unorm] }, + ] + ) + .fn(t => { + const expected = toF32Vector(t.params.expected); + + const got = unpack4x8unormInterval(t.params.input); + + t.expect( + objectEquals(expected, got), + `unpack4x8unormInterval(${t.params.input}) returned [${got}]. Expected [${expected}]` + ); + }); +} + +interface VectorToIntervalCase { + input: number[]; + expected: IntervalBounds; +} + +g.test('lengthIntervalVector') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult to express in a closed human readable + // form due to the inherited nature of the errors. + // vec2 + {input: [1.0, 0.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [0.0, 1.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [1.0, 1.0], expected: [hexToF64(0x3ff6a09d, 0xb0000000), hexToF64(0x3ff6a09f, 0x10000000)] }, // ~√2 + {input: [-1.0, -1.0], expected: [hexToF64(0x3ff6a09d, 0xb0000000), hexToF64(0x3ff6a09f, 0x10000000)] }, // ~√2 + {input: [-1.0, 1.0], expected: [hexToF64(0x3ff6a09d, 0xb0000000), hexToF64(0x3ff6a09f, 0x10000000)] }, // ~√2 + {input: [0.1, 0.0], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + + // vec3 + {input: [1.0, 0.0, 0.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [0.0, 1.0, 0.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [0.0, 0.0, 1.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [1.0, 1.0, 1.0], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + {input: [-1.0, -1.0, -1.0], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + {input: [1.0, -1.0, -1.0], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + {input: [0.1, 0.0, 0.0], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + + // vec4 + {input: [1.0, 0.0, 0.0, 0.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [0.0, 1.0, 0.0, 0.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [0.0, 0.0, 1.0, 0.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [0.0, 0.0, 0.0, 1.0], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + {input: [1.0, 1.0, 1.0, 1.0], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + {input: [-1.0, -1.0, -1.0, -1.0], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + {input: [-1.0, 1.0, -1.0, 1.0], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + {input: [0.1, 0.0, 0.0, 0.0], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + + // Test that dot going OOB bounds in the intermediate calculations propagates + { input: [kValue.f32.positive.nearest_max, kValue.f32.positive.max, kValue.f32.negative.min], expected: kAny }, + { input: [kValue.f32.positive.max, kValue.f32.positive.nearest_max, kValue.f32.negative.min], expected: kAny }, + { input: [kValue.f32.negative.min, kValue.f32.positive.max, kValue.f32.positive.nearest_max], expected: kAny }, + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = lengthInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `lengthInterval([${t.params.input}]) returned ${got}. Expected ${expected}` + ); + }); + +interface VectorPairToIntervalCase { + input: [number[], number[]]; + expected: IntervalBounds; +} + +g.test('distanceIntervalVector') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Some of these are hard coded, since the error intervals are difficult + // to express in a closed human readable form due to the inherited nature + // of the errors. + // + // distance(x, y), where x - y = 0 has an acceptance interval of kAny, + // because distance(x, y) = length(x - y), and length(0) = kAny + + // vec2 + { input: [[1.0, 0.0], [1.0, 0.0]], expected: kAny }, + { input: [[1.0, 0.0], [0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0], [1.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[-1.0, 0.0], [0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0], [-1.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 1.0], [-1.0, 0.0]], expected: [hexToF64(0x3ff6a09d, 0xb0000000), hexToF64(0x3ff6a09f, 0x10000000)] }, // ~√2 + { input: [[0.1, 0.0], [0.0, 0.0]], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + + // vec3 + { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kAny }, + { input: [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 1.0, 0.0], [0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 1.0], [0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[1.0, 1.0, 1.0], [0.0, 0.0, 0.0]], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + { input: [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + { input: [[-1.0, -1.0, -1.0], [0.0, 0.0, 0.0]], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + { input: [[0.0, 0.0, 0.0], [-1.0, -1.0, -1.0]], expected: [hexToF64(0x3ffbb67a, 0x10000000), hexToF64(0x3ffbb67b, 0xb0000000)] }, // ~√3 + { input: [[0.1, 0.0, 0.0], [0.0, 0.0, 0.0]], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: [[0.0, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + + // vec4 + { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kAny }, + { input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]], expected: [hexToF64(0x3fefffff, 0x70000000), hexToF64(0x3ff00000, 0x90000000)] }, // ~1 + { input: [[1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + { input: [[0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0]], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + { input: [[-1.0, 1.0, -1.0, 1.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + { input: [[0.0, 0.0, 0.0, 0.0], [1.0, -1.0, 1.0, -1.0]], expected: [hexToF64(0x3fffffff, 0x70000000), hexToF64(0x40000000, 0x90000000)] }, // ~2 + { input: [[0.1, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + { input: [[0.0, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fb99998, 0x90000000), hexToF64(0x3fb9999a, 0x70000000)] }, // ~0.1 + ] + ) + .fn(t => { + const expected = toF32Interval(t.params.expected); + + const got = distanceInterval(...t.params.input); + t.expect( + objectEquals(expected, got), + `distanceInterval([${t.params.input[0]}, ${t.params.input[1]}]) returned ${got}. Expected ${expected}` + ); + }); + +g.test('dotInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // vec2 + { input: [[1.0, 0.0], [1.0, 0.0]], expected: [1.0] }, + { input: [[0.0, 1.0], [0.0, 1.0]], expected: [1.0] }, + { input: [[1.0, 1.0], [1.0, 1.0]], expected: [2.0] }, + { input: [[-1.0, -1.0], [-1.0, -1.0]], expected: [2.0] }, + { input: [[-1.0, 1.0], [1.0, -1.0]], expected: [-2.0] }, + { input: [[0.1, 0.0], [1.0, 0.0]], expected: [hexToF64(0x3fb99999, 0x80000000), hexToF64(0x3fb99999, 0xa0000000)]}, // ~0.1 + + // vec3 + { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [1.0] }, + { input: [[0.0, 1.0, 0.0], [0.0, 1.0, 0.0]], expected: [1.0] }, + { input: [[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], expected: [1.0] }, + { input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [3.0] }, + { input: [[-1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]], expected: [3.0] }, + { input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: [-1.0] }, + { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [hexToF64(0x3fb99999, 0x80000000), hexToF64(0x3fb99999, 0xa0000000)]}, // ~0.1 + + // vec4 + { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [1.0] }, + { input: [[0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]], expected: [1.0] }, + { input: [[0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: [1.0] }, + { input: [[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0]], expected: [1.0] }, + { input: [[1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0]], expected: [4.0] }, + { input: [[-1.0, -1.0, -1.0, -1.0], [-1.0, -1.0, -1.0, -1.0]], expected: [4.0] }, + { input: [[-1.0, 1.0, -1.0, 1.0], [1.0, -1.0, 1.0, -1.0]], expected: [-4.0] }, + { input: [[0.1, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [hexToF64(0x3fb99999, 0x80000000), hexToF64(0x3fb99999, 0xa0000000)]}, // ~0.1 + + // Test that going out of bounds in the intermediate calculations is caught correctly. + { input: [[kValue.f32.positive.nearest_max, kValue.f32.positive.max, kValue.f32.negative.min], [1.0, 1.0, 1.0]], expected: kAny }, + { input: [[kValue.f32.positive.nearest_max, kValue.f32.negative.min, kValue.f32.positive.max], [1.0, 1.0, 1.0]], expected: kAny }, + { input: [[kValue.f32.positive.max, kValue.f32.positive.nearest_max, kValue.f32.negative.min], [1.0, 1.0, 1.0]], expected: kAny }, + { input: [[kValue.f32.negative.min, kValue.f32.positive.nearest_max, kValue.f32.positive.max], [1.0, 1.0, 1.0]], expected: kAny }, + { input: [[kValue.f32.positive.max, kValue.f32.negative.min, kValue.f32.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kAny }, + { input: [[kValue.f32.negative.min, kValue.f32.positive.max, kValue.f32.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kAny }, + + // https://github.com/gpuweb/cts/issues/2155 + { input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f32.positive.max, -2.0, -3.0]], expected: [-13, 0] }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Interval(t.params.expected); + + const got = dotInterval(x, y); + t.expect( + objectEquals(expected, got), + `dotInterval([${x}], [${y}]) returned ${got}. Expected ${expected}` + ); + }); + +interface VectorToVectorCase { + input: number[]; + expected: IntervalBounds[]; +} + +g.test('normalizeInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // vec2 + {input: [1.0, 0.0], expected: [[hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~1.0, ~0.0] + {input: [0.0, 1.0], expected: [[hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)]] }, // [ ~0.0, ~1.0] + {input: [-1.0, 0.0], expected: [[hexToF64(0xbff00000, 0xb0000000), hexToF64(0xbfeffffe, 0x70000000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~1.0, ~0.0] + {input: [1.0, 1.0], expected: [[hexToF64(0x3fe6a09d, 0x50000000), hexToF64(0x3fe6a09f, 0x90000000)], [hexToF64(0x3fe6a09d, 0x50000000), hexToF64(0x3fe6a09f, 0x90000000)]] }, // [ ~1/√2, ~1/√2] + + // vec3 + {input: [1.0, 0.0, 0.0], expected: [[hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0] + {input: [0.0, 1.0, 0.0], expected: [[hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0] + {input: [0.0, 0.0, 1.0], expected: [[hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)]] }, // [ ~0.0, ~0.0, ~1.0] + {input: [-1.0, 0.0, 0.0], expected: [[hexToF64(0xbff00000, 0xb0000000), hexToF64(0xbfeffffe, 0x70000000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0] + {input: [1.0, 1.0, 1.0], expected: [[hexToF64(0x3fe279a6, 0x50000000), hexToF64(0x3fe279a8, 0x50000000)], [hexToF64(0x3fe279a6, 0x50000000), hexToF64(0x3fe279a8, 0x50000000)], [hexToF64(0x3fe279a6, 0x50000000), hexToF64(0x3fe279a8, 0x50000000)]] }, // [ ~1/√3, ~1/√3, ~1/√3] + + // vec4 + {input: [1.0, 0.0, 0.0, 0.0], expected: [[hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0] + {input: [0.0, 1.0, 0.0, 0.0], expected: [[hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0] + {input: [0.0, 0.0, 1.0, 0.0], expected: [[hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0] + {input: [0.0, 0.0, 0.0, 1.0], expected: [[hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF64(0x3feffffe, 0x70000000), hexToF64(0x3ff00000, 0xb0000000)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0] + {input: [-1.0, 0.0, 0.0, 0.0], expected: [[hexToF64(0xbff00000, 0xb0000000), hexToF64(0xbfeffffe, 0x70000000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)], [hexToF32(0x81200000), hexToF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0] + {input: [1.0, 1.0, 1.0, 1.0], expected: [[hexToF64(0x3fdffffe, 0x70000000), hexToF64(0x3fe00000, 0xb0000000)], [hexToF64(0x3fdffffe, 0x70000000), hexToF64(0x3fe00000, 0xb0000000)], [hexToF64(0x3fdffffe, 0x70000000), hexToF64(0x3fe00000, 0xb0000000)], [hexToF64(0x3fdffffe, 0x70000000), hexToF64(0x3fe00000, 0xb0000000)]] }, // [ ~1/√4, ~1/√4, ~1/√4] + ] + ) + .fn(t => { + const x = t.params.input; + const expected = toF32Vector(t.params.expected); + + const got = normalizeInterval(x); + t.expect( + objectEquals(expected, got), + `normalizeInterval([${x}]) returned ${got}. Expected ${expected}` + ); + }); + +interface VectorPairToVectorCase { + input: [number[], number[]]; + expected: IntervalBounds[]; +} + +g.test('crossInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // parallel vectors, AXB == 0 + { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [[0.0], [0.0], [0.0]] }, + { input: [[0.0, 1.0, 0.0], [0.0, 1.0, 0.0]], expected: [[0.0], [0.0], [0.0]] }, + { input: [[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], expected: [[0.0], [0.0], [0.0]] }, + { input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [[0.0], [0.0], [0.0]] }, + { input: [[-1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]], expected: [[0.0], [0.0], [0.0]] }, + { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [[0.0], [0.0], [0.0]] }, + { input: [[kValue.f32.subnormal.positive.max, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [[0.0], [0.0], [0.0]] }, + + // non-parallel vectors, AXB != 0 + // f32 normals + { input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: [[2.0], [2.0], [0.0]] }, + { input: [[1.0, 2, 3], [1.0, 5.0, 7.0]], expected: [[-1], [-4], [3]] }, + + // f64 normals + { input: [[0.1, -0.1, -0.1], [-0.1, 0.1, -0.1]], + expected: [[hexToF32(0x3ca3d708), hexToF32(0x3ca3d70b)], // ~0.02 + [hexToF32(0x3ca3d708), hexToF32(0x3ca3d70b)], // ~0.02 + [hexToF32(0xb1400000), hexToF32(0x31400000)]] }, // ~0 + + // f32 subnormals + { input: [[kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.max, kValue.f32.subnormal.negative.min], + [kValue.f32.subnormal.negative.min, kValue.f32.subnormal.positive.min, kValue.f32.subnormal.negative.max]], + expected: [[0.0, hexToF32(0x00000002)], // ~0 + [0.0, hexToF32(0x00000002)], // ~0 + [hexToF32(0x80000001), hexToF32(0x00000001)]] }, // ~0 + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Vector(t.params.expected); + + const got = crossInterval(x, y); + t.expect( + objectEquals(expected, got), + `crossInterval([${x}], [${y}]) returned ${got}. Expected ${expected}` + ); + }); + +g.test('reflectInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // vec2s + { input: [[1.0, 0.0], [1.0, 0.0]], expected: [[-1.0], [0.0]] }, + { input: [[1.0, 0.0], [0.0, 1.0]], expected: [[1.0], [0.0]] }, + { input: [[0.0, 1.0], [0.0, 1.0]], expected: [[0.0], [-1.0]] }, + { input: [[0.0, 1.0], [1.0, 0.0]], expected: [[0.0], [1.0]] }, + { input: [[1.0, 1.0], [1.0, 1.0]], expected: [[-3.0], [-3.0]] }, + { input: [[-1.0, -1.0], [1.0, 1.0]], expected: [[3.0], [3.0]] }, + { input: [[0.1, 0.1], [1.0, 1.0]], expected: [[hexToF32(0xbe99999a), hexToF32(0xbe999998)], [hexToF32(0xbe99999a), hexToF32(0xbe999998)]] }, // [~-0.3, ~-0.3] + { input: [[kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.max], [1.0, 1.0]], expected: [[hexToF32(0x80fffffe), hexToF32(0x00800001)], [hexToF32(0x80ffffff), hexToF32(0x00000002)]] }, // [~0.0, ~0.0] + + // vec3s + { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [[-1.0], [0.0], [0.0]] }, + { input: [[0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], expected: [[0.0], [1.0], [0.0]] }, + { input: [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], expected: [[0.0], [0.0], [1.0]] }, + { input: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], expected: [[1.0], [0.0], [0.0]] }, + { input: [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], expected: [[1.0], [0.0], [0.0]] }, + { input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [[-5.0], [-5.0], [-5.0]] }, + { input: [[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]], expected: [[5.0], [5.0], [5.0]] }, + { input: [[0.1, 0.1, 0.1], [1.0, 1.0, 1.0]], expected: [[hexToF32(0xbf000001), hexToF32(0xbefffffe)], [hexToF32(0xbf000001), hexToF32(0xbefffffe)], [hexToF32(0xbf000001), hexToF32(0xbefffffe)]] }, // [~-0.5, ~-0.5, ~-0.5] + { input: [[kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.max, 0.0], [1.0, 1.0, 1.0]], expected: [[hexToF32(0x80fffffe), hexToF32(0x00800001)], [hexToF32(0x80ffffff), hexToF32(0x00000002)], [hexToF32(0x80fffffe), hexToF32(0x00000002)]] }, // [~0.0, ~0.0, ~0.0] + + // vec4s + { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [[-1.0], [0.0], [0.0], [0.0]] }, + { input: [[0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [[0.0], [1.0], [0.0], [0.0]] }, + { input: [[0.0, 0.0, 1.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [[0.0], [0.0], [1.0], [0.0]] }, + { input: [[0.0, 0.0, 0.0, 1.0], [1.0, 0.0, 0.0, 0.0]], expected: [[0.0], [0.0], [0.0], [1.0]] }, + { input: [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]], expected: [[1.0], [0.0], [0.0], [0.0]] }, + { input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: [[1.0], [0.0], [0.0], [0.0]] }, + { input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]], expected: [[1.0], [0.0], [0.0], [0.0]] }, + { input: [[-1.0, -1.0, -1.0, -1.0], [1.0, 1.0, 1.0, 1.0]], expected: [[7.0], [7.0], [7.0], [7.0]] }, + { input: [[0.1, 0.1, 0.1, 0.1], [1.0, 1.0, 1.0, 1.0]], expected: [[hexToF32(0xbf333335), hexToF32(0xbf333332)], [hexToF32(0xbf333335), hexToF32(0xbf333332)], [hexToF32(0xbf333335), hexToF32(0xbf333332)], [hexToF32(0xbf333335), hexToF32(0xbf333332)]] }, // [~-0.7, ~-0.7, ~-0.7, ~-0.7] + { input: [[kValue.f32.subnormal.positive.max, kValue.f32.subnormal.negative.max, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0]], expected: [[hexToF32(0x80fffffe), hexToF32(0x00800001)], [hexToF32(0x80ffffff), hexToF32(0x00000002)], [hexToF32(0x80fffffe), hexToF32(0x00000002)], [hexToF32(0x80fffffe), hexToF32(0x00000002)]] }, // [~0.0, ~0.0, ~0.0, ~0.0] + + // Test that dot going OOB bounds in the intermediate calculations propagates + { input: [[kValue.f32.positive.nearest_max, kValue.f32.positive.max, kValue.f32.negative.min], [1.0, 1.0, 1.0]], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.positive.nearest_max, kValue.f32.negative.min, kValue.f32.positive.max], [1.0, 1.0, 1.0]], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.positive.max, kValue.f32.positive.nearest_max, kValue.f32.negative.min], [1.0, 1.0, 1.0]], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.negative.min, kValue.f32.positive.nearest_max, kValue.f32.positive.max], [1.0, 1.0, 1.0]], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.positive.max, kValue.f32.negative.min, kValue.f32.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.negative.min, kValue.f32.positive.max, kValue.f32.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kAny, kAny, kAny] }, + + // Test that post-dot going OOB propagates + { input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f32.positive.max, -2.0, -3.0]], expected: [kAny, kAny, kAny, kAny] }, + ] + ) + .fn(t => { + const [x, y] = t.params.input; + const expected = toF32Vector(t.params.expected); + + const got = reflectInterval(x, y); + t.expect( + objectEquals(expected, got), + `reflectInterval([${x}], [${y}]) returned ${got}. Expected ${expected}` + ); + }); + +interface FaceForwardCase { + input: [number[], number[], number[]]; + expected: (IntervalBounds[] | undefined)[]; +} + +g.test('faceForwardIntervals') + .paramsSubcasesOnly( + // prettier-ignore + [ + // vec2 + { input: [[1.0, 0.0], [1.0, 0.0], [1.0, 0.0]], expected: [[[-1.0], [0.0]]] }, + { input: [[-1.0, 0.0], [1.0, 0.0], [1.0, 0.0]], expected: [[[1.0], [0.0]]] }, + { input: [[1.0, 0.0], [-1.0, 1.0], [1.0, -1.0]], expected: [[[1.0], [0.0]]] }, + { input: [[-1.0, 0.0], [-1.0, 1.0], [1.0, -1.0]], expected: [[[-1.0], [0.0]]] }, + { input: [[10.0, 0.0], [10.0, 0.0], [10.0, 0.0]], expected: [[[-10.0], [0.0]]] }, + { input: [[-10.0, 0.0], [10.0, 0.0], [10.0, 0.0]], expected: [[[10.0], [0.0]]] }, + { input: [[10.0, 0.0], [-10.0, 10.0], [10.0, -10.0]], expected: [[[10.0], [0.0]]] }, + { input: [[-10.0, 0.0], [-10.0, 10.0], [10.0, -10.0]], expected: [[[-10.0], [0.0]]] }, + { input: [[0.1, 0.0], [0.1, 0.0], [0.1, 0.0]], expected: [[[hexToF32(0xbdcccccd), hexToF32(0xbdcccccc)], [0.0]]] }, + { input: [[-0.1, 0.0], [0.1, 0.0], [0.1, 0.0]], expected: [[[hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)], [0.0]]] }, + { input: [[0.1, 0.0], [-0.1, 0.1], [0.1, -0.1]], expected: [[[hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)], [0.0]]] }, + { input: [[-0.1, 0.0], [-0.1, 0.1], [0.1, -0.1]], expected: [[[hexToF32(0xbdcccccd), hexToF32(0xbdcccccc)], [0.0]]] }, + + // vec3 + { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [[[-1.0], [0.0], [0.0]]] }, + { input: [[-1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [[[1.0], [0.0], [0.0]]] }, + { input: [[1.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [1.0, -1.0, 0.0]], expected: [[[1.0], [0.0], [0.0]]] }, + { input: [[-1.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [1.0, -1.0, 0.0]], expected: [[[-1.0], [0.0], [0.0]]] }, + { input: [[10.0, 0.0, 0.0], [10.0, 0.0, 0.0], [10.0, 0.0, 0.0]], expected: [[[-10.0], [0.0], [0.0]]] }, + { input: [[-10.0, 0.0, 0.0], [10.0, 0.0, 0.0], [10.0, 0.0, 0.0]], expected: [[[10.0], [0.0], [0.0]]] }, + { input: [[10.0, 0.0, 0.0], [-10.0, 10.0, 0.0], [10.0, -10.0, 0.0]], expected: [[[10.0], [0.0], [0.0]]] }, + { input: [[-10.0, 0.0, 0.0], [-10.0, 10.0, 0.0], [10.0, -10.0, 0.0]], expected: [[[-10.0], [0.0], [0.0]]] }, + { input: [[0.1, 0.0, 0.0], [0.1, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: [[[hexToF32(0xbdcccccd), hexToF32(0xbdcccccc)], [0.0], [0.0]]] }, + { input: [[-0.1, 0.0, 0.0], [0.1, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: [[[hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)], [0.0], [0.0]]] }, + { input: [[0.1, 0.0, 0.0], [-0.1, 0.0, 0.0], [0.1, -0.0, 0.0]], expected: [[[hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)], [0.0], [0.0]]] }, + { input: [[-0.1, 0.0, 0.0], [-0.1, 0.0, 0.0], [0.1, -0.0, 0.0]], expected: [[[hexToF32(0xbdcccccd), hexToF32(0xbdcccccc)], [0.0], [0.0]]] }, + + // vec4 + { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [[[-1.0], [0.0], [0.0], [0.0]]] }, + { input: [[-1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [[[1.0], [0.0], [0.0], [0.0]]] }, + { input: [[1.0, 0.0, 0.0, 0.0], [-1.0, 1.0, 0.0, 0.0], [1.0, -1.0, 0.0, 0.0]], expected: [[[1.0], [0.0], [0.0], [0.0]]] }, + { input: [[-1.0, 0.0, 0.0, 0.0], [-1.0, 1.0, 0.0, 0.0], [1.0, -1.0, 0.0, 0.0]], expected: [[[-1.0], [0.0], [0.0], [0.0]]] }, + { input: [[10.0, 0.0, 0.0, 0.0], [10.0, 0.0, 0.0, 0.0], [10.0, 0.0, 0.0, 0.0]], expected: [[[-10.0], [0.0], [0.0], [0.0]]] }, + { input: [[-10.0, 0.0, 0.0, 0.0], [10.0, 0.0, 0.0, 0.0], [10.0, 0.0, 0.0, 0.0]], expected: [[[10.0], [0.0], [0.0], [0.0]]] }, + { input: [[10.0, 0.0, 0.0, 0.0], [-10.0, 10.0, 0.0, 0.0], [10.0, -10.0, 0.0, 0.0]], expected: [[[10.0], [0.0], [0.0], [0.0]]] }, + { input: [[-10.0, 0.0, 0.0, 0.0], [-10.0, 10.0, 0.0, 0.0], [10.0, -10.0, 0.0, 0.0]], expected: [[[-10.0], [0.0], [0.0], [0.0]]] }, + { input: [[0.1, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0]], expected: [[[hexToF32(0xbdcccccd), hexToF32(0xbdcccccc)], [0.0], [0.0], [0.0]]] }, + { input: [[-0.1, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0]], expected: [[[hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)], [0.0], [0.0], [0.0]]] }, + { input: [[0.1, 0.0, 0.0, 0.0], [-0.1, 0.0, 0.0, 0.0], [0.1, -0.0, 0.0, 0.0]], expected: [[[hexToF32(0x3dcccccc), hexToF32(0x3dcccccd)], [0.0], [0.0], [0.0]]] }, + { input: [[-0.1, 0.0, 0.0, 0.0], [-0.1, 0.0, 0.0, 0.0], [0.1, -0.0, 0.0, 0.0]], expected: [[[hexToF32(0xbdcccccd), hexToF32(0xbdcccccc)], [0.0], [0.0], [0.0]]] }, + + // dot(y, z) === 0 + { input: [[1.0, 1.0], [1.0, 0.0], [0.0, 1.0]], expected: [[[-1.0], [-1.0]]]}, + + // subnormals, also dot(y, z) spans 0 + { input: [[kValue.f32.subnormal.positive.max, 0.0], [kValue.f32.subnormal.positive.min, 0.0], [kValue.f32.subnormal.negative.min, 0.0]], expected: [[[0.0, kValue.f32.subnormal.positive.max], [0.0]], [[kValue.f32.subnormal.negative.min, 0], [0.0]]] }, + + // dot going OOB returns [undefined, x, -x] + { input: [[1.0, 1.0], [kValue.f32.positive.max, kValue.f32.positive.max], [kValue.f32.positive.max, kValue.f32.positive.max]], expected: [undefined, [[1], [1]], [[-1], [-1]]] }, + + ] + ) + .fn(t => { + const [x, y, z] = t.params.input; + const expected = t.params.expected.map(e => (e !== undefined ? toF32Vector(e) : undefined)); + + const got = faceForwardIntervals(x, y, z); + t.expect( + objectEquals(expected, got), + `faceForwardInterval([${x}], [${y}], [${z}]) returned [${got}]. Expected [${expected}]` + ); + }); + +interface RefractCase { + input: [number[], number[], number]; + expected: IntervalBounds[]; +} + +// Scope for refractInterval tests so that they can have constants for magic +// numbers that don't pollute the global namespace or have unwieldy long names. +{ + const kNegativeOneBounds: IntervalBounds = [ + hexToF64(0xbff00000, 0xc0000000), + hexToF64(0xbfefffff, 0x40000000), + ]; + + g.test('refractInterval') + .paramsSubcasesOnly( + // Some of these are hard coded, since the error intervals are difficult + // to express in a closed human readable form due to the inherited nature + // of the errors. + + // prettier-ignore + [ + // k < 0 + { input: [[1, 1], [0.1, 0], 10], expected: [[0], [0]] }, + + // k contains 0 + { input: [[1, 1], [0.1, 0], 1.005038], expected: [kAny, kAny] }, + + // k > 0 + // vec2 + { input: [[1, 1], [1, 0], 1], expected: [kNegativeOneBounds, [1]] }, + { input: [[1, -2], [3, 4], 5], expected: [[hexToF32(0x40ce87a4), hexToF32(0x40ce8840)], // ~6.454... + [hexToF32(0xc100fae8), hexToF32(0xc100fa80)]] }, // ~-8.061... + + // vec3 + { input: [[1, 1, 1], [1, 0, 0], 1], expected: [kNegativeOneBounds, [1], [1]] }, + { input: [[1, -2, 3], [-4, 5, -6], 7], expected: [[hexToF32(0x40d24480), hexToF32(0x40d24c00)], // ~6.571... + [hexToF32(0xc1576f80), hexToF32(0xc1576ad0)], // ~-13.464... + [hexToF32(0x41a2d9b0), hexToF32(0x41a2dc80)]] }, // ~20.356... + + // vec4 + { input: [[1, 1, 1, 1], [1, 0, 0, 0], 1], expected: [kNegativeOneBounds, [1], [1], [1]] }, + { input: [[1, -2, 3,-4], [-5, 6, -7, 8], 9], expected: [[hexToF32(0x410ae480), hexToF32(0x410af240)], // ~8.680... + [hexToF32(0xc18cf7c0), hexToF32(0xc18cef80)], // ~-17.620... + [hexToF32(0x41d46cc0), hexToF32(0x41d47660)], // ~26.553... + [hexToF32(0xc20dfa80), hexToF32(0xc20df500)]] }, // ~-35.494... + + // Test that dot going OOB bounds in the intermediate calculations propagates + { input: [[kValue.f32.positive.nearest_max, kValue.f32.positive.max, kValue.f32.negative.min], [1.0, 1.0, 1.0], 1], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.positive.nearest_max, kValue.f32.negative.min, kValue.f32.positive.max], [1.0, 1.0, 1.0], 1], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.positive.max, kValue.f32.positive.nearest_max, kValue.f32.negative.min], [1.0, 1.0, 1.0], 1], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.negative.min, kValue.f32.positive.nearest_max, kValue.f32.positive.max], [1.0, 1.0, 1.0], 1], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.positive.max, kValue.f32.negative.min, kValue.f32.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kAny, kAny, kAny] }, + { input: [[kValue.f32.negative.min, kValue.f32.positive.max, kValue.f32.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kAny, kAny, kAny] }, + ] + ) + .fn(t => { + const [i, s, r] = t.params.input; + const expected = toF32Vector(t.params.expected); + + const got = refractInterval(i, s, r); + t.expect( + objectEquals(expected, got), + `refractIntervals([${i}], [${s}], ${r}) returned [${got}]. Expected [${expected}]` + ); + }); +} + +interface ModfCase { + input: number; + fract: IntervalBounds; + whole: IntervalBounds; +} + +g.test('modfInterval') + .paramsSubcasesOnly( + // prettier-ignore + [ + // Normals + { input: 0, fract: [0], whole: [0] }, + { input: 1, fract: [0], whole: [1] }, + { input: -1, fract: [0], whole: [-1] }, + { input: 0.5, fract: [0.5], whole: [0] }, + { input: -0.5, fract: [-0.5], whole: [0] }, + { input: 2.5, fract: [0.5], whole: [2] }, + { input: -2.5, fract: [-0.5], whole: [-2] }, + { input: 10.0, fract: [0], whole: [10] }, + { input: -10.0, fract: [0], whole: [-10] }, + + // Subnormals + { input: kValue.f32.subnormal.negative.min, fract: [kValue.f32.subnormal.negative.min, 0], whole: [0] }, + { input: kValue.f32.subnormal.negative.max, fract: [kValue.f32.subnormal.negative.max, 0], whole: [0] }, + { input: kValue.f32.subnormal.positive.min, fract: [0, kValue.f32.subnormal.positive.min], whole: [0] }, + { input: kValue.f32.subnormal.positive.max, fract: [0, kValue.f32.subnormal.positive.max], whole: [0] }, + + // Boundaries + { input: kValue.f32.negative.min, fract: [0], whole: [kValue.f32.negative.min] }, + { input: kValue.f32.negative.max, fract: [kValue.f32.negative.max], whole: [0] }, + { input: kValue.f32.positive.min, fract: [kValue.f32.positive.min], whole: [0] }, + { input: kValue.f32.positive.max, fract: [0], whole: [kValue.f32.positive.max] }, + ] + ) + .fn(t => { + const expected = { + fract: toF32Interval(t.params.fract), + whole: toF32Interval(t.params.whole), + }; + + const got = modfInterval(t.params.input); + t.expect( + objectEquals(expected, got), + `modfInterval([${t.params.input}) returned { fract: [${got.fract}], whole: [${got.whole}] }. Expected { fract: [${expected.fract}], whole: [${expected.whole}] }` + ); + }); -- cgit v1.2.3