tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

floating_point.spec.ts (377034B)


      1 export const description = `
      2 Floating Point unit tests.
      3 `;
      4 
      5 import { makeTestGroup } from '../common/framework/test_group.js';
      6 import { objectEquals, unreachable } from '../common/util/util.js';
      7 import { kValue } from '../webgpu/util/constants.js';
      8 import {
      9  FP,
     10  FPInterval,
     11  FPIntervalParam,
     12  IntervalEndpoints,
     13 } from '../webgpu/util/floating_point.js';
     14 import { map2DArray, oneULPF32, oneULPF16, oneULPF64 } from '../webgpu/util/math.js';
     15 import {
     16  reinterpretU16AsF16,
     17  reinterpretU32AsF32,
     18  reinterpretU64AsF64,
     19 } from '../webgpu/util/reinterpret.js';
     20 
     21 import { UnitTest } from './unit_test.js';
     22 
     23 export const g = makeTestGroup(UnitTest);
     24 
     25 const kFPTraitForULP = {
     26  f32: 'f32',
     27  f16: 'f16',
     28 } as const;
     29 
     30 /** Endpoints indicating an expectation of unbounded error */
     31 const kUnboundedEndpoints: IntervalEndpoints = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
     32 
     33 /** Interval from kUnboundedEndpoints */
     34 const kUnboundedInterval = {
     35  f32: FP.f32.toParam(kUnboundedEndpoints),
     36  f16: FP.f16.toParam(kUnboundedEndpoints),
     37  abstract: FP.abstract.toParam(kUnboundedEndpoints),
     38 };
     39 
     40 /** @returns a number N * ULP greater than the provided number */
     41 const kPlusNULPFunctions = {
     42  f32: (x: number, n: number) => {
     43    return x + n * oneULPF32(x);
     44  },
     45  f16: (x: number, n: number) => {
     46    return x + n * oneULPF16(x);
     47  },
     48  abstract: (x: number, n: number) => {
     49    return x + n * oneULPF64(x);
     50  },
     51 };
     52 
     53 /** @returns a number one ULP greater than the provided number */
     54 const kPlusOneULPFunctions = {
     55  f32: (x: number): number => {
     56    return kPlusNULPFunctions['f32'](x, 1);
     57  },
     58  f16: (x: number): number => {
     59    return kPlusNULPFunctions['f16'](x, 1);
     60  },
     61  abstract: (x: number): number => {
     62    return kPlusNULPFunctions['abstract'](x, 1);
     63  },
     64 };
     65 
     66 /** @returns a number N * ULP less than the provided number */
     67 const kMinusNULPFunctions = {
     68  f32: (x: number, n: number) => {
     69    return x - n * oneULPF32(x);
     70  },
     71  f16: (x: number, n: number) => {
     72    return x - n * oneULPF16(x);
     73  },
     74  abstract: (x: number, n: number) => {
     75    return x - n * oneULPF64(x);
     76  },
     77 };
     78 
     79 /** @returns a number one ULP less than the provided number */
     80 const kMinusOneULPFunctions = {
     81  f32: (x: number): number => {
     82    return kMinusNULPFunctions['f32'](x, 1);
     83  },
     84  f16: (x: number): number => {
     85    return kMinusNULPFunctions['f16'](x, 1);
     86  },
     87  abstract: (x: number): number => {
     88    return kMinusNULPFunctions['abstract'](x, 1);
     89  },
     90 };
     91 
     92 /** @returns the expected IntervalEndpoints adjusted by the given error function
     93 *
     94 * @param expected the endpoints to be adjusted
     95 * @param error error function to adjust the endpoints via
     96 */
     97 function applyError(
     98  expected: number | IntervalEndpoints,
     99  error: (n: number) => number
    100 ): IntervalEndpoints {
    101  // Avoiding going through FPInterval to avoid tying this to a specific kind
    102  const unpack = (n: number | IntervalEndpoints): [number, number] => {
    103    if (expected instanceof Array) {
    104      switch (expected.length) {
    105        case 1:
    106          return [expected[0], expected[0]];
    107        case 2:
    108          return [expected[0], expected[1]];
    109      }
    110      unreachable(`Tried to unpack an IntervalEndpoints with length other than 1 or 2`);
    111    } else {
    112      // TS doesn't narrow this to number automatically
    113      return [n as number, n as number];
    114    }
    115  };
    116 
    117  let [begin, end] = unpack(expected);
    118 
    119  begin -= error(begin);
    120  end += error(end);
    121 
    122  if (begin === end) {
    123    return [begin];
    124  }
    125  return [begin, end];
    126 }
    127 
    128 // FPInterval
    129 
    130 interface ConstructorCase {
    131  input: IntervalEndpoints;
    132  expected: IntervalEndpoints;
    133 }
    134 
    135 g.test('constructor')
    136  .params(u =>
    137    u
    138      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    139      .beginSubcases()
    140      .expandWithParams<ConstructorCase>(p => {
    141        const constants = FP[p.trait].constants();
    142        // prettier-ignore
    143        const cases: ConstructorCase[] = [
    144          // Common cases
    145          { input: [0, 10], expected: [0, 10] },
    146          { input: [-5, 0], expected: [-5, 0] },
    147          { input: [-5, 10], expected: [-5, 10] },
    148          { input: [0], expected: [0] },
    149          { input: [10], expected: [10] },
    150          { input: [-5], expected: [-5] },
    151          { input: [2.5], expected: [2.5] },
    152          { input: [-1.375], expected: [-1.375] },
    153          { input: [-1.375, 2.5], expected: [-1.375, 2.5] },
    154 
    155          // Edges
    156          { input: [0, constants.positive.max], expected: [0, constants.positive.max] },
    157          { input: [constants.negative.min, 0], expected: [constants.negative.min, 0] },
    158          { input: [constants.negative.min, constants.positive.max], expected: [constants.negative.min, constants.positive.max] },
    159 
    160          // Infinities
    161          { input: [0, constants.positive.infinity], expected: [0, Number.POSITIVE_INFINITY] },
    162          { input: [constants.negative.infinity, 0], expected: [Number.NEGATIVE_INFINITY, 0] },
    163          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
    164        ];
    165 
    166        // Note: Out of range values are limited to infinities for abstract float, due to abstract
    167        // float and 'number' both being f64. So there are no separate OOR tests for abstract float,
    168        // otherwise the testing framework will consider them duplicated.
    169        if (p.trait !== 'abstract') {
    170          // prettier-ignore
    171          cases.push(...[
    172            // Out of range
    173            { input: [0, 2 * constants.positive.max], expected: [0, 2 * constants.positive.max] },
    174            { input: [2 * constants.negative.min, 0], expected: [2 * constants.negative.min, 0] },
    175            { input: [2 * constants.negative.min, 2 * constants.positive.max], expected: [2 * constants.negative.min, 2 * constants.positive.max] },
    176          ] as ConstructorCase[]);
    177        }
    178 
    179        return cases;
    180      })
    181  )
    182  .fn(t => {
    183    const i = new FPInterval(t.params.trait, ...t.params.input);
    184    t.expect(
    185      objectEquals(i.endpoints(), t.params.expected),
    186      `new FPInterval('${t.params.trait}', [${t.params.input}]) returned ${i}. Expected [${t.params.expected}]`
    187    );
    188  });
    189 
    190 interface ContainsNumberCase {
    191  endpoints: number | IntervalEndpoints;
    192  value: number;
    193  expected: boolean;
    194 }
    195 
    196 g.test('contains_number')
    197  .params(u =>
    198    u
    199      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    200      .beginSubcases()
    201      .expandWithParams<ContainsNumberCase>(p => {
    202        const constants = FP[p.trait].constants();
    203        // prettier-ignore
    204        const cases: ContainsNumberCase[] = [
    205          // Common usage
    206          { endpoints: [0, 10], value: 0, expected: true },
    207          { endpoints: [0, 10], value: 10, expected: true },
    208          { endpoints: [0, 10], value: 5, expected: true },
    209          { endpoints: [0, 10], value: -5, expected: false },
    210          { endpoints: [0, 10], value: 50, expected: false },
    211          { endpoints: [0, 10], value: Number.NaN, expected: false },
    212          { endpoints: [-5, 10], value: 0, expected: true },
    213          { endpoints: [-5, 10], value: 10, expected: true },
    214          { endpoints: [-5, 10], value: 5, expected: true },
    215          { endpoints: [-5, 10], value: -5, expected: true },
    216          { endpoints: [-5, 10], value: -6, expected: false },
    217          { endpoints: [-5, 10], value: 50, expected: false },
    218          { endpoints: [-5, 10], value: -10, expected: false },
    219          { endpoints: [-1.375, 2.5], value: -10, expected: false },
    220          { endpoints: [-1.375, 2.5], value: 0.5, expected: true },
    221          { endpoints: [-1.375, 2.5], value: 10, expected: false },
    222 
    223          // Point
    224          { endpoints: 0, value: 0, expected: true },
    225          { endpoints: 0, value: 10, expected: false },
    226          { endpoints: 0, value: -1000, expected: false },
    227          { endpoints: 10, value: 10, expected: true },
    228          { endpoints: 10, value: 0, expected: false },
    229          { endpoints: 10, value: -10, expected: false },
    230          { endpoints: 10, value: 11, expected: false },
    231 
    232          // Upper infinity
    233          { endpoints: [0, constants.positive.infinity], value: constants.positive.min, expected: true },
    234          { endpoints: [0, constants.positive.infinity], value: constants.positive.max, expected: true },
    235          { endpoints: [0, constants.positive.infinity], value: constants.positive.infinity, expected: true },
    236          { endpoints: [0, constants.positive.infinity], value: constants.negative.min, expected: false },
    237          { endpoints: [0, constants.positive.infinity], value: constants.negative.max, expected: false },
    238          { endpoints: [0, constants.positive.infinity], value: constants.negative.infinity, expected: false },
    239 
    240          // Lower infinity
    241          { endpoints: [constants.negative.infinity, 0], value: constants.positive.min, expected: false },
    242          { endpoints: [constants.negative.infinity, 0], value: constants.positive.max, expected: false },
    243          { endpoints: [constants.negative.infinity, 0], value: constants.positive.infinity, expected: false },
    244          { endpoints: [constants.negative.infinity, 0], value: constants.negative.min, expected: true },
    245          { endpoints: [constants.negative.infinity, 0], value: constants.negative.max, expected: true },
    246          { endpoints: [constants.negative.infinity, 0], value: constants.negative.infinity, expected: true },
    247 
    248          // Full infinity
    249          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.min, expected: true },
    250          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.max, expected: true },
    251          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.infinity, expected: true },
    252          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.min, expected: true },
    253          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.max, expected: true },
    254          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.infinity, expected: true },
    255          { endpoints: [constants.negative.infinity, constants.positive.infinity], value: Number.NaN, expected: true },
    256 
    257          // Maximum f32 boundary
    258          { endpoints: [0, constants.positive.max], value: constants.positive.min, expected: true },
    259          { endpoints: [0, constants.positive.max], value: constants.positive.max, expected: true },
    260          { endpoints: [0, constants.positive.max], value: constants.positive.infinity, expected: false },
    261          { endpoints: [0, constants.positive.max], value: constants.negative.min, expected: false },
    262          { endpoints: [0, constants.positive.max], value: constants.negative.max, expected: false },
    263          { endpoints: [0, constants.positive.max], value: constants.negative.infinity, expected: false },
    264 
    265          // Minimum f32 boundary
    266          { endpoints: [constants.negative.min, 0], value: constants.positive.min, expected: false },
    267          { endpoints: [constants.negative.min, 0], value: constants.positive.max, expected: false },
    268          { endpoints: [constants.negative.min, 0], value: constants.positive.infinity, expected: false },
    269          { endpoints: [constants.negative.min, 0], value: constants.negative.min, expected: true },
    270          { endpoints: [constants.negative.min, 0], value: constants.negative.max, expected: true },
    271          { endpoints: [constants.negative.min, 0], value: constants.negative.infinity, expected: false },
    272 
    273          // Subnormals
    274          { endpoints: [0, constants.positive.min], value: constants.positive.subnormal.min, expected: true },
    275          { endpoints: [0, constants.positive.min], value: constants.positive.subnormal.max, expected: true },
    276          { endpoints: [0, constants.positive.min], value: constants.negative.subnormal.min, expected: false },
    277          { endpoints: [0, constants.positive.min], value: constants.negative.subnormal.max, expected: false },
    278          { endpoints: [constants.negative.max, 0], value: constants.positive.subnormal.min, expected: false },
    279          { endpoints: [constants.negative.max, 0], value: constants.positive.subnormal.max, expected: false },
    280          { endpoints: [constants.negative.max, 0], value: constants.negative.subnormal.min, expected: true },
    281          { endpoints: [constants.negative.max, 0], value: constants.negative.subnormal.max, expected: true },
    282          { endpoints: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.min, expected: true },
    283          { endpoints: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.max, expected: false },
    284          { endpoints: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.min, expected: false },
    285          { endpoints: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.max, expected: false },
    286          { endpoints: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.min, expected: false },
    287          { endpoints: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.max, expected: false },
    288          { endpoints: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.min, expected: false },
    289          { endpoints: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.max, expected: true },
    290        ];
    291 
    292        // Note: Out of range values are limited to infinities for abstract float, due to abstract
    293        // float and 'number' both being f64. So there are no separate OOR tests for abstract float,
    294        // otherwise the testing framework will consider them duplicated.
    295        if (p.trait !== 'abstract') {
    296          // prettier-ignore
    297          cases.push(...[
    298            // Out of range high
    299            { endpoints: [0, 2 * constants.positive.max], value: constants.positive.min, expected: true },
    300            { endpoints: [0, 2 * constants.positive.max], value: constants.positive.max, expected: true },
    301            { endpoints: [0, 2 * constants.positive.max], value: constants.positive.infinity, expected: false },
    302            { endpoints: [0, 2 * constants.positive.max], value: constants.negative.min, expected: false },
    303            { endpoints: [0, 2 * constants.positive.max], value: constants.negative.max, expected: false },
    304            { endpoints: [0, 2 * constants.positive.max], value: constants.negative.infinity, expected: false },
    305 
    306            // Out of range low
    307            { endpoints: [2 * constants.negative.min, 0], value: constants.positive.min, expected: false },
    308            { endpoints: [2 * constants.negative.min, 0], value: constants.positive.max, expected: false },
    309            { endpoints: [2 * constants.negative.min, 0], value: constants.positive.infinity, expected: false },
    310            { endpoints: [2 * constants.negative.min, 0], value: constants.negative.min, expected: true },
    311            { endpoints: [2 * constants.negative.min, 0], value: constants.negative.max, expected: true },
    312            { endpoints: [2 * constants.negative.min, 0], value: constants.negative.infinity, expected: false },
    313          ] as ContainsNumberCase[]);
    314        }
    315 
    316        return cases;
    317      })
    318  )
    319  .fn(t => {
    320    const trait = FP[t.params.trait];
    321    const i = trait.toInterval(t.params.endpoints);
    322    const value = t.params.value;
    323    const expected = t.params.expected;
    324 
    325    const got = i.contains(value);
    326    t.expect(expected === got, `${i}.contains(${value}) returned ${got}. Expected ${expected}`);
    327  });
    328 
    329 interface ContainsIntervalCase {
    330  lhs: number | IntervalEndpoints;
    331  rhs: number | IntervalEndpoints;
    332  expected: boolean;
    333 }
    334 
    335 g.test('contains_interval')
    336  .params(u =>
    337    u
    338      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    339      .beginSubcases()
    340      .expandWithParams<ContainsIntervalCase>(p => {
    341        const constants = FP[p.trait].constants();
    342        // prettier-ignore
    343        const cases: ContainsIntervalCase[] = [
    344          // Common usage
    345          { lhs: [-10, 10], rhs: 0, expected: true },
    346          { lhs: [-10, 10], rhs: [-1, 0], expected: true },
    347          { lhs: [-10, 10], rhs: [0, 2], expected: true },
    348          { lhs: [-10, 10], rhs: [-1, 2], expected: true },
    349          { lhs: [-10, 10], rhs: [0, 10], expected: true },
    350          { lhs: [-10, 10], rhs: [-10, 2], expected: true },
    351          { lhs: [-10, 10], rhs: [-10, 10], expected: true },
    352          { lhs: [-10, 10], rhs: [-100, 10], expected: false },
    353 
    354          // Upper infinity
    355          { lhs: [0, constants.positive.infinity], rhs: 0, expected: true },
    356          { lhs: [0, constants.positive.infinity], rhs: [-1, 0], expected: false },
    357          { lhs: [0, constants.positive.infinity], rhs: [0, 1], expected: true },
    358          { lhs: [0, constants.positive.infinity], rhs: [0, constants.positive.max], expected: true },
    359          { lhs: [0, constants.positive.infinity], rhs: [0, constants.positive.infinity], expected: true },
    360          { lhs: [0, constants.positive.infinity], rhs: [100, constants.positive.infinity], expected: true },
    361          { lhs: [0, constants.positive.infinity], rhs: [Number.NEGATIVE_INFINITY, constants.positive.infinity], expected: false },
    362 
    363          // Lower infinity
    364          { lhs: [constants.negative.infinity, 0], rhs: 0, expected: true },
    365          { lhs: [constants.negative.infinity, 0], rhs: [-1, 0], expected: true },
    366          { lhs: [constants.negative.infinity, 0], rhs: [constants.negative.min, 0], expected: true },
    367          { lhs: [constants.negative.infinity, 0], rhs: [0, 1], expected: false },
    368          { lhs: [constants.negative.infinity, 0], rhs: [constants.negative.infinity, 0], expected: true },
    369          { lhs: [constants.negative.infinity, 0], rhs: [constants.negative.infinity, -100 ], expected: true },
    370          { lhs: [constants.negative.infinity, 0], rhs: [constants.negative.infinity, constants.positive.infinity], expected: false },
    371 
    372          // Full infinity
    373          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: 0, expected: true },
    374          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [-1, 0], expected: true },
    375          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [0, 1], expected: true },
    376          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [0, constants.positive.infinity], expected: true },
    377          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [100, constants.positive.infinity], expected: true },
    378          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [constants.negative.infinity, 0], expected: true },
    379          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [constants.negative.infinity, -100 ], expected: true },
    380          { lhs: [constants.negative.infinity, constants.positive.infinity], rhs: [constants.negative.infinity, constants.positive.infinity], expected: true },
    381 
    382          // Maximum boundary
    383          { lhs: [0, constants.positive.max], rhs: 0, expected: true },
    384          { lhs: [0, constants.positive.max], rhs: [-1, 0], expected: false },
    385          { lhs: [0, constants.positive.max], rhs: [0, 1], expected: true },
    386          { lhs: [0, constants.positive.max], rhs: [0, constants.positive.max], expected: true },
    387          { lhs: [0, constants.positive.max], rhs: [0, constants.positive.infinity], expected: false },
    388          { lhs: [0, constants.positive.max], rhs: [100, constants.positive.infinity], expected: false },
    389          { lhs: [0, constants.positive.max], rhs: [constants.negative.infinity, constants.positive.infinity], expected: false },
    390 
    391          // Minimum boundary
    392          { lhs: [constants.negative.min, 0], rhs: [0, 0], expected: true },
    393          { lhs: [constants.negative.min, 0], rhs: [-1, 0], expected: true },
    394          { lhs: [constants.negative.min, 0], rhs: [constants.negative.min, 0], expected: true },
    395          { lhs: [constants.negative.min, 0], rhs: [0, 1], expected: false },
    396          { lhs: [constants.negative.min, 0], rhs: [constants.negative.infinity, 0], expected: false },
    397          { lhs: [constants.negative.min, 0], rhs: [constants.negative.infinity, -100 ], expected: false },
    398          { lhs: [constants.negative.min, 0], rhs: [constants.negative.infinity, constants.positive.infinity], expected: false },
    399        ];
    400 
    401        // Note: Out of range values are limited to infinities for abstract float, due to abstract
    402        // float and 'number' both being f64. So there are no separate OOR tests for abstract float,
    403        // otherwise the testing framework will consider them duplicated.
    404        if (p.trait !== 'abstract') {
    405          // prettier-ignore
    406          cases.push(...[
    407            // Out of range high
    408            { lhs: [0, 2 * constants.positive.max], rhs: 0, expected: true },
    409            { lhs: [0, 2 * constants.positive.max], rhs: [-1, 0], expected: false },
    410            { lhs: [0, 2 * constants.positive.max], rhs: [0, 1], expected: true },
    411            { lhs: [0, 2 * constants.positive.max], rhs: [0, constants.positive.max], expected: true },
    412            { lhs: [0, 2 * constants.positive.max], rhs: [0, constants.positive.infinity], expected: false },
    413            { lhs: [0, 2 * constants.positive.max], rhs: [100, constants.positive.infinity], expected: false },
    414            { lhs: [0, 2 * constants.positive.max], rhs: [constants.negative.infinity, constants.positive.infinity], expected: false },
    415 
    416            // Out of range low
    417            { lhs: [2 * constants.negative.min, 0], rhs: 0, expected: true },
    418            { lhs: [2 * constants.negative.min, 0], rhs: [-1, 0], expected: true },
    419            { lhs: [2 * constants.negative.min, 0], rhs: [constants.negative.min, 0], expected: true },
    420            { lhs: [2 * constants.negative.min, 0], rhs: [0, 1], expected: false },
    421            { lhs: [2 * constants.negative.min, 0], rhs: [constants.negative.infinity, 0], expected: false },
    422            { lhs: [2 * constants.negative.min, 0], rhs: [constants.negative.infinity, -100 ], expected: false },
    423            { lhs: [2 * constants.negative.min, 0], rhs: [constants.negative.infinity, constants.positive.infinity], expected: false },
    424          ] as ContainsIntervalCase[]);
    425        }
    426 
    427        return cases;
    428      })
    429  )
    430  .fn(t => {
    431    const trait = FP[t.params.trait];
    432    const lhs = trait.toInterval(t.params.lhs);
    433    const rhs = trait.toInterval(t.params.rhs);
    434    const expected = t.params.expected;
    435 
    436    const got = lhs.contains(rhs);
    437    t.expect(expected === got, `${lhs}.contains(${rhs}) returned ${got}. Expected ${expected}`);
    438  });
    439 
    440 // Utilities
    441 
    442 interface SpanIntervalsCase {
    443  intervals: (number | IntervalEndpoints)[];
    444  expected: number | IntervalEndpoints;
    445 }
    446 
    447 g.test('spanIntervals')
    448  .params(u =>
    449    u
    450      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    451      .beginSubcases()
    452      .expandWithParams<SpanIntervalsCase>(p => {
    453        const constants = FP[p.trait].constants();
    454        // prettier-ignore
    455        return [
    456          // Single Intervals
    457          { intervals: [[0, 10]], expected: [0, 10] },
    458          { intervals: [[0, constants.positive.max]], expected: [0, constants.positive.max] },
    459          { intervals: [[0, constants.positive.nearest_max]], expected: [0, constants.positive.nearest_max] },
    460          { intervals: [[0, constants.positive.infinity]], expected: [0, Number.POSITIVE_INFINITY] },
    461          { intervals: [[constants.negative.min, 0]], expected: [constants.negative.min, 0] },
    462          { intervals: [[constants.negative.nearest_min, 0]], expected: [constants.negative.nearest_min, 0] },
    463          { intervals: [[constants.negative.infinity, 0]], expected: [Number.NEGATIVE_INFINITY, 0] },
    464 
    465          // Double Intervals
    466          { intervals: [[0, 1], [2, 5]], expected: [0, 5] },
    467          { intervals: [[2, 5], [0, 1]], expected: [0, 5] },
    468          { intervals: [[0, 2], [1, 5]], expected: [0, 5] },
    469          { intervals: [[0, 5], [1, 2]], expected: [0, 5] },
    470          { intervals: [[constants.negative.infinity, 0], [0, constants.positive.infinity]], expected: kUnboundedEndpoints },
    471 
    472          // Multiple Intervals
    473          { intervals: [[0, 1], [2, 3], [4, 5]], expected: [0, 5] },
    474          { intervals: [[0, 1], [4, 5], [2, 3]], expected: [0, 5] },
    475          { intervals: [[0, 1], [0, 1], [0, 1]], expected: [0, 1] },
    476 
    477          // Point Intervals
    478          { intervals: [1], expected: 1 },
    479          { intervals: [1, 2], expected: [1, 2] },
    480          { intervals: [-10, 2], expected: [-10, 2] },
    481        ];
    482      })
    483  )
    484  .fn(t => {
    485    const trait = FP[t.params.trait];
    486    const intervals = t.params.intervals.map(i => trait.toInterval(i));
    487    const expected = trait.toInterval(t.params.expected);
    488 
    489    const got = trait.spanIntervals(...intervals);
    490    t.expect(
    491      objectEquals(got, expected),
    492      `${t.params.trait}.span({${intervals}}) returned ${got}. Expected ${expected}`
    493    );
    494  });
    495 
    496 interface isVectorCase {
    497  input: (number | IntervalEndpoints | FPIntervalParam)[];
    498  expected: boolean;
    499 }
    500 
    501 g.test('isVector')
    502  .params(u =>
    503    u
    504      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    505      .beginSubcases()
    506      .expandWithParams<isVectorCase>(p => {
    507        const trait = FP[p.trait];
    508        return [
    509          // numbers
    510          { input: [1, 2], expected: false },
    511          { input: [1, 2, 3], expected: false },
    512          { input: [1, 2, 3, 4], expected: false },
    513 
    514          // IntervalEndpoints
    515          { input: [[1], [2]], expected: false },
    516          { input: [[1], [2], [3]], expected: false },
    517          { input: [[1], [2], [3], [4]], expected: false },
    518          {
    519            input: [
    520              [1, 2],
    521              [2, 3],
    522            ],
    523            expected: false,
    524          },
    525          {
    526            input: [
    527              [1, 2],
    528              [2, 3],
    529              [3, 4],
    530            ],
    531            expected: false,
    532          },
    533          {
    534            input: [
    535              [1, 2],
    536              [2, 3],
    537              [3, 4],
    538              [4, 5],
    539            ],
    540            expected: false,
    541          },
    542 
    543          // FPInterval, valid dimensions
    544          { input: [trait.toParam([1]), trait.toParam([2])], expected: true },
    545          { input: [trait.toParam([1, 2]), trait.toParam([2, 3])], expected: true },
    546          {
    547            input: [trait.toParam([1]), trait.toParam([2]), trait.toParam([3])],
    548            expected: true,
    549          },
    550          {
    551            input: [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
    552            expected: true,
    553          },
    554          {
    555            input: [trait.toParam([1]), trait.toParam([2]), trait.toParam([3]), trait.toParam([4])],
    556            expected: true,
    557          },
    558          {
    559            input: [
    560              trait.toParam([1, 2]),
    561              trait.toParam([2, 3]),
    562              trait.toParam([3, 4]),
    563              trait.toParam([4, 5]),
    564            ],
    565            expected: true,
    566          },
    567 
    568          // FPInterval, invalid dimensions
    569          { input: [trait.toParam([1])], expected: false },
    570          {
    571            input: [
    572              trait.toParam([1]),
    573              trait.toParam([2]),
    574              trait.toParam([3]),
    575              trait.toParam([4]),
    576              trait.toParam([5]),
    577            ],
    578            expected: false,
    579          },
    580 
    581          // Mixed
    582          { input: [1, [2]], expected: false },
    583          { input: [1, [2], trait.toParam([3])], expected: false },
    584          { input: [1, trait.toParam([2]), [3], 4], expected: false },
    585          { input: [trait.toParam(1), 2], expected: false },
    586          { input: [trait.toParam(1), [2]], expected: false },
    587        ];
    588      })
    589  )
    590  .fn(t => {
    591    const trait = FP[t.params.trait];
    592    const input = t.params.input.map(e => trait.fromParam(e));
    593    const expected = t.params.expected;
    594 
    595    const got = trait.isVector(input);
    596    t.expect(
    597      got === expected,
    598      `${t.params.trait}.isVector([${input}]) returned ${got}. Expected ${expected}`
    599    );
    600  });
    601 
    602 interface toVectorCase {
    603  input: (number | IntervalEndpoints | FPIntervalParam)[];
    604  expected: (number | IntervalEndpoints)[];
    605 }
    606 
    607 g.test('toVector')
    608  .params(u =>
    609    u
    610      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    611      .beginSubcases()
    612      .expandWithParams<toVectorCase>(p => {
    613        const trait = FP[p.trait];
    614        return [
    615          // numbers
    616          { input: [1, 2], expected: [1, 2] },
    617          { input: [1, 2, 3], expected: [1, 2, 3] },
    618          { input: [1, 2, 3, 4], expected: [1, 2, 3, 4] },
    619 
    620          // IntervalEndpoints
    621          { input: [[1], [2]], expected: [1, 2] },
    622          { input: [[1], [2], [3]], expected: [1, 2, 3] },
    623          { input: [[1], [2], [3], [4]], expected: [1, 2, 3, 4] },
    624          {
    625            input: [
    626              [1, 2],
    627              [2, 3],
    628            ],
    629            expected: [
    630              [1, 2],
    631              [2, 3],
    632            ],
    633          },
    634          {
    635            input: [
    636              [1, 2],
    637              [2, 3],
    638              [3, 4],
    639            ],
    640            expected: [
    641              [1, 2],
    642              [2, 3],
    643              [3, 4],
    644            ],
    645          },
    646          {
    647            input: [
    648              [1, 2],
    649              [2, 3],
    650              [3, 4],
    651              [4, 5],
    652            ],
    653            expected: [
    654              [1, 2],
    655              [2, 3],
    656              [3, 4],
    657              [4, 5],
    658            ],
    659          },
    660 
    661          // FPInterval
    662          { input: [trait.toParam([1]), trait.toParam([2])], expected: [1, 2] },
    663          {
    664            input: [trait.toParam([1, 2]), trait.toParam([2, 3])],
    665            expected: [
    666              [1, 2],
    667              [2, 3],
    668            ],
    669          },
    670          {
    671            input: [trait.toParam([1]), trait.toParam([2]), trait.toParam([3])],
    672            expected: [1, 2, 3],
    673          },
    674          {
    675            input: [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
    676            expected: [
    677              [1, 2],
    678              [2, 3],
    679              [3, 4],
    680            ],
    681          },
    682          {
    683            input: [trait.toParam([1]), trait.toParam([2]), trait.toParam([3]), trait.toParam([4])],
    684            expected: [1, 2, 3, 4],
    685          },
    686          {
    687            input: [
    688              trait.toParam([1, 2]),
    689              trait.toParam([2, 3]),
    690              trait.toParam([3, 4]),
    691              trait.toParam([4, 5]),
    692            ],
    693            expected: [
    694              [1, 2],
    695              [2, 3],
    696              [3, 4],
    697              [4, 5],
    698            ],
    699          },
    700 
    701          // Mixed
    702          { input: [1, [2]], expected: [1, 2] },
    703          { input: [1, [2], trait.toParam([3])], expected: [1, 2, 3] },
    704          { input: [1, trait.toParam([2]), [3], 4], expected: [1, 2, 3, 4] },
    705          {
    706            input: [1, [2], [2, 3], kUnboundedInterval[p.trait]],
    707            expected: [1, 2, [2, 3], kUnboundedEndpoints],
    708          },
    709        ];
    710      })
    711  )
    712  .fn(t => {
    713    const trait = FP[t.params.trait];
    714    const input = t.params.input.map(e => trait.fromParam(e));
    715    const expected = t.params.expected.map(e => trait.toInterval(e));
    716 
    717    const got = trait.toVector(input);
    718    t.expect(
    719      objectEquals(got, expected),
    720      `${t.params.trait}.toVector([${input}]) returned [${got}]. Expected [${expected}]`
    721    );
    722  });
    723 
    724 interface isMatrixCase {
    725  input: (number | IntervalEndpoints | FPIntervalParam)[][];
    726  expected: boolean;
    727 }
    728 
    729 g.test('isMatrix')
    730  .params(u =>
    731    u
    732      .combine('trait', ['f32', 'f16', 'abstract'] as const)
    733      .beginSubcases()
    734      .expandWithParams<isMatrixCase>(p => {
    735        const trait = FP[p.trait];
    736        return [
    737          // numbers
    738          {
    739            input: [
    740              [1, 2],
    741              [3, 4],
    742            ],
    743            expected: false,
    744          },
    745          {
    746            input: [
    747              [1, 2],
    748              [3, 4],
    749              [5, 6],
    750            ],
    751            expected: false,
    752          },
    753          {
    754            input: [
    755              [1, 2],
    756              [3, 4],
    757              [5, 6],
    758              [7, 8],
    759            ],
    760            expected: false,
    761          },
    762          {
    763            input: [
    764              [1, 2, 3],
    765              [4, 5, 6],
    766            ],
    767            expected: false,
    768          },
    769          {
    770            input: [
    771              [1, 2, 3],
    772              [4, 5, 6],
    773              [7, 8, 9],
    774            ],
    775            expected: false,
    776          },
    777          {
    778            input: [
    779              [1, 2, 3],
    780              [4, 5, 6],
    781              [7, 8, 9],
    782              [10, 11, 12],
    783            ],
    784            expected: false,
    785          },
    786          {
    787            input: [
    788              [1, 2, 3, 4],
    789              [5, 6, 7, 8],
    790            ],
    791            expected: false,
    792          },
    793          {
    794            input: [
    795              [1, 2, 3, 4],
    796              [5, 6, 7, 8],
    797              [9, 10, 11, 12],
    798            ],
    799            expected: false,
    800          },
    801          {
    802            input: [
    803              [1, 2, 3, 4],
    804              [5, 6, 7, 8],
    805              [9, 10, 11, 12],
    806              [13, 14, 15, 16],
    807            ],
    808            expected: false,
    809          },
    810 
    811          // IntervalEndpoints
    812          {
    813            input: [
    814              [[1], [2]],
    815              [[3], [4]],
    816            ],
    817            expected: false,
    818          },
    819          {
    820            input: [
    821              [[1], [2]],
    822              [[3], [4]],
    823              [[5], [6]],
    824            ],
    825            expected: false,
    826          },
    827          {
    828            input: [
    829              [[1], [2]],
    830              [[3], [4]],
    831              [[5], [6]],
    832              [[7], [8]],
    833            ],
    834            expected: false,
    835          },
    836          {
    837            input: [
    838              [[1], [2], [3]],
    839              [[4], [5], [6]],
    840            ],
    841            expected: false,
    842          },
    843          {
    844            input: [
    845              [[1], [2], [3]],
    846              [[4], [5], [6]],
    847              [[7], [8], [9]],
    848            ],
    849            expected: false,
    850          },
    851          {
    852            input: [
    853              [[1], [2], [3]],
    854              [[4], [5], [6]],
    855              [[7], [8], [9]],
    856              [[10], [11], [12]],
    857            ],
    858            expected: false,
    859          },
    860          {
    861            input: [
    862              [[1], [2], [3], [4]],
    863              [[5], [6], [7], [8]],
    864            ],
    865            expected: false,
    866          },
    867          {
    868            input: [
    869              [[1], [2], [3], [4]],
    870              [[5], [6], [7], [8]],
    871              [[9], [10], [11], [12]],
    872            ],
    873            expected: false,
    874          },
    875          {
    876            input: [
    877              [[1], [2], [3], [4]],
    878              [[5], [6], [7], [8]],
    879              [[9], [10], [11], [12]],
    880              [[13], [14], [15], [16]],
    881            ],
    882            expected: false,
    883          },
    884 
    885          // FPInterval, valid dimensions
    886          {
    887            input: [
    888              [trait.toParam(1), trait.toParam(2)],
    889              [trait.toParam(3), trait.toParam(4)],
    890            ],
    891            expected: true,
    892          },
    893          {
    894            input: [
    895              [trait.toParam(1), trait.toParam(2)],
    896              [trait.toParam(3), trait.toParam(4)],
    897              [trait.toParam(5), trait.toParam(6)],
    898            ],
    899            expected: true,
    900          },
    901          {
    902            input: [
    903              [trait.toParam(1), trait.toParam(2)],
    904              [trait.toParam(3), trait.toParam(4)],
    905              [trait.toParam(5), trait.toParam(6)],
    906              [trait.toParam(7), trait.toParam(8)],
    907            ],
    908            expected: true,
    909          },
    910          {
    911            input: [
    912              [trait.toParam(1), trait.toParam(2), trait.toParam(3)],
    913              [trait.toParam(4), trait.toParam(5), trait.toParam(6)],
    914            ],
    915            expected: true,
    916          },
    917          {
    918            input: [
    919              [trait.toParam(1), trait.toParam(2), trait.toParam(3)],
    920              [trait.toParam(4), trait.toParam(5), trait.toParam(6)],
    921              [trait.toParam(7), trait.toParam(8), trait.toParam(9)],
    922            ],
    923            expected: true,
    924          },
    925          {
    926            input: [
    927              [trait.toParam(1), trait.toParam(2), trait.toParam(3)],
    928              [trait.toParam(4), trait.toParam(5), trait.toParam(6)],
    929              [trait.toParam(7), trait.toParam(8), trait.toParam(9)],
    930              [trait.toParam(10), trait.toParam(11), trait.toParam(12)],
    931            ],
    932            expected: true,
    933          },
    934          {
    935            input: [
    936              [trait.toParam(1), trait.toParam(2), trait.toParam(3), trait.toParam(4)],
    937              [trait.toParam(5), trait.toParam(6), trait.toParam(7), trait.toParam(8)],
    938            ],
    939            expected: true,
    940          },
    941          {
    942            input: [
    943              [trait.toParam(1), trait.toParam(2), trait.toParam(3), trait.toParam(4)],
    944              [trait.toParam(5), trait.toParam(6), trait.toParam(7), trait.toParam(8)],
    945              [trait.toParam(9), trait.toParam(10), trait.toParam(11), trait.toParam(12)],
    946            ],
    947            expected: true,
    948          },
    949          {
    950            input: [
    951              [trait.toParam(1), trait.toParam(2), trait.toParam(3), trait.toParam(4)],
    952              [trait.toParam(5), trait.toParam(6), trait.toParam(7), trait.toParam(8)],
    953              [trait.toParam(9), trait.toParam(10), trait.toParam(11), trait.toParam(12)],
    954              [trait.toParam(13), trait.toParam(14), trait.toParam(15), trait.toParam(16)],
    955            ],
    956            expected: true,
    957          },
    958          {
    959            input: [
    960              [trait.toParam([1, 2]), trait.toParam([2, 3])],
    961              [trait.toParam([3, 4]), trait.toParam([4, 5])],
    962            ],
    963            expected: true,
    964          },
    965          {
    966            input: [
    967              [trait.toParam([1, 2]), trait.toParam([2, 3])],
    968              [trait.toParam([3, 4]), trait.toParam([4, 5])],
    969              [trait.toParam([5, 6]), trait.toParam([6, 7])],
    970            ],
    971            expected: true,
    972          },
    973          {
    974            input: [
    975              [trait.toParam([1, 2]), trait.toParam([2, 3])],
    976              [trait.toParam([3, 4]), trait.toParam([4, 5])],
    977              [trait.toParam([5, 6]), trait.toParam([6, 7])],
    978              [trait.toParam([7, 8]), trait.toParam([8, 9])],
    979            ],
    980            expected: true,
    981          },
    982          {
    983            input: [
    984              [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
    985              [trait.toParam([4, 5]), trait.toParam([5, 6]), trait.toParam([6, 7])],
    986            ],
    987            expected: true,
    988          },
    989          {
    990            input: [
    991              [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
    992              [trait.toParam([4, 5]), trait.toParam([5, 6]), trait.toParam([6, 7])],
    993              [trait.toParam([7, 8]), trait.toParam([8, 9]), trait.toParam([9, 10])],
    994            ],
    995            expected: true,
    996          },
    997          {
    998            input: [
    999              [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
   1000              [trait.toParam([4, 5]), trait.toParam([5, 6]), trait.toParam([6, 7])],
   1001              [trait.toParam([7, 8]), trait.toParam([8, 9]), trait.toParam([9, 10])],
   1002              [trait.toParam([10, 11]), trait.toParam([11, 12]), trait.toParam([12, 13])],
   1003            ],
   1004            expected: true,
   1005          },
   1006          {
   1007            input: [
   1008              [
   1009                trait.toParam([1, 2]),
   1010                trait.toParam([2, 3]),
   1011                trait.toParam([3, 4]),
   1012                trait.toParam([4, 5]),
   1013              ],
   1014              [
   1015                trait.toParam([5, 6]),
   1016                trait.toParam([6, 7]),
   1017                trait.toParam([7, 8]),
   1018                trait.toParam([8, 9]),
   1019              ],
   1020            ],
   1021            expected: true,
   1022          },
   1023          {
   1024            input: [
   1025              [
   1026                trait.toParam([1, 2]),
   1027                trait.toParam([2, 3]),
   1028                trait.toParam([3, 4]),
   1029                trait.toParam([4, 5]),
   1030              ],
   1031              [
   1032                trait.toParam([5, 6]),
   1033                trait.toParam([6, 7]),
   1034                trait.toParam([7, 8]),
   1035                trait.toParam([8, 9]),
   1036              ],
   1037              [
   1038                trait.toParam([9, 10]),
   1039                trait.toParam([10, 11]),
   1040                trait.toParam([11, 12]),
   1041                trait.toParam([12, 13]),
   1042              ],
   1043            ],
   1044            expected: true,
   1045          },
   1046          {
   1047            input: [
   1048              [
   1049                trait.toParam([1, 2]),
   1050                trait.toParam([2, 3]),
   1051                trait.toParam([3, 4]),
   1052                trait.toParam([4, 5]),
   1053              ],
   1054              [
   1055                trait.toParam([5, 6]),
   1056                trait.toParam([6, 7]),
   1057                trait.toParam([7, 8]),
   1058                trait.toParam([8, 9]),
   1059              ],
   1060              [
   1061                trait.toParam([9, 10]),
   1062                trait.toParam([10, 11]),
   1063                trait.toParam([11, 12]),
   1064                trait.toParam([12, 13]),
   1065              ],
   1066              [
   1067                trait.toParam([13, 14]),
   1068                trait.toParam([14, 15]),
   1069                trait.toParam([15, 16]),
   1070                trait.toParam([16, 17]),
   1071              ],
   1072            ],
   1073            expected: true,
   1074          },
   1075 
   1076          // FPInterval, invalid dimensions
   1077          { input: [[trait.toParam(1)]], expected: false },
   1078          {
   1079            input: [[trait.toParam(1)], [trait.toParam(3), trait.toParam(4)]],
   1080            expected: false,
   1081          },
   1082          {
   1083            input: [
   1084              [trait.toParam(1), trait.toParam(2)],
   1085              [trait.toParam(3), trait.toParam(4), trait.toParam(5)],
   1086            ],
   1087            expected: false,
   1088          },
   1089          {
   1090            input: [
   1091              [trait.toParam(1), trait.toParam(2)],
   1092              [trait.toParam(3), trait.toParam(4)],
   1093              [trait.toParam(5)],
   1094            ],
   1095            expected: false,
   1096          },
   1097          {
   1098            input: [
   1099              [trait.toParam(1), trait.toParam(2)],
   1100              [trait.toParam(3), trait.toParam(4)],
   1101              [trait.toParam(5), trait.toParam(6)],
   1102              [trait.toParam(7), trait.toParam(8)],
   1103              [trait.toParam(9), trait.toParam(10)],
   1104            ],
   1105            expected: false,
   1106          },
   1107 
   1108          // Mixed
   1109          {
   1110            input: [
   1111              [1, [2]],
   1112              [3, 4],
   1113            ],
   1114            expected: false,
   1115          },
   1116          {
   1117            input: [
   1118              [[1], [2]],
   1119              [[3], 4],
   1120            ],
   1121            expected: false,
   1122          },
   1123          {
   1124            input: [
   1125              [1, 2],
   1126              [trait.toParam([3]), 4],
   1127            ],
   1128            expected: false,
   1129          },
   1130          {
   1131            input: [
   1132              [[1], trait.toParam([2])],
   1133              [trait.toParam([3]), trait.toParam([4])],
   1134            ],
   1135            expected: false,
   1136          },
   1137          {
   1138            input: [
   1139              [trait.toParam(1), [2]],
   1140              [3, 4],
   1141            ],
   1142            expected: false,
   1143          },
   1144        ];
   1145      })
   1146  )
   1147  .fn(t => {
   1148    const trait = FP[t.params.trait];
   1149    const input = t.params.input.map(a => a.map(e => trait.fromParam(e)));
   1150    const expected = t.params.expected;
   1151 
   1152    const got = trait.isMatrix(input);
   1153    t.expect(
   1154      got === expected,
   1155      `${t.params.trait}.isMatrix([${input}]) returned ${got}. Expected ${expected}`
   1156    );
   1157  });
   1158 
   1159 interface toMatrixCase {
   1160  input: (number | IntervalEndpoints | FPIntervalParam)[][];
   1161  expected: (number | IntervalEndpoints)[][];
   1162 }
   1163 
   1164 g.test('toMatrix')
   1165  .params(u =>
   1166    u
   1167      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   1168      .beginSubcases()
   1169      .expandWithParams<toMatrixCase>(p => {
   1170        const trait = FP[p.trait];
   1171        return [
   1172          // numbers
   1173          {
   1174            input: [
   1175              [1, 2],
   1176              [3, 4],
   1177            ],
   1178            expected: [
   1179              [1, 2],
   1180              [3, 4],
   1181            ],
   1182          },
   1183          {
   1184            input: [
   1185              [1, 2],
   1186              [3, 4],
   1187              [5, 6],
   1188            ],
   1189            expected: [
   1190              [1, 2],
   1191              [3, 4],
   1192              [5, 6],
   1193            ],
   1194          },
   1195          {
   1196            input: [
   1197              [1, 2],
   1198              [3, 4],
   1199              [5, 6],
   1200              [7, 8],
   1201            ],
   1202            expected: [
   1203              [1, 2],
   1204              [3, 4],
   1205              [5, 6],
   1206              [7, 8],
   1207            ],
   1208          },
   1209          {
   1210            input: [
   1211              [1, 2, 3],
   1212              [4, 5, 6],
   1213            ],
   1214            expected: [
   1215              [1, 2, 3],
   1216              [4, 5, 6],
   1217            ],
   1218          },
   1219          {
   1220            input: [
   1221              [1, 2, 3],
   1222              [4, 5, 6],
   1223              [7, 8, 9],
   1224            ],
   1225            expected: [
   1226              [1, 2, 3],
   1227              [4, 5, 6],
   1228              [7, 8, 9],
   1229            ],
   1230          },
   1231          {
   1232            input: [
   1233              [1, 2, 3],
   1234              [4, 5, 6],
   1235              [7, 8, 9],
   1236              [10, 11, 12],
   1237            ],
   1238            expected: [
   1239              [1, 2, 3],
   1240              [4, 5, 6],
   1241              [7, 8, 9],
   1242              [10, 11, 12],
   1243            ],
   1244          },
   1245          {
   1246            input: [
   1247              [1, 2, 3, 4],
   1248              [5, 6, 7, 8],
   1249            ],
   1250            expected: [
   1251              [1, 2, 3, 4],
   1252              [5, 6, 7, 8],
   1253            ],
   1254          },
   1255          {
   1256            input: [
   1257              [1, 2, 3, 4],
   1258              [5, 6, 7, 8],
   1259              [9, 10, 11, 12],
   1260            ],
   1261            expected: [
   1262              [1, 2, 3, 4],
   1263              [5, 6, 7, 8],
   1264              [9, 10, 11, 12],
   1265            ],
   1266          },
   1267          {
   1268            input: [
   1269              [1, 2, 3, 4],
   1270              [5, 6, 7, 8],
   1271              [9, 10, 11, 12],
   1272              [13, 14, 15, 16],
   1273            ],
   1274            expected: [
   1275              [1, 2, 3, 4],
   1276              [5, 6, 7, 8],
   1277              [9, 10, 11, 12],
   1278              [13, 14, 15, 16],
   1279            ],
   1280          },
   1281 
   1282          // IntervalEndpoints
   1283          {
   1284            input: [
   1285              [[1], [2]],
   1286              [[3], [4]],
   1287            ],
   1288            expected: [
   1289              [1, 2],
   1290              [3, 4],
   1291            ],
   1292          },
   1293          {
   1294            input: [
   1295              [[1], [2]],
   1296              [[3], [4]],
   1297              [[5], [6]],
   1298            ],
   1299            expected: [
   1300              [1, 2],
   1301              [3, 4],
   1302              [5, 6],
   1303            ],
   1304          },
   1305          {
   1306            input: [
   1307              [[1], [2]],
   1308              [[3], [4]],
   1309              [[5], [6]],
   1310              [[7], [8]],
   1311            ],
   1312            expected: [
   1313              [1, 2],
   1314              [3, 4],
   1315              [5, 6],
   1316              [7, 8],
   1317            ],
   1318          },
   1319          {
   1320            input: [
   1321              [[1], [2], [3]],
   1322              [[4], [5], [6]],
   1323            ],
   1324            expected: [
   1325              [1, 2, 3],
   1326              [4, 5, 6],
   1327            ],
   1328          },
   1329          {
   1330            input: [
   1331              [[1], [2], [3]],
   1332              [[4], [5], [6]],
   1333              [[7], [8], [9]],
   1334            ],
   1335            expected: [
   1336              [1, 2, 3],
   1337              [4, 5, 6],
   1338              [7, 8, 9],
   1339            ],
   1340          },
   1341          {
   1342            input: [
   1343              [[1], [2], [3]],
   1344              [[4], [5], [6]],
   1345              [[7], [8], [9]],
   1346              [[10], [11], [12]],
   1347            ],
   1348            expected: [
   1349              [1, 2, 3],
   1350              [4, 5, 6],
   1351              [7, 8, 9],
   1352              [10, 11, 12],
   1353            ],
   1354          },
   1355          {
   1356            input: [
   1357              [[1], [2], [3], [4]],
   1358              [[5], [6], [7], [8]],
   1359            ],
   1360            expected: [
   1361              [1, 2, 3, 4],
   1362              [5, 6, 7, 8],
   1363            ],
   1364          },
   1365          {
   1366            input: [
   1367              [[1], [2], [3], [4]],
   1368              [[5], [6], [7], [8]],
   1369              [[9], [10], [11], [12]],
   1370            ],
   1371            expected: [
   1372              [1, 2, 3, 4],
   1373              [5, 6, 7, 8],
   1374              [9, 10, 11, 12],
   1375            ],
   1376          },
   1377          {
   1378            input: [
   1379              [[1], [2], [3], [4]],
   1380              [[5], [6], [7], [8]],
   1381              [[9], [10], [11], [12]],
   1382              [[13], [14], [15], [16]],
   1383            ],
   1384            expected: [
   1385              [1, 2, 3, 4],
   1386              [5, 6, 7, 8],
   1387              [9, 10, 11, 12],
   1388              [13, 14, 15, 16],
   1389            ],
   1390          },
   1391 
   1392          // FPInterval
   1393          {
   1394            input: [
   1395              [trait.toParam(1), trait.toParam(2)],
   1396              [trait.toParam(3), trait.toParam(4)],
   1397            ],
   1398            expected: [
   1399              [1, 2],
   1400              [3, 4],
   1401            ],
   1402          },
   1403          {
   1404            input: [
   1405              [trait.toParam(1), trait.toParam(2)],
   1406              [trait.toParam(3), trait.toParam(4)],
   1407              [trait.toParam(5), trait.toParam(6)],
   1408            ],
   1409            expected: [
   1410              [1, 2],
   1411              [3, 4],
   1412              [5, 6],
   1413            ],
   1414          },
   1415          {
   1416            input: [
   1417              [trait.toParam(1), trait.toParam(2)],
   1418              [trait.toParam(3), trait.toParam(4)],
   1419              [trait.toParam(5), trait.toParam(6)],
   1420              [trait.toParam(7), trait.toParam(8)],
   1421            ],
   1422            expected: [
   1423              [1, 2],
   1424              [3, 4],
   1425              [5, 6],
   1426              [7, 8],
   1427            ],
   1428          },
   1429          {
   1430            input: [
   1431              [trait.toParam(1), trait.toParam(2), trait.toParam(3)],
   1432              [trait.toParam(4), trait.toParam(5), trait.toParam(6)],
   1433            ],
   1434            expected: [
   1435              [1, 2, 3],
   1436              [4, 5, 6],
   1437            ],
   1438          },
   1439          {
   1440            input: [
   1441              [trait.toParam(1), trait.toParam(2), trait.toParam(3)],
   1442              [trait.toParam(4), trait.toParam(5), trait.toParam(6)],
   1443              [trait.toParam(7), trait.toParam(8), trait.toParam(9)],
   1444            ],
   1445            expected: [
   1446              [1, 2, 3],
   1447              [4, 5, 6],
   1448              [7, 8, 9],
   1449            ],
   1450          },
   1451          {
   1452            input: [
   1453              [trait.toParam(1), trait.toParam(2), trait.toParam(3)],
   1454              [trait.toParam(4), trait.toParam(5), trait.toParam(6)],
   1455              [trait.toParam(7), trait.toParam(8), trait.toParam(9)],
   1456              [trait.toParam(10), trait.toParam(11), trait.toParam(12)],
   1457            ],
   1458            expected: [
   1459              [1, 2, 3],
   1460              [4, 5, 6],
   1461              [7, 8, 9],
   1462              [10, 11, 12],
   1463            ],
   1464          },
   1465          {
   1466            input: [
   1467              [trait.toParam(1), trait.toParam(2), trait.toParam(3), trait.toParam(4)],
   1468              [trait.toParam(5), trait.toParam(6), trait.toParam(7), trait.toParam(8)],
   1469            ],
   1470            expected: [
   1471              [1, 2, 3, 4],
   1472              [5, 6, 7, 8],
   1473            ],
   1474          },
   1475          {
   1476            input: [
   1477              [trait.toParam(1), trait.toParam(2), trait.toParam(3), trait.toParam(4)],
   1478              [trait.toParam(5), trait.toParam(6), trait.toParam(7), trait.toParam(8)],
   1479              [trait.toParam(9), trait.toParam(10), trait.toParam(11), trait.toParam(12)],
   1480            ],
   1481            expected: [
   1482              [1, 2, 3, 4],
   1483              [5, 6, 7, 8],
   1484              [9, 10, 11, 12],
   1485            ],
   1486          },
   1487          {
   1488            input: [
   1489              [trait.toParam(1), trait.toParam(2), trait.toParam(3), trait.toParam(4)],
   1490              [trait.toParam(5), trait.toParam(6), trait.toParam(7), trait.toParam(8)],
   1491              [trait.toParam(9), trait.toParam(10), trait.toParam(11), trait.toParam(12)],
   1492              [trait.toParam(13), trait.toParam(14), trait.toParam(15), trait.toParam(16)],
   1493            ],
   1494            expected: [
   1495              [1, 2, 3, 4],
   1496              [5, 6, 7, 8],
   1497              [9, 10, 11, 12],
   1498              [13, 14, 15, 16],
   1499            ],
   1500          },
   1501 
   1502          {
   1503            input: [
   1504              [trait.toParam([1, 2]), trait.toParam([2, 3])],
   1505              [trait.toParam([3, 4]), trait.toParam([4, 5])],
   1506            ],
   1507            expected: [
   1508              [
   1509                [1, 2],
   1510                [2, 3],
   1511              ],
   1512              [
   1513                [3, 4],
   1514                [4, 5],
   1515              ],
   1516            ],
   1517          },
   1518          {
   1519            input: [
   1520              [trait.toParam([1, 2]), trait.toParam([2, 3])],
   1521              [trait.toParam([3, 4]), trait.toParam([4, 5])],
   1522              [trait.toParam([5, 6]), trait.toParam([6, 7])],
   1523            ],
   1524            expected: [
   1525              [
   1526                [1, 2],
   1527                [2, 3],
   1528              ],
   1529              [
   1530                [3, 4],
   1531                [4, 5],
   1532              ],
   1533              [
   1534                [5, 6],
   1535                [6, 7],
   1536              ],
   1537            ],
   1538          },
   1539          {
   1540            input: [
   1541              [trait.toParam([1, 2]), trait.toParam([2, 3])],
   1542              [trait.toParam([3, 4]), trait.toParam([4, 5])],
   1543              [trait.toParam([5, 6]), trait.toParam([6, 7])],
   1544              [trait.toParam([7, 8]), trait.toParam([8, 9])],
   1545            ],
   1546            expected: [
   1547              [
   1548                [1, 2],
   1549                [2, 3],
   1550              ],
   1551              [
   1552                [3, 4],
   1553                [4, 5],
   1554              ],
   1555              [
   1556                [5, 6],
   1557                [6, 7],
   1558              ],
   1559              [
   1560                [7, 8],
   1561                [8, 9],
   1562              ],
   1563            ],
   1564          },
   1565          {
   1566            input: [
   1567              [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
   1568              [trait.toParam([4, 5]), trait.toParam([5, 6]), trait.toParam([6, 7])],
   1569            ],
   1570            expected: [
   1571              [
   1572                [1, 2],
   1573                [2, 3],
   1574                [3, 4],
   1575              ],
   1576              [
   1577                [4, 5],
   1578                [5, 6],
   1579                [6, 7],
   1580              ],
   1581            ],
   1582          },
   1583          {
   1584            input: [
   1585              [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
   1586              [trait.toParam([4, 5]), trait.toParam([5, 6]), trait.toParam([6, 7])],
   1587              [trait.toParam([7, 8]), trait.toParam([8, 9]), trait.toParam([9, 10])],
   1588            ],
   1589            expected: [
   1590              [
   1591                [1, 2],
   1592                [2, 3],
   1593                [3, 4],
   1594              ],
   1595              [
   1596                [4, 5],
   1597                [5, 6],
   1598                [6, 7],
   1599              ],
   1600              [
   1601                [7, 8],
   1602                [8, 9],
   1603                [9, 10],
   1604              ],
   1605            ],
   1606          },
   1607          {
   1608            input: [
   1609              [trait.toParam([1, 2]), trait.toParam([2, 3]), trait.toParam([3, 4])],
   1610              [trait.toParam([4, 5]), trait.toParam([5, 6]), trait.toParam([6, 7])],
   1611              [trait.toParam([7, 8]), trait.toParam([8, 9]), trait.toParam([9, 10])],
   1612              [trait.toParam([10, 11]), trait.toParam([11, 12]), trait.toParam([12, 13])],
   1613            ],
   1614            expected: [
   1615              [
   1616                [1, 2],
   1617                [2, 3],
   1618                [3, 4],
   1619              ],
   1620              [
   1621                [4, 5],
   1622                [5, 6],
   1623                [6, 7],
   1624              ],
   1625              [
   1626                [7, 8],
   1627                [8, 9],
   1628                [9, 10],
   1629              ],
   1630              [
   1631                [10, 11],
   1632                [11, 12],
   1633                [12, 13],
   1634              ],
   1635            ],
   1636          },
   1637          {
   1638            input: [
   1639              [
   1640                trait.toParam([1, 2]),
   1641                trait.toParam([2, 3]),
   1642                trait.toParam([3, 4]),
   1643                trait.toParam([4, 5]),
   1644              ],
   1645              [
   1646                trait.toParam([5, 6]),
   1647                trait.toParam([6, 7]),
   1648                trait.toParam([7, 8]),
   1649                trait.toParam([8, 9]),
   1650              ],
   1651            ],
   1652            expected: [
   1653              [
   1654                [1, 2],
   1655                [2, 3],
   1656                [3, 4],
   1657                [4, 5],
   1658              ],
   1659              [
   1660                [5, 6],
   1661                [6, 7],
   1662                [7, 8],
   1663                [8, 9],
   1664              ],
   1665            ],
   1666          },
   1667          {
   1668            input: [
   1669              [
   1670                trait.toParam([1, 2]),
   1671                trait.toParam([2, 3]),
   1672                trait.toParam([3, 4]),
   1673                trait.toParam([4, 5]),
   1674              ],
   1675              [
   1676                trait.toParam([5, 6]),
   1677                trait.toParam([6, 7]),
   1678                trait.toParam([7, 8]),
   1679                trait.toParam([8, 9]),
   1680              ],
   1681              [
   1682                trait.toParam([9, 10]),
   1683                trait.toParam([10, 11]),
   1684                trait.toParam([11, 12]),
   1685                trait.toParam([12, 13]),
   1686              ],
   1687            ],
   1688            expected: [
   1689              [
   1690                [1, 2],
   1691                [2, 3],
   1692                [3, 4],
   1693                [4, 5],
   1694              ],
   1695              [
   1696                [5, 6],
   1697                [6, 7],
   1698                [7, 8],
   1699                [8, 9],
   1700              ],
   1701              [
   1702                [9, 10],
   1703                [10, 11],
   1704                [11, 12],
   1705                [12, 13],
   1706              ],
   1707            ],
   1708          },
   1709          {
   1710            input: [
   1711              [
   1712                trait.toParam([1, 2]),
   1713                trait.toParam([2, 3]),
   1714                trait.toParam([3, 4]),
   1715                trait.toParam([4, 5]),
   1716              ],
   1717              [
   1718                trait.toParam([5, 6]),
   1719                trait.toParam([6, 7]),
   1720                trait.toParam([7, 8]),
   1721                trait.toParam([8, 9]),
   1722              ],
   1723              [
   1724                trait.toParam([9, 10]),
   1725                trait.toParam([10, 11]),
   1726                trait.toParam([11, 12]),
   1727                trait.toParam([12, 13]),
   1728              ],
   1729              [
   1730                trait.toParam([13, 14]),
   1731                trait.toParam([14, 15]),
   1732                trait.toParam([15, 16]),
   1733                trait.toParam([16, 17]),
   1734              ],
   1735            ],
   1736            expected: [
   1737              [
   1738                [1, 2],
   1739                [2, 3],
   1740                [3, 4],
   1741                [4, 5],
   1742              ],
   1743              [
   1744                [5, 6],
   1745                [6, 7],
   1746                [7, 8],
   1747                [8, 9],
   1748              ],
   1749              [
   1750                [9, 10],
   1751                [10, 11],
   1752                [11, 12],
   1753                [12, 13],
   1754              ],
   1755              [
   1756                [13, 14],
   1757                [14, 15],
   1758                [15, 16],
   1759                [16, 17],
   1760              ],
   1761            ],
   1762          },
   1763 
   1764          // Mixed
   1765          {
   1766            input: [
   1767              [1, [2]],
   1768              [3, 4],
   1769            ],
   1770            expected: [
   1771              [1, 2],
   1772              [3, 4],
   1773            ],
   1774          },
   1775          {
   1776            input: [
   1777              [[1], [2]],
   1778              [[3], 4],
   1779            ],
   1780            expected: [
   1781              [1, 2],
   1782              [3, 4],
   1783            ],
   1784          },
   1785          {
   1786            input: [
   1787              [1, 2],
   1788              [trait.toParam([3]), 4],
   1789            ],
   1790            expected: [
   1791              [1, 2],
   1792              [3, 4],
   1793            ],
   1794          },
   1795          {
   1796            input: [
   1797              [[1], trait.toParam([2])],
   1798              [trait.toParam([3]), trait.toParam([4])],
   1799            ],
   1800            expected: [
   1801              [1, 2],
   1802              [3, 4],
   1803            ],
   1804          },
   1805        ];
   1806      })
   1807  )
   1808  .fn(t => {
   1809    const trait = FP[t.params.trait];
   1810    const input = map2DArray(t.params.input, e => trait.fromParam(e));
   1811    const expected = map2DArray(t.params.expected, e => trait.toInterval(e));
   1812 
   1813    const got = trait.toMatrix(input);
   1814    t.expect(
   1815      objectEquals(got, expected),
   1816      `${t.params.trait}.toMatrix([${input}]) returned [${got}]. Expected [${expected}]`
   1817    );
   1818  });
   1819 
   1820 // API - Fundamental Error Intervals
   1821 
   1822 interface AbsoluteErrorCase {
   1823  value: number;
   1824  error: number;
   1825  expected: number | IntervalEndpoints;
   1826 }
   1827 
   1828 // Special values used for testing absolute error interval
   1829 // A small absolute error value is a representable value x that much smaller than 1.0,
   1830 // but 1.0 +/- x is still exactly representable.
   1831 const kSmallAbsoluteErrorValue = {
   1832  f32: 2 ** -11, // Builtin cos and sin has a absolute error 2**-11 for f32
   1833  f16: 2 ** -7, // Builtin cos and sin has a absolute error 2**-7 for f16
   1834 } as const;
   1835 // A large absolute error value is a representable value x that much smaller than maximum
   1836 // positive, but positive.max - x is still exactly representable.
   1837 const kLargeAbsoluteErrorValue = {
   1838  f32: 2 ** 110, // f32.positive.max - 2**110 = 3.4028104e+38 = 0x7f7fffbf in f32
   1839  f16: 2 ** 10, // f16.positive.max - 2**10 = 64480 = 0x7bdf in f16
   1840 } as const;
   1841 // A subnormal absolute error value is a subnormal representable value x of kind, which ensures
   1842 // that positive.subnormal.min +/- x is still exactly representable.
   1843 const kSubnormalAbsoluteErrorValue = {
   1844  f32: 2 ** -140, // f32 0x00000200
   1845  f16: 2 ** -20, // f16 0x0010
   1846 } as const;
   1847 
   1848 g.test('absoluteErrorInterval')
   1849  .params(u =>
   1850    u
   1851      .combine('trait', ['f32', 'f16'] as const)
   1852      .beginSubcases()
   1853      .expandWithParams<AbsoluteErrorCase>(p => {
   1854        const trait = FP[p.trait];
   1855        const constants = trait.constants();
   1856        const smallErr = kSmallAbsoluteErrorValue[p.trait];
   1857        const largeErr = kLargeAbsoluteErrorValue[p.trait];
   1858        const subnormalErr = kSubnormalAbsoluteErrorValue[p.trait];
   1859 
   1860        // prettier-ignore
   1861        return [
   1862          // Edge Cases
   1863          // 1. Interval around infinity would be kUnboundedEndpoints
   1864          { value: constants.positive.infinity, error: 0, expected: kUnboundedEndpoints },
   1865          { value: constants.positive.infinity, error: largeErr, expected: kUnboundedEndpoints },
   1866          { value: constants.positive.infinity, error: 1, expected: kUnboundedEndpoints },
   1867          { value: constants.negative.infinity, error: 0, expected: kUnboundedEndpoints },
   1868          { value: constants.negative.infinity, error: largeErr, expected: kUnboundedEndpoints },
   1869          { value: constants.negative.infinity, error: 1, expected: kUnboundedEndpoints },
   1870          // 2. Interval around largest finite positive/negative
   1871          { value: constants.positive.max, error: 0, expected: constants.positive.max },
   1872          { value: constants.positive.max, error: largeErr, expected: kUnboundedEndpoints},
   1873          { value: constants.positive.max, error: constants.positive.max, expected: kUnboundedEndpoints},
   1874          { value: constants.negative.min, error: 0, expected: constants.negative.min },
   1875          { value: constants.negative.min, error: largeErr, expected: kUnboundedEndpoints},
   1876          { value: constants.negative.min, error: constants.positive.max, expected: kUnboundedEndpoints},
   1877          // 3. Interval around small but normal center, center should not get flushed.
   1878          { value: constants.positive.min, error: 0, expected: constants.positive.min },
   1879          { value: constants.positive.min, error: smallErr, expected: [constants.positive.min - smallErr, constants.positive.min + smallErr]},
   1880          { value: constants.positive.min, error: 1, expected: [constants.positive.min - 1, constants.positive.min + 1]},
   1881          { value: constants.negative.max, error: 0, expected: constants.negative.max },
   1882          { value: constants.negative.max, error: smallErr, expected: [constants.negative.max - smallErr, constants.negative.max + smallErr]},
   1883          { value: constants.negative.max, error: 1, expected: [constants.negative.max - 1, constants.negative.max + 1] },
   1884          // 4. Subnormals, center can be flushed to 0.0
   1885          { value: constants.positive.subnormal.max, error: 0, expected: [0, constants.positive.subnormal.max] },
   1886          { value: constants.positive.subnormal.max, error: subnormalErr, expected: [-subnormalErr, constants.positive.subnormal.max + subnormalErr]},
   1887          { value: constants.positive.subnormal.max, error: smallErr, expected: [-smallErr, constants.positive.subnormal.max + smallErr]},
   1888          { value: constants.positive.subnormal.max, error: 1, expected: [-1, constants.positive.subnormal.max + 1]},
   1889          { value: constants.positive.subnormal.min, error: 0, expected: [0, constants.positive.subnormal.min] },
   1890          { value: constants.positive.subnormal.min, error: subnormalErr, expected: [-subnormalErr, constants.positive.subnormal.min + subnormalErr]},
   1891          { value: constants.positive.subnormal.min, error: smallErr, expected: [-smallErr, constants.positive.subnormal.min + smallErr]},
   1892          { value: constants.positive.subnormal.min, error: 1, expected: [-1, constants.positive.subnormal.min + 1] },
   1893          { value: constants.negative.subnormal.min, error: 0, expected: [constants.negative.subnormal.min, 0] },
   1894          { value: constants.negative.subnormal.min, error: subnormalErr, expected: [constants.negative.subnormal.min - subnormalErr, subnormalErr] },
   1895          { value: constants.negative.subnormal.min, error: smallErr, expected: [constants.negative.subnormal.min - smallErr, smallErr] },
   1896          { value: constants.negative.subnormal.min, error: 1, expected: [constants.negative.subnormal.min - 1, 1] },
   1897          { value: constants.negative.subnormal.max, error: 0, expected: [constants.negative.subnormal.max, 0] },
   1898          { value: constants.negative.subnormal.max, error: subnormalErr, expected: [constants.negative.subnormal.max - subnormalErr, subnormalErr] },
   1899          { value: constants.negative.subnormal.max, error: smallErr, expected: [constants.negative.subnormal.max - smallErr, smallErr] },
   1900          { value: constants.negative.subnormal.max, error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
   1901 
   1902          // Zero
   1903          { value: 0, error: 0, expected: 0 },
   1904          { value: 0, error: smallErr, expected: [-smallErr, smallErr] },
   1905          { value: 0, error: 1, expected: [-1, 1] },
   1906 
   1907          // Two
   1908          { value: 2, error: 0, expected: 2 },
   1909          { value: 2, error: smallErr, expected: [2 - smallErr, 2 + smallErr] },
   1910          { value: 2, error: 1, expected: [1, 3] },
   1911          { value: -2, error: 0, expected: -2 },
   1912          { value: -2, error: smallErr, expected: [-2 - smallErr, -2 + smallErr] },
   1913          { value: -2, error: 1, expected: [-3, -1] },
   1914 
   1915          // 64-bit subnormals, expected to be treated as 0.0 or smallest subnormal of kind.
   1916          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: 0, expected: [0, constants.positive.subnormal.min] },
   1917          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: subnormalErr, expected: [-subnormalErr, constants.positive.subnormal.min + subnormalErr] },
   1918          // Note that f32 minimum subnormal is so smaller than 1.0, adding them together may result in the f64 results 1.0.
   1919          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: 1, expected: [-1, constants.positive.subnormal.min + 1] },
   1920          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), error: 0, expected: [0, constants.positive.subnormal.min] },
   1921          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), error: subnormalErr, expected: [-subnormalErr, constants.positive.subnormal.min + subnormalErr] },
   1922          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), error: 1, expected: [-1, constants.positive.subnormal.min + 1] },
   1923          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), error: 0, expected: [constants.negative.subnormal.max, 0] },
   1924          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), error: subnormalErr, expected: [constants.negative.subnormal.max - subnormalErr, subnormalErr] },
   1925          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
   1926          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: 0, expected: [constants.negative.subnormal.max, 0] },
   1927          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: subnormalErr, expected: [constants.negative.subnormal.max - subnormalErr, subnormalErr] },
   1928          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
   1929        ];
   1930      })
   1931  )
   1932  .fn(t => {
   1933    const trait = FP[t.params.trait];
   1934    const expected = trait.toInterval(t.params.expected);
   1935    const got = trait.absoluteErrorInterval(t.params.value, t.params.error);
   1936    t.expect(
   1937      objectEquals(expected, got),
   1938      `${t.params.trait}.absoluteErrorInterval(${t.params.value}, ${
   1939        t.params.error
   1940      }) returned ${got} (${got.begin.toExponential()}, ${got.end.toExponential()}). Expected ${expected}`
   1941    );
   1942  });
   1943 
   1944 interface CorrectlyRoundedCase {
   1945  value: number;
   1946  expected: number | IntervalEndpoints;
   1947 }
   1948 
   1949 // Correctly rounded cases that input values are exactly representable normal values of target type
   1950 // prettier-ignore
   1951 const kCorrectlyRoundedNormalCases = {
   1952  f32: [
   1953    { value: 0, expected: [0, 0] },
   1954    { value: reinterpretU32AsF32(0x03800000), expected: reinterpretU32AsF32(0x03800000) },
   1955    { value: reinterpretU32AsF32(0x03800001), expected: reinterpretU32AsF32(0x03800001) },
   1956    { value: reinterpretU32AsF32(0x83800000), expected: reinterpretU32AsF32(0x83800000) },
   1957    { value: reinterpretU32AsF32(0x83800001), expected: reinterpretU32AsF32(0x83800001) },
   1958  ] as CorrectlyRoundedCase[],
   1959  f16: [
   1960    { value: 0, expected: [0, 0] },
   1961    { value: reinterpretU16AsF16(0x0c00), expected: reinterpretU16AsF16(0x0c00) },
   1962    { value: reinterpretU16AsF16(0x0c01), expected: reinterpretU16AsF16(0x0c01) },
   1963    { value: reinterpretU16AsF16(0x8c00), expected: reinterpretU16AsF16(0x8c00) },
   1964    { value: reinterpretU16AsF16(0x8c01), expected: reinterpretU16AsF16(0x8c01) },
   1965  ] as CorrectlyRoundedCase[],
   1966 } as const;
   1967 
   1968 // 64-bit normals that fall between two conjunction normal values in target type
   1969 const kCorrectlyRoundedF64NormalCases = [
   1970  {
   1971    value: reinterpretU64AsF64(0x3ff0_0000_0000_0001n),
   1972    expected: {
   1973      f32: [reinterpretU32AsF32(0x3f800000), reinterpretU32AsF32(0x3f800001)],
   1974      f16: [reinterpretU16AsF16(0x3c00), reinterpretU16AsF16(0x3c01)],
   1975    },
   1976  },
   1977  {
   1978    value: reinterpretU64AsF64(0x3ff0_0000_0000_0002n),
   1979    expected: {
   1980      f32: [reinterpretU32AsF32(0x3f800000), reinterpretU32AsF32(0x3f800001)],
   1981      f16: [reinterpretU16AsF16(0x3c00), reinterpretU16AsF16(0x3c01)],
   1982    },
   1983  },
   1984  {
   1985    value: reinterpretU64AsF64(0x3ff0_0800_0000_0010n),
   1986    expected: {
   1987      f32: [reinterpretU32AsF32(0x3f804000), reinterpretU32AsF32(0x3f804001)],
   1988      f16: [reinterpretU16AsF16(0x3c02), reinterpretU16AsF16(0x3c03)],
   1989    },
   1990  },
   1991  {
   1992    value: reinterpretU64AsF64(0x3ff0_1000_0000_0020n),
   1993    expected: {
   1994      f32: [reinterpretU32AsF32(0x3f808000), reinterpretU32AsF32(0x3f808001)],
   1995      f16: [reinterpretU16AsF16(0x3c04), reinterpretU16AsF16(0x3c05)],
   1996    },
   1997  },
   1998  {
   1999    value: reinterpretU64AsF64(0xbff0_0000_0000_0001n),
   2000    expected: {
   2001      f32: [reinterpretU32AsF32(0xbf800001), reinterpretU32AsF32(0xbf800000)],
   2002      f16: [reinterpretU16AsF16(0xbc01), reinterpretU16AsF16(0xbc00)],
   2003    },
   2004  },
   2005  {
   2006    value: reinterpretU64AsF64(0xbff0_0000_0000_0002n),
   2007    expected: {
   2008      f32: [reinterpretU32AsF32(0xbf800001), reinterpretU32AsF32(0xbf800000)],
   2009      f16: [reinterpretU16AsF16(0xbc01), reinterpretU16AsF16(0xbc00)],
   2010    },
   2011  },
   2012  {
   2013    value: reinterpretU64AsF64(0xbff0_0800_0000_0010n),
   2014    expected: {
   2015      f32: [reinterpretU32AsF32(0xbf804001), reinterpretU32AsF32(0xbf804000)],
   2016      f16: [reinterpretU16AsF16(0xbc03), reinterpretU16AsF16(0xbc02)],
   2017    },
   2018  },
   2019  {
   2020    value: reinterpretU64AsF64(0xbff0_1000_0000_0020n),
   2021    expected: {
   2022      f32: [reinterpretU32AsF32(0xbf808001), reinterpretU32AsF32(0xbf808000)],
   2023      f16: [reinterpretU16AsF16(0xbc05), reinterpretU16AsF16(0xbc04)],
   2024    },
   2025  },
   2026 ] as const;
   2027 
   2028 g.test('correctlyRoundedInterval')
   2029  .params(u =>
   2030    u
   2031      .combine('trait', ['f32', 'f16'] as const)
   2032      .beginSubcases()
   2033      .expandWithParams<CorrectlyRoundedCase>(p => {
   2034        const constants = FP[p.trait].constants();
   2035        // prettier-ignore
   2036        return [
   2037          // Edge Cases
   2038          { value: constants.positive.infinity, expected: kUnboundedEndpoints },
   2039          { value: constants.negative.infinity, expected: kUnboundedEndpoints },
   2040          { value: constants.positive.max, expected: constants.positive.max },
   2041          { value: constants.negative.min, expected: constants.negative.min },
   2042          { value: constants.positive.min, expected: constants.positive.min },
   2043          { value: constants.negative.max, expected: constants.negative.max },
   2044 
   2045          // Subnormals
   2046          { value: constants.positive.subnormal.min, expected: [0, constants.positive.subnormal.min] },
   2047          { value: constants.positive.subnormal.max, expected: [0, constants.positive.subnormal.max] },
   2048          { value: constants.negative.subnormal.min, expected: [constants.negative.subnormal.min, 0] },
   2049          { value: constants.negative.subnormal.max, expected: [constants.negative.subnormal.max, 0] },
   2050 
   2051          // 64-bit subnormals should be rounded down to 0 or up to smallest subnormal
   2052          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), expected: [0, constants.positive.subnormal.min] },
   2053          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), expected: [0, constants.positive.subnormal.min] },
   2054          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), expected: [constants.negative.subnormal.max, 0] },
   2055          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), expected: [constants.negative.subnormal.max, 0] },
   2056 
   2057          // Normals
   2058          ...kCorrectlyRoundedNormalCases[p.trait],
   2059 
   2060          // 64-bit normals that fall between two conjunction normal values in target type
   2061          ...kCorrectlyRoundedF64NormalCases.map(t => { return {value: t.value, expected: t.expected[p.trait]} as CorrectlyRoundedCase;}),
   2062        ];
   2063      })
   2064  )
   2065  .fn(t => {
   2066    const trait = FP[t.params.trait];
   2067    const expected = trait.toInterval(t.params.expected);
   2068    const got = trait.correctlyRoundedInterval(t.params.value);
   2069    t.expect(
   2070      objectEquals(expected, got),
   2071      `${t.params.trait}.correctlyRoundedInterval(${t.params.value}) returned ${got}. Expected ${expected}`
   2072    );
   2073  });
   2074 
   2075 interface ULPCase {
   2076  value: number;
   2077  num_ulp: number;
   2078  expected: number | IntervalEndpoints;
   2079 }
   2080 
   2081 // Special values used for testing ULP error interval
   2082 const kULPErrorValue = {
   2083  f32: 4096, // 4096 ULP is required for atan accuracy on f32
   2084  f16: 5, // 5 ULP is required for atan accuracy on f16
   2085 };
   2086 
   2087 g.test('ulpInterval')
   2088  .params(u =>
   2089    u
   2090      .combine('trait', ['f32', 'f16'] as const)
   2091      .beginSubcases()
   2092      .expandWithParams<ULPCase>(p => {
   2093        const trait = kFPTraitForULP[p.trait];
   2094        const constants = FP[trait].constants();
   2095        const ULPValue = kULPErrorValue[trait];
   2096        const plusOneULP = kPlusOneULPFunctions[trait];
   2097        const plusNULP = kPlusNULPFunctions[trait];
   2098        const minusOneULP = kMinusOneULPFunctions[trait];
   2099        const minusNULP = kMinusNULPFunctions[trait];
   2100        // prettier-ignore
   2101        return [
   2102          // Edge Cases
   2103          { value: constants.positive.infinity, num_ulp: 0, expected: kUnboundedEndpoints },
   2104          { value: constants.positive.infinity, num_ulp: 1, expected: kUnboundedEndpoints },
   2105          { value: constants.positive.infinity, num_ulp: ULPValue, expected: kUnboundedEndpoints },
   2106          { value: constants.negative.infinity, num_ulp: 0, expected: kUnboundedEndpoints },
   2107          { value: constants.negative.infinity, num_ulp: 1, expected: kUnboundedEndpoints },
   2108          { value: constants.negative.infinity, num_ulp: ULPValue, expected: kUnboundedEndpoints },
   2109          { value: constants.positive.max, num_ulp: 0, expected: constants.positive.max },
   2110          { value: constants.positive.max, num_ulp: 1, expected: kUnboundedEndpoints },
   2111          { value: constants.positive.max, num_ulp: ULPValue, expected: kUnboundedEndpoints },
   2112          { value: constants.positive.min, num_ulp: 0, expected: constants.positive.min },
   2113          { value: constants.positive.min, num_ulp: 1, expected: [0, plusOneULP(constants.positive.min)] },
   2114          { value: constants.positive.min, num_ulp: ULPValue, expected: [0, plusNULP(constants.positive.min, ULPValue)] },
   2115          { value: constants.negative.min, num_ulp: 0, expected: constants.negative.min },
   2116          { value: constants.negative.min, num_ulp: 1, expected: kUnboundedEndpoints },
   2117          { value: constants.negative.min, num_ulp: ULPValue, expected: kUnboundedEndpoints },
   2118          { value: constants.negative.max, num_ulp: 0, expected: constants.negative.max },
   2119          { value: constants.negative.max, num_ulp: 1, expected: [minusOneULP(constants.negative.max), 0] },
   2120          { value: constants.negative.max, num_ulp: ULPValue, expected: [minusNULP(constants.negative.max, ULPValue), 0] },
   2121 
   2122          // Subnormals
   2123          { value: constants.positive.subnormal.max, num_ulp: 0, expected: [0, constants.positive.subnormal.max] },
   2124          { value: constants.positive.subnormal.max, num_ulp: 1, expected: [minusOneULP(0), plusOneULP(constants.positive.subnormal.max)] },
   2125          { value: constants.positive.subnormal.max, num_ulp: ULPValue, expected: [minusNULP(0, ULPValue), plusNULP(constants.positive.subnormal.max, ULPValue)] },
   2126          { value: constants.positive.subnormal.min, num_ulp: 0, expected: [0, constants.positive.subnormal.min] },
   2127          { value: constants.positive.subnormal.min, num_ulp: 1, expected: [minusOneULP(0), plusOneULP(constants.positive.subnormal.min)] },
   2128          { value: constants.positive.subnormal.min, num_ulp: ULPValue, expected: [minusNULP(0, ULPValue), plusNULP(constants.positive.subnormal.min, ULPValue)] },
   2129          { value: constants.negative.subnormal.min, num_ulp: 0, expected: [constants.negative.subnormal.min, 0] },
   2130          { value: constants.negative.subnormal.min, num_ulp: 1, expected: [minusOneULP(constants.negative.subnormal.min), plusOneULP(0)] },
   2131          { value: constants.negative.subnormal.min, num_ulp: ULPValue, expected: [minusNULP(constants.negative.subnormal.min, ULPValue), plusNULP(0, ULPValue)] },
   2132          { value: constants.negative.subnormal.max, num_ulp: 0, expected: [constants.negative.subnormal.max, 0] },
   2133          { value: constants.negative.subnormal.max, num_ulp: 1, expected: [minusOneULP(constants.negative.subnormal.max), plusOneULP(0)] },
   2134          { value: constants.negative.subnormal.max, num_ulp: ULPValue, expected: [minusNULP(constants.negative.subnormal.max, ULPValue), plusNULP(0, ULPValue)] },
   2135 
   2136          // 64-bit subnormals
   2137          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), num_ulp: 0, expected: [0, constants.positive.subnormal.min] },
   2138          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), num_ulp: 1, expected: [minusOneULP(0), plusOneULP(constants.positive.subnormal.min)] },
   2139          { value: reinterpretU64AsF64(0x0000_0000_0000_0001n), num_ulp: ULPValue, expected: [minusNULP(0, ULPValue), plusNULP(constants.positive.subnormal.min, ULPValue)] },
   2140          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), num_ulp: 0, expected: [0, constants.positive.subnormal.min] },
   2141          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), num_ulp: 1, expected: [minusOneULP(0), plusOneULP(constants.positive.subnormal.min)] },
   2142          { value: reinterpretU64AsF64(0x0000_0000_0000_0002n), num_ulp: ULPValue, expected: [minusNULP(0, ULPValue), plusNULP(constants.positive.subnormal.min, ULPValue)] },
   2143          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), num_ulp: 0, expected: [constants.negative.subnormal.max, 0] },
   2144          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), num_ulp: 1, expected: [minusOneULP(constants.negative.subnormal.max), plusOneULP(0)] },
   2145          { value: reinterpretU64AsF64(0x800f_ffff_ffff_ffffn), num_ulp: ULPValue, expected: [minusNULP(constants.negative.subnormal.max, ULPValue), plusNULP(0, ULPValue)] },
   2146          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), num_ulp: 0, expected: [constants.negative.subnormal.max, 0] },
   2147          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), num_ulp: 1, expected: [minusOneULP(constants.negative.subnormal.max), plusOneULP(0)] },
   2148          { value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), num_ulp: ULPValue, expected: [minusNULP(constants.negative.subnormal.max, ULPValue), plusNULP(0, ULPValue)] },
   2149 
   2150          // Zero
   2151          { value: 0, num_ulp: 0, expected: 0 },
   2152          { value: 0, num_ulp: 1, expected: [minusOneULP(0), plusOneULP(0)] },
   2153          { value: 0, num_ulp: ULPValue, expected: [minusNULP(0, ULPValue), plusNULP(0, ULPValue)] },
   2154        ];
   2155      })
   2156  )
   2157  .fn(t => {
   2158    const trait = FP[t.params.trait];
   2159    const expected = trait.toInterval(t.params.expected);
   2160    const got = trait.ulpInterval(t.params.value, t.params.num_ulp);
   2161    t.expect(
   2162      objectEquals(expected, got),
   2163      `${t.params.trait}.ulpInterval(${t.params.value}, ${t.params.num_ulp}) returned ${got}. Expected ${expected}`
   2164    );
   2165  });
   2166 
   2167 // API - Acceptance Intervals
   2168 // List of frequently used JS number in test cases, which are not exactly representable in f32 or f16.
   2169 type ConstantNumberFrequentlyUsedInCases = '0.1' | '-0.1' | '1.9' | '-1.9';
   2170 
   2171 // Correctly rounded expectation of frequently used JS Number value in test cases
   2172 const kConstantCorrectlyRoundedExpectation = {
   2173  f32: {
   2174    // 0.1 falls between f32 0x3DCCCCCC and 0x3DCCCCCD
   2175    '0.1': [reinterpretU32AsF32(0x3dcccccc), reinterpretU32AsF32(0x3dcccccd)],
   2176    // -0.1 falls between f32 0xBDCCCCCD and 0xBDCCCCCC
   2177    '-0.1': [reinterpretU32AsF32(0xbdcccccd), reinterpretU32AsF32(0xbdcccccc)],
   2178    // 1.9 falls between f32 0x3FF33333 and 0x3FF33334
   2179    '1.9': [reinterpretU32AsF32(0x3ff33333), reinterpretU32AsF32(0x3ff33334)],
   2180    // -1.9 falls between f32 0xBFF33334 and 0xBFF33333
   2181    '-1.9': [reinterpretU32AsF32(0xbff33334), reinterpretU32AsF32(0xbff33333)],
   2182  } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalEndpoints },
   2183  f16: {
   2184    // 0.1 falls between f16 0x2E66 and 0x2E67
   2185    '0.1': [reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0x2e67)],
   2186    // -0.1 falls between f16 0xAE67 and 0xAE66
   2187    '-0.1': [reinterpretU16AsF16(0xae67), reinterpretU16AsF16(0xae66)],
   2188    // 1.9 falls between f16 0x3F99 and 0x3F9A
   2189    '1.9': [reinterpretU16AsF16(0x3f99), reinterpretU16AsF16(0x3f9a)],
   2190    // 1.9 falls between f16 0xBF9A and 0xBF99
   2191    '-1.9': [reinterpretU16AsF16(0xbf9a), reinterpretU16AsF16(0xbf99)],
   2192  } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalEndpoints },
   2193  // Since abstract is actually f64 and JS number is also f64, the JS number value will map to
   2194  // identical abstracty value without rounded.
   2195  abstract: {
   2196    '0.1': 0.1,
   2197    '-0.1': -0.1,
   2198    '1.9': 1.9,
   2199    '-1.9': -1.9,
   2200  } as { [value in ConstantNumberFrequentlyUsedInCases]: number },
   2201 } as const;
   2202 
   2203 interface ScalarToIntervalCase {
   2204  input: number;
   2205  expected: number | IntervalEndpoints;
   2206 }
   2207 
   2208 g.test('absInterval')
   2209  .params(u =>
   2210    u
   2211      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   2212      .beginSubcases()
   2213      .expandWithParams<ScalarToIntervalCase>(p => {
   2214        const constants = FP[p.trait].constants();
   2215        // prettier-ignore
   2216        return [
   2217          // Common usages
   2218          { input: 1, expected: 1 },
   2219          { input: -1, expected: 1 },
   2220          // abs(+/-0.1) is correctly rounded interval of 0.1
   2221          { input: 0.1, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']},
   2222          { input: -0.1, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']},
   2223          // abs(+/-1.9) is correctly rounded interval of 1.9
   2224          { input: 1.9, expected: kConstantCorrectlyRoundedExpectation[p.trait]['1.9']},
   2225          { input: -1.9, expected: kConstantCorrectlyRoundedExpectation[p.trait]['1.9']},
   2226 
   2227          // Edge cases
   2228          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2229          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2230          { input: constants.positive.max, expected: constants.positive.max },
   2231          { input: constants.positive.min, expected: constants.positive.min },
   2232          { input: constants.negative.min, expected: constants.positive.max },
   2233          { input: constants.negative.max, expected: constants.positive.min },
   2234 
   2235          // Subnormals
   2236          { input: constants.positive.subnormal.max, expected: [0, constants.positive.subnormal.max] },
   2237          { input: constants.positive.subnormal.min, expected: [0, constants.positive.subnormal.min] },
   2238          { input: constants.negative.subnormal.min, expected: [0, constants.positive.subnormal.max] },
   2239          { input: constants.negative.subnormal.max, expected: [0, constants.positive.subnormal.min] },
   2240 
   2241          // Zero
   2242          { input: 0, expected: 0 },
   2243        ];
   2244      })
   2245  )
   2246  .fn(t => {
   2247    const trait = FP[t.params.trait];
   2248    const expected = trait.toInterval(t.params.expected);
   2249    const got = trait.absInterval(t.params.input);
   2250    t.expect(
   2251      objectEquals(expected, got),
   2252      `${t.params.trait}.absInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2253    );
   2254  });
   2255 
   2256 // Acos expectation intervals are bounded by both inherited atan2(sqrt(1.0 - x*x), x) and absolute error.
   2257 // Atan2 introduce 4096ULP for f32 and 5ULP for f16, and sqrt inherited from 1.0/inverseSqrt.
   2258 // prettier-ignore
   2259 const kAcosIntervalCases = {
   2260  f32: [
   2261    { input: kPlusOneULPFunctions['f32'](-1), expected: [reinterpretU32AsF32(0x4048fa32), reinterpretU32AsF32(0x40491bdb)] },  // ~Ï€
   2262    { input: -1/2, expected: [reinterpretU32AsF32(0x4005fa90), reinterpretU32AsF32(0x40061a93)] },  // ~2Ï€/3
   2263    { input: 1/2, expected: [reinterpretU32AsF32(0x3f85fa8f), reinterpretU32AsF32(0x3f861a94)] },  // ~Ï€/3
   2264    // Input case to get smallest well-defined expected result, the expectation interval is bounded
   2265    // by ULP (lower boundary) and absolute error (upper boundary).
   2266    // f32 1.0-1ULP=0x3F7FFFFF=0.9999999403953552,
   2267    // acos(0.9999999403953552)=3.4526698478747995220159699019994e-4 rounded to f32 0x39B504F3 or 0x39B504F4,
   2268    // absolute error interval upper boundary 0x39B504F4+6.77e-5=0.00041296700619608164 i.e. f64 0x3F3B_106F_C933_4FB9.
   2269    { input: kMinusOneULPFunctions['f32'](1), expected: [reinterpretU64AsF64(0x3f2f_fdff_6000_0000n), reinterpretU64AsF64(0x3f3b_106f_c933_4fb9n)] },  // ~0.0003
   2270  ] as ScalarToIntervalCase[],
   2271  f16: [
   2272    { input: kPlusOneULPFunctions['f16'](-1), expected: [reinterpretU16AsF16(0x4233), reinterpretU16AsF16(0x4243)] },  // ~Ï€
   2273    { input: -1/2, expected: [reinterpretU16AsF16(0x402a), reinterpretU16AsF16(0x4037)] },  // ~2Ï€/3
   2274    { input: 1/2, expected: [reinterpretU16AsF16(0x3c29), reinterpretU16AsF16(0x3c38)] },  // ~Ï€/3
   2275    // Input case to get smallest well-defined expected result, the expectation interval is bounded
   2276    // by ULP (lower boundary) and absolute error (upper boundary).
   2277    // f16 1.0-1ULP=0x3BFF=0.99951171875,
   2278    // acos(0.99951171875)=0.03125127170547389912035676677648 rounded to f16 0x2800 or 0x2801,
   2279    // absolute error interval upper boundary 0x2801+3.91e-3=0.035190517578125 i.e. f64 0x3FA2_047D_D441_3554.
   2280    { input: kMinusOneULPFunctions['f16'](1), expected: [reinterpretU16AsF16(0x259d), reinterpretU64AsF64(0x3fa2_047d_d441_3554n)] },  // ~0.03
   2281  ] as ScalarToIntervalCase[],
   2282 } as const;
   2283 
   2284 g.test('acosInterval')
   2285  .params(u =>
   2286    u
   2287      .combine('trait', ['f32', 'f16'] as const)
   2288      .beginSubcases()
   2289      .expandWithParams<ScalarToIntervalCase>(p => {
   2290        const trait = FP[p.trait];
   2291        const constants = trait.constants();
   2292        // prettier-ignore
   2293        return [
   2294          // The acceptance interval @ x = -1 and 1 is kUnboundedEndpoints,
   2295          // because  sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of
   2296          // inverseqrt.
   2297          // The acceptance interval @ x = 0 is kUnboundedEndpoints, because atan2
   2298          // is not well-defined/implemented at 0.
   2299          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2300          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2301          { input: -1, expected: kUnboundedEndpoints },
   2302          { input: 0, expected: kUnboundedEndpoints },
   2303          { input: 1, expected: kUnboundedEndpoints },
   2304          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2305          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2306 
   2307          // Cases that bounded by absolute error and inherited from atan2(sqrt(1-x*x), x). Note that
   2308          // even x is very close to 1.0 and the expected result is close to 0.0, the expected
   2309          // interval is still bounded by ULP as well as absolute error, specifically lower boundary
   2310          // comes from ULP error and upper boundary comes from absolute error in those cases.
   2311          ...kAcosIntervalCases[p.trait],
   2312        ];
   2313      })
   2314  )
   2315  .fn(t => {
   2316    const trait = FP[t.params.trait];
   2317    const expected = trait.toInterval(t.params.expected);
   2318    const got = trait.acosInterval(t.params.input);
   2319    t.expect(
   2320      objectEquals(expected, got),
   2321      `${t.params.trait}.acosInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2322    );
   2323  });
   2324 
   2325 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   2326 // human-readable form due to the inherited nature of the errors.
   2327 // prettier-ignore
   2328 const kAcoshAlternativeIntervalCases = {
   2329  f32: [
   2330    { input: 1.1, expected: [reinterpretU64AsF64(0x3fdc_6368_8000_0000n), reinterpretU64AsF64(0x3fdc_636f_2000_0000n)] },  // ~0.443..., differs from the primary in the later digits
   2331    { input: 10, expected: [reinterpretU64AsF64(0x4007_f21e_4000_0000n), reinterpretU64AsF64(0x4007_f21f_6000_0000n)] },  // ~2.993...
   2332  ] as ScalarToIntervalCase[],
   2333  f16: [
   2334    { input: 1.1, expected: [reinterpretU64AsF64(0x3fdb_bc00_0000_0000n), reinterpretU64AsF64(0x3fdd_1000_0000_0000n)] },  // ~0.443..., differs from the primary in the later digits
   2335    { input: 10, expected: [reinterpretU64AsF64(0x4007_e000_0000_0000n), reinterpretU64AsF64(0x4008_0400_0000_0000n)] },  // ~2.993...
   2336  ] as ScalarToIntervalCase[],
   2337 } as const;
   2338 
   2339 g.test('acoshAlternativeInterval')
   2340  .params(u =>
   2341    u
   2342      .combine('trait', ['f32', 'f16'] as const)
   2343      .beginSubcases()
   2344      .expandWithParams<ScalarToIntervalCase>(p => {
   2345        const trait = FP[p.trait];
   2346        const constants = trait.constants();
   2347        // prettier-ignore
   2348        return [
   2349          ...kAcoshAlternativeIntervalCases[p.trait],
   2350 
   2351          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2352          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2353          { input: -1, expected: kUnboundedEndpoints },
   2354          { input: 0, expected: kUnboundedEndpoints },
   2355          { input: 1, expected: kUnboundedEndpoints },  // 1/0 occurs in inverseSqrt in this formulation
   2356          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2357          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2358        ];
   2359      })
   2360  )
   2361  .fn(t => {
   2362    const trait = FP[t.params.trait];
   2363    const expected = trait.toInterval(t.params.expected);
   2364    const got = trait.acoshAlternativeInterval(t.params.input);
   2365    t.expect(
   2366      objectEquals(expected, got),
   2367      `${t.params.trait}.acoshAlternativeInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2368    );
   2369  });
   2370 
   2371 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   2372 // human-readable form due to the inherited nature of the errors.
   2373 // prettier-ignore
   2374 const kAcoshPrimaryIntervalCases = {
   2375  f32: [
   2376    { input: 1.1, expected: [reinterpretU64AsF64(0x3fdc_6368_2000_0000n), reinterpretU64AsF64(0x3fdc_636f_8000_0000n)] }, // ~0.443..., differs from the alternative in the later digits
   2377    { input: 10, expected: [reinterpretU64AsF64(0x4007_f21e_4000_0000n), reinterpretU64AsF64(0x4007_f21f_6000_0000n)] },  // ~2.993...
   2378  ] as ScalarToIntervalCase[],
   2379  f16: [
   2380    { input: 1.1, expected: [reinterpretU64AsF64(0x3fdb_bc00_0000_0000n), reinterpretU64AsF64(0x3fdd_1c00_0000_0000n)] },  // ~0.443..., differs from the primary in the later digits
   2381    { input: 10, expected: [reinterpretU64AsF64(0x4007_e000_0000_0000n), reinterpretU64AsF64(0x4008_0400_0000_0000n)] },  // ~2.993...
   2382  ] as ScalarToIntervalCase[],
   2383 } as const;
   2384 
   2385 g.test('acoshPrimaryInterval')
   2386  .params(u =>
   2387    u
   2388      .combine('trait', ['f32', 'f16'] as const)
   2389      .beginSubcases()
   2390      .expandWithParams<ScalarToIntervalCase>(p => {
   2391        const trait = FP[p.trait];
   2392        const constants = trait.constants();
   2393        // prettier-ignore
   2394        return [
   2395          ...kAcoshPrimaryIntervalCases[p.trait],
   2396 
   2397          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2398          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2399          { input: -1, expected: kUnboundedEndpoints },
   2400          { input: 0, expected: kUnboundedEndpoints },
   2401          { input: 1, expected: kUnboundedEndpoints },  // 1/0 occurs in inverseSqrt in this formulation
   2402          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2403          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2404        ];
   2405      })
   2406  )
   2407  .fn(t => {
   2408    const trait = FP[t.params.trait];
   2409    const expected = trait.toInterval(t.params.expected);
   2410    const got = trait.acoshPrimaryInterval(t.params.input);
   2411    t.expect(
   2412      objectEquals(expected, got),
   2413      `${t.params.trait}.acoshPrimaryInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2414    );
   2415  });
   2416 
   2417 // Asin cases that bounded by inherited atan2(x, sqrt(1.0 - x*x)) rather than absolute error.
   2418 // Atan2 introduce 4096ULP for f32 and 5ULP for f16, and sqrt inherited from 1.0/inverseSqrt.
   2419 // prettier-ignore
   2420 const kAsinIntervalInheritedCases = {
   2421  f32: [
   2422    { input: -1/2, expected: [reinterpretU32AsF32(0xbf061a96), reinterpretU32AsF32(0xbf05fa8e)] },  // ~-Ï€/6
   2423    { input: 1/2, expected: [reinterpretU32AsF32(0x3f05fa8e), reinterpretU32AsF32(0x3f061a96)] },  // ~Ï€/6
   2424  ] as ScalarToIntervalCase[],
   2425  f16: [
   2426    { input: -1/2, expected: [reinterpretU16AsF16(0xb83a), reinterpretU16AsF16(0xb827)] },  // ~-Ï€/6
   2427    { input: 1/2, expected: [reinterpretU16AsF16(0x3827), reinterpretU16AsF16(0x383a)] },  // ~Ï€/6
   2428  ] as ScalarToIntervalCase[],
   2429 } as const;
   2430 
   2431 g.test('asinInterval')
   2432  .params(u =>
   2433    u
   2434      .combine('trait', ['f32', 'f16'] as const)
   2435      .beginSubcases()
   2436      .expandWithParams<ScalarToIntervalCase>(p => {
   2437        const trait = FP[p.trait];
   2438        const constants = trait.constants();
   2439        const abs_error = p.trait === 'f32' ? 6.81e-5 : 3.91e-3;
   2440        // prettier-ignore
   2441        return [
   2442          // The acceptance interval @ x = -1 and 1 is kUnboundedEndpoints,
   2443          // because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of
   2444          // inversqrt.
   2445          // The acceptance interval @ x = 0 is kUnboundedEndpoints, because
   2446          // atan2 is not well-defined/implemented at 0.
   2447          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2448          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2449          { input: -1, expected: kUnboundedEndpoints },
   2450          // Subnormal input may get flushed to 0, and result in kUnboundedEndpoints.
   2451          { input: constants.negative.subnormal.min, expected: kUnboundedEndpoints },
   2452          { input: 0, expected: kUnboundedEndpoints },
   2453          { input: constants.positive.subnormal.max, expected: kUnboundedEndpoints },
   2454          { input: 1, expected: kUnboundedEndpoints },
   2455          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2456          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2457 
   2458          // When input near 0, the expected result is bounded by absolute error rather than ULP
   2459          // error. Away from 0 the atan2 inherited error should be larger.
   2460          { input: constants.negative.max, expected: trait.absoluteErrorInterval(Math.asin(constants.negative.max), abs_error).endpoints() },  // ~0
   2461          { input: constants.positive.min, expected: trait.absoluteErrorInterval(Math.asin(constants.positive.min), abs_error).endpoints() },  // ~0
   2462 
   2463          // Cases that inherited from atan2(x, sqrt(1-x*x))
   2464          ...kAsinIntervalInheritedCases[p.trait],
   2465        ];
   2466      })
   2467  )
   2468  .fn(t => {
   2469    const trait = FP[t.params.trait];
   2470    const expected = trait.toInterval(t.params.expected);
   2471    const got = trait.asinInterval(t.params.input);
   2472    t.expect(
   2473      objectEquals(expected, got),
   2474      `${t.params.trait}.asinInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2475    );
   2476  });
   2477 
   2478 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   2479 // human-readable form due to the inherited nature of the errors.
   2480 // prettier-ignore
   2481 const kAsinhIntervalCases = {
   2482  f32: [
   2483    { input: -1, expected: [reinterpretU64AsF64(0xbfec_343a_8000_0000n), reinterpretU64AsF64(0xbfec_3432_8000_0000n)] },  // ~-0.88137...
   2484    { input: 0, expected: [reinterpretU64AsF64(0xbeaa_0000_2000_0000n), reinterpretU64AsF64(0x3eb1_ffff_d000_0000n)] },  // ~0
   2485    { input: 1, expected: [reinterpretU64AsF64(0x3fec_3435_4000_0000n), reinterpretU64AsF64(0x3fec_3437_8000_0000n)] },  // ~0.88137...
   2486  ] as ScalarToIntervalCase[],
   2487  f16: [
   2488    { input: -1, expected: [reinterpretU64AsF64(0xbfec_b800_0000_0000n), reinterpretU64AsF64(0xbfeb_b800_0000_0000n)] },  // ~-0.88137...
   2489    { input: 0, expected: [reinterpretU64AsF64(0xbf85_0200_0000_0000n), reinterpretU64AsF64(0x3f89_fa00_0000_0000n)] },  // ~0
   2490    { input: 1, expected: [reinterpretU64AsF64(0x3fec_1000_0000_0000n), reinterpretU64AsF64(0x3fec_5400_0000_0000n)] },  // ~0.88137...
   2491  ] as ScalarToIntervalCase[],
   2492 } as const;
   2493 
   2494 g.test('asinhInterval')
   2495  .params(u =>
   2496    u
   2497      .combine('trait', ['f32', 'f16'] as const)
   2498      .beginSubcases()
   2499      .expandWithParams<ScalarToIntervalCase>(p => {
   2500        const trait = FP[p.trait];
   2501        const constants = trait.constants();
   2502        // prettier-ignore
   2503        return [
   2504          ...kAsinhIntervalCases[p.trait],
   2505 
   2506          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2507          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2508          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2509          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2510        ];
   2511      })
   2512  )
   2513  .fn(t => {
   2514    const trait = FP[t.params.trait];
   2515    const expected = trait.toInterval(t.params.expected);
   2516    const got = trait.asinhInterval(t.params.input);
   2517    t.expect(
   2518      objectEquals(expected, got),
   2519      `${t.params.trait}.asinhInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2520    );
   2521  });
   2522 
   2523 // prettier-ignore
   2524 const kAtanIntervalCases = {
   2525  f32: [
   2526    // x=-√3=-1.7320508... quantized to f32 0xBFDDB3D7,
   2527    // atan(0xBFDDB3D7)=-1.0471975434247854181546378047331 ~ -pi/3 rounded to f32 0xBF860A92 or 0xBF860A91,
   2528    // kValue.f32.negative.pi.third is 0xBF860A92.
   2529    { input: reinterpretU32AsF32(0xbfddb3d7), expected: [kValue.f32.negative.pi.third, kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.third)] },
   2530    // atan(-1)=-0.78539816339744830961566084581988 ~ -pi/4 rounded to f32 0xBF490FDB or 0xBF490FDA,
   2531    // kValue.f32.negative.pi.quarter is 0xBF490FDB.
   2532    { input: -1, expected: [kValue.f32.negative.pi.quarter, kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.quarter)] },
   2533    // x=-1/√3=-0.577350269... quantized to f32 0xBF13CD3A,
   2534    // atan(0xBF13CD3A)=-0.52359876782648663982267459646249 ~ -pi/6 rounded to f32 0xBF060A92 or 0xBF060A91,
   2535    // kValue.f32.negative.pi.sixth is 0xBF060A92.
   2536    { input: reinterpretU32AsF32(0xbf13cd3a), expected: [kValue.f32.negative.pi.sixth, kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.sixth)] },
   2537    // x=1/√3=0.577350269... quantized to f32 0x3F13CD3A.
   2538    { input: reinterpretU32AsF32(0x3f13cd3a), expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.sixth), kValue.f32.positive.pi.sixth] },
   2539    { input: 1, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.quarter), kValue.f32.positive.pi.quarter] },
   2540    // x=√3=1.7320508... quantized to f32 0x3FDDB3D7.
   2541    { input: reinterpretU32AsF32(0x3fddb3d7), expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.third), kValue.f32.positive.pi.third] },
   2542  ] as ScalarToIntervalCase[],
   2543  f16: [
   2544    // x=-√3=-1.7320508... quantized to f16 0xBEED,
   2545    // atan(0xBEED)=-1.0470461377318847079113932677171 ~ -pi/3 rounded to f16 0xBC31 or 0xBC30,
   2546    // kValue.f16.negative.pi.third is 0xBC30.
   2547    { input: reinterpretU16AsF16(0xbeed), expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.third), kValue.f16.negative.pi.third] },
   2548    // atan(-1)=-0.78539816339744830961566084581988 ~ -pi/4 rounded to f16 0xBA49 or 0xBA48.
   2549    // kValue.f16.negative.pi.quarter is 0xBA48.
   2550    { input: -1, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.quarter), kValue.f16.negative.pi.quarter] },
   2551    // x=-1/√3=-0.577350269... quantized to f16 0xB89E,
   2552    // atan(0xB89E)=-0.52344738860166563645762619364966 ~ -pi/6 rounded to f16 0xB831 or 0xB830,
   2553    // kValue.f16.negative.pi.sixth is 0xB830.
   2554    { input: reinterpretU16AsF16(0xb89e), expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.sixth), kValue.f16.negative.pi.sixth] },
   2555    // x=1/√3=0.577350269... quantized to f16 0x389E
   2556    { input: reinterpretU16AsF16(0x389e), expected: [kValue.f16.positive.pi.sixth, kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.sixth)] },
   2557    { input: 1, expected: [kValue.f16.positive.pi.quarter, kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.quarter)] },
   2558    // x=√3=1.7320508... quantized to f16 0x3EED
   2559    { input: reinterpretU16AsF16(0x3eed), expected: [kValue.f16.positive.pi.third, kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.third)] },
   2560  ] as ScalarToIntervalCase[],
   2561 } as const;
   2562 
   2563 g.test('atanInterval')
   2564  .params(u =>
   2565    u
   2566      .combine('trait', ['f32', 'f16'] as const)
   2567      .beginSubcases()
   2568      .expandWithParams<ScalarToIntervalCase>(p => {
   2569        const constants = FP[p.trait].constants();
   2570        // prettier-ignore
   2571        return [
   2572          { input: 0, expected: 0 },
   2573          ...kAtanIntervalCases[p.trait],
   2574 
   2575          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2576          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2577        ];
   2578      })
   2579  )
   2580  .fn(t => {
   2581    const trait = FP[t.params.trait];
   2582 
   2583    const ulp_error = t.params.trait === 'f32' ? 4096 : 5;
   2584    const error = (n: number): number => {
   2585      return ulp_error * trait.oneULP(n);
   2586    };
   2587 
   2588    const expected = trait.toInterval(applyError(t.params.expected, error));
   2589 
   2590    const got = trait.atanInterval(t.params.input);
   2591    t.expect(
   2592      objectEquals(expected, got),
   2593      `${t.params.trait}.atanInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2594    );
   2595  });
   2596 
   2597 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   2598 // human-readable form due to the inherited nature of the errors.
   2599 // prettier-ignore
   2600 const kAtanhIntervalCases = {
   2601  f32: [
   2602    { input: -0.1, expected: [reinterpretU64AsF64(0xbfb9_af9a_6000_0000n), reinterpretU64AsF64(0xbfb9_af8c_c000_0000n)] },  // ~-0.1003...
   2603    { input: 0, expected: [reinterpretU64AsF64(0xbe96_0000_2000_0000n), reinterpretU64AsF64(0x3e98_0000_0000_0000n)] },  // ~0
   2604    { input: 0.1, expected: [reinterpretU64AsF64(0x3fb9_af8b_8000_0000n), reinterpretU64AsF64(0x3fb9_af9b_0000_0000n)] },  // ~0.1003...
   2605  ] as ScalarToIntervalCase[],
   2606  f16: [
   2607    { input: -0.1, expected: [reinterpretU64AsF64(0xbfbb_0c00_0000_0000n), reinterpretU64AsF64(0xbfb8_5800_0000_0000n)] },  // ~-0.1003...
   2608    { input: 0, expected: [reinterpretU64AsF64(0xbf73_0400_0000_0000n), reinterpretU64AsF64(0x3f74_0000_0000_0000n)] },  // ~0
   2609    { input: 0.1, expected: [reinterpretU64AsF64(0x3fb8_3800_0000_0000n), reinterpretU64AsF64(0x3fbb_2400_0000_0000n)] },  // ~0.1003...
   2610  ] as ScalarToIntervalCase[],
   2611 } as const;
   2612 
   2613 g.test('atanhInterval')
   2614  .params(u =>
   2615    u
   2616      .combine('trait', ['f32', 'f16'] as const)
   2617      .beginSubcases()
   2618      .expandWithParams<ScalarToIntervalCase>(p => {
   2619        const trait = FP[p.trait];
   2620        const constants = trait.constants();
   2621        // prettier-ignore
   2622        return [
   2623          ...kAtanhIntervalCases[p.trait],
   2624 
   2625          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2626          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2627          { input: -1, expected: kUnboundedEndpoints },
   2628          { input: 1, expected: kUnboundedEndpoints },
   2629          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2630          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2631        ];
   2632      })
   2633  )
   2634  .fn(t => {
   2635    const trait = FP[t.params.trait];
   2636    const expected = trait.toInterval(t.params.expected);
   2637    const got = trait.atanhInterval(t.params.input);
   2638    t.expect(
   2639      objectEquals(expected, got),
   2640      `${t.params.trait}.atanhInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2641    );
   2642  });
   2643 
   2644 // Large but still representable integer
   2645 const kCeilIntervalCases = {
   2646  f32: [
   2647    { input: 2 ** 30, expected: 2 ** 30 },
   2648    { input: -(2 ** 30), expected: -(2 ** 30) },
   2649    { input: 0x80000000, expected: 0x80000000 }, // https://github.com/gpuweb/cts/issues/2766
   2650  ],
   2651  f16: [
   2652    { input: 2 ** 14, expected: 2 ** 14 },
   2653    { input: -(2 ** 14), expected: -(2 ** 14) },
   2654    { input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
   2655  ],
   2656  abstract: [
   2657    { input: 2 ** 52, expected: 2 ** 52 },
   2658    { input: -(2 ** 52), expected: -(2 ** 52) },
   2659    { input: 0x8000000000000000, expected: 0x8000000000000000 }, // https://github.com/gpuweb/cts/issues/2766
   2660  ],
   2661 } as const;
   2662 
   2663 g.test('ceilInterval')
   2664  .params(u =>
   2665    u
   2666      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   2667      .beginSubcases()
   2668      .expandWithParams<ScalarToIntervalCase>(p => {
   2669        const constants = FP[p.trait].constants();
   2670        // prettier-ignore
   2671        return [
   2672          { input: 0, expected: 0 },
   2673          { input: 0.1, expected: 1 },
   2674          { input: 0.9, expected: 1 },
   2675          { input: 1.0, expected: 1 },
   2676          { input: 1.1, expected: 2 },
   2677          { input: 1.9, expected: 2 },
   2678          { input: -0.1, expected: 0 },
   2679          { input: -0.9, expected: 0 },
   2680          { input: -1.0, expected: -1 },
   2681          { input: -1.1, expected: -1 },
   2682          { input: -1.9, expected: -1 },
   2683 
   2684          // Edge cases
   2685          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2686          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2687          { input: constants.positive.max, expected: constants.positive.max },
   2688          { input: constants.positive.min, expected: 1 },
   2689          { input: constants.negative.min, expected: constants.negative.min },
   2690          { input: constants.negative.max, expected: 0 },
   2691          ...kCeilIntervalCases[p.trait],
   2692 
   2693          // Subnormals
   2694          { input: constants.positive.subnormal.max, expected: [0, 1] },
   2695          { input: constants.positive.subnormal.min, expected: [0, 1] },
   2696          { input: constants.negative.subnormal.min, expected: 0 },
   2697          { input: constants.negative.subnormal.max, expected: 0 },
   2698        ];
   2699      })
   2700  )
   2701  .fn(t => {
   2702    const trait = FP[t.params.trait];
   2703    const expected = trait.toInterval(t.params.expected);
   2704    const got = trait.ceilInterval(t.params.input);
   2705    t.expect(
   2706      objectEquals(expected, got),
   2707      `${t.params.trait}.ceilInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2708    );
   2709  });
   2710 
   2711 // Cos interval cases on x=π/3, the result of f32 and f16 is different because π/3 quantized to
   2712 // different direction for two types.
   2713 const kCosIntervalThirdPiCases = {
   2714  // prettier-ignore
   2715  f32: [
   2716    // cos(-1.0471975803375244) = 0.499999974763
   2717    { input: kValue.f32.negative.pi.third, expected: [kMinusOneULPFunctions['f32'](1/2), 1/2] },
   2718    // cos(1.0471975803375244) = 0.499999974763
   2719    { input: kValue.f32.positive.pi.third, expected: [kMinusOneULPFunctions['f32'](1/2), 1/2] },
   2720  ],
   2721  f16: [
   2722    // cos(-1.046875) = 0.50027931
   2723    {
   2724      input: kValue.f16.negative.pi.third,
   2725      expected: FP['f16'].correctlyRoundedInterval(0.50027931).endpoints(),
   2726    },
   2727    // cos(1.046875) = 0.50027931
   2728    {
   2729      input: kValue.f16.positive.pi.third,
   2730      expected: FP['f16'].correctlyRoundedInterval(0.50027931).endpoints(),
   2731    },
   2732  ],
   2733 };
   2734 
   2735 g.test('cosInterval')
   2736  .params(u =>
   2737    u
   2738      .combine('trait', ['f32', 'f16'] as const)
   2739      .beginSubcases()
   2740      .expandWithParams<ScalarToIntervalCase>(p => {
   2741        const trait = FP[p.trait];
   2742        const constants = trait.constants();
   2743        // prettier-ignore
   2744        return [
   2745          // This test does not include some common cases. i.e. f(x = π/2) = 0,
   2746          // because the difference between true x and x as a f32 is sufficiently
   2747          // large, such that the high slope of f @ x causes the results to be
   2748          // substantially different, so instead of getting 0 you get a value on the
   2749          // order of 10^-8 away from 0, thus difficult to express in a
   2750          // human-readable manner.
   2751          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2752          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2753          { input: constants.negative.pi.whole, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
   2754          { input: 0, expected: [1, 1] },
   2755          { input: constants.positive.pi.whole, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
   2756          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2757          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2758 
   2759          ...(kCosIntervalThirdPiCases[p.trait] as ScalarToIntervalCase[]),
   2760        ];
   2761      })
   2762  )
   2763  .fn(t => {
   2764    const trait = FP[t.params.trait];
   2765 
   2766    const error = (_: number): number => {
   2767      return t.params.trait === 'f32' ? 2 ** -11 : 2 ** -7;
   2768    };
   2769 
   2770    const expected = trait.toInterval(applyError(t.params.expected, error));
   2771 
   2772    const got = trait.cosInterval(t.params.input);
   2773    t.expect(
   2774      objectEquals(expected, got),
   2775      `${t.params.trait}.cosInterval(${t.params.input}) returned ${got}. Expected ${expected}, ===${t.params.expected}===`
   2776    );
   2777  });
   2778 
   2779 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   2780 // human-readable form due to the inherited nature of the errors.
   2781 // prettier-ignore
   2782 const kCoshIntervalCases = {
   2783  f32: [
   2784    { input: -1, expected: [reinterpretU32AsF32(0x3fc583a4), reinterpretU32AsF32(0x3fc583b1)] },  // ~1.1543...
   2785    { input: 0, expected: [reinterpretU32AsF32(0x3f7ffffd), reinterpretU32AsF32(0x3f800002)] },  // ~1
   2786    { input: 1, expected: [reinterpretU32AsF32(0x3fc583a4), reinterpretU32AsF32(0x3fc583b1)] },  // ~1.1543...
   2787  ] as ScalarToIntervalCase[],
   2788  f16: [
   2789    { input: -1, expected: [reinterpretU16AsF16(0x3e27), reinterpretU16AsF16(0x3e30)] },  // ~1.1543...
   2790    { input: 0, expected: [reinterpretU16AsF16(0x3bff), reinterpretU16AsF16(0x3c01)] },  // ~1
   2791    { input: 1, expected: [reinterpretU16AsF16(0x3e27), reinterpretU16AsF16(0x3e30)] },  // ~1.1543...
   2792  ] as ScalarToIntervalCase[],
   2793 } as const;
   2794 
   2795 g.test('coshInterval')
   2796  .params(u =>
   2797    u
   2798      .combine('trait', ['f32', 'f16'] as const)
   2799      .beginSubcases()
   2800      .expandWithParams<ScalarToIntervalCase>(p => {
   2801        const trait = FP[p.trait];
   2802        const constants = trait.constants();
   2803        // prettier-ignore
   2804        return [
   2805          ...kCoshIntervalCases[p.trait],
   2806 
   2807          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2808          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2809          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2810          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2811        ];
   2812      })
   2813  )
   2814  .fn(t => {
   2815    const trait = FP[t.params.trait];
   2816    const expected = trait.toInterval(t.params.expected);
   2817    const got = trait.coshInterval(t.params.input);
   2818    t.expect(
   2819      objectEquals(expected, got),
   2820      `${t.params.trait}.coshInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2821    );
   2822  });
   2823 
   2824 // prettier-ignore
   2825 const kDegreesIntervalCases = {
   2826  f32: [
   2827    { input: kValue.f32.negative.pi.whole, expected: [kMinusOneULPFunctions['f32'](-180), kPlusOneULPFunctions['f32'](-180)] },
   2828    { input: kValue.f32.negative.pi.three_quarters, expected: [kMinusOneULPFunctions['f32'](-135), kPlusOneULPFunctions['f32'](-135)] },
   2829    { input: kValue.f32.negative.pi.half, expected: [kMinusOneULPFunctions['f32'](-90), kPlusOneULPFunctions['f32'](-90)] },
   2830    { input: kValue.f32.negative.pi.third, expected: [kMinusOneULPFunctions['f32'](-60), kPlusOneULPFunctions['f32'](-60)] },
   2831    { input: kValue.f32.negative.pi.quarter, expected: [kMinusOneULPFunctions['f32'](-45), kPlusOneULPFunctions['f32'](-45)] },
   2832    { input: kValue.f32.negative.pi.sixth, expected: [kMinusOneULPFunctions['f32'](-30), kPlusOneULPFunctions['f32'](-30)] },
   2833    { input: kValue.f32.positive.pi.sixth, expected: [kMinusOneULPFunctions['f32'](30), kPlusOneULPFunctions['f32'](30)] },
   2834    { input: kValue.f32.positive.pi.quarter, expected: [kMinusOneULPFunctions['f32'](45), kPlusOneULPFunctions['f32'](45)] },
   2835    { input: kValue.f32.positive.pi.third, expected: [kMinusOneULPFunctions['f32'](60), kPlusOneULPFunctions['f32'](60)] },
   2836    { input: kValue.f32.positive.pi.half, expected: [kMinusOneULPFunctions['f32'](90), kPlusOneULPFunctions['f32'](90)] },
   2837    { input: kValue.f32.positive.pi.three_quarters, expected: [kMinusOneULPFunctions['f32'](135), kPlusOneULPFunctions['f32'](135)] },
   2838    { input: kValue.f32.positive.pi.whole, expected: [kMinusOneULPFunctions['f32'](180), kPlusOneULPFunctions['f32'](180)] },
   2839  ] as ScalarToIntervalCase[],
   2840  f16: [
   2841    { input: kValue.f16.negative.pi.whole, expected: [-180, kPlusOneULPFunctions['f16'](-180)] },
   2842    { input: kValue.f16.negative.pi.three_quarters, expected: [-135, kPlusOneULPFunctions['f16'](-135)] },
   2843    { input: kValue.f16.negative.pi.half, expected: [-90, kPlusOneULPFunctions['f16'](-90)] },
   2844    { input: kValue.f16.negative.pi.third, expected: [-60, kPlusNULPFunctions['f16'](-60, 2)] },
   2845    { input: kValue.f16.negative.pi.quarter, expected: [-45, kPlusOneULPFunctions['f16'](-45)] },
   2846    { input: kValue.f16.negative.pi.sixth, expected: [-30, kPlusNULPFunctions['f16'](-30, 2)] },
   2847    { input: kValue.f16.positive.pi.sixth, expected: [kMinusNULPFunctions['f16'](30, 2), 30] },
   2848    { input: kValue.f16.positive.pi.quarter, expected: [kMinusOneULPFunctions['f16'](45), 45] },
   2849    { input: kValue.f16.positive.pi.third, expected: [kMinusNULPFunctions['f16'](60, 2), 60] },
   2850    { input: kValue.f16.positive.pi.half, expected: [kMinusOneULPFunctions['f16'](90), 90] },
   2851    { input: kValue.f16.positive.pi.three_quarters, expected: [kMinusOneULPFunctions['f16'](135), 135] },
   2852    { input: kValue.f16.positive.pi.whole, expected: [kMinusOneULPFunctions['f16'](180), 180] },
   2853  ] as ScalarToIntervalCase[],
   2854 } as const;
   2855 
   2856 g.test('degreesInterval')
   2857  .params(u =>
   2858    u
   2859      .combine('trait', ['f32', 'f16'] as const)
   2860      .beginSubcases()
   2861      .expandWithParams<ScalarToIntervalCase>(p => {
   2862        const trait = p.trait;
   2863        const constants = FP[trait].constants();
   2864        // prettier-ignore
   2865        return [
   2866          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   2867          { input: constants.negative.min, expected: kUnboundedEndpoints },
   2868          { input: 0, expected: 0 },
   2869          { input: constants.positive.max, expected: kUnboundedEndpoints },
   2870          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2871          ...kDegreesIntervalCases[trait]
   2872        ];
   2873      })
   2874  )
   2875  .fn(t => {
   2876    const trait = FP[t.params.trait];
   2877    const expected = trait.toInterval(t.params.expected);
   2878    const got = trait.degreesInterval(t.params.input);
   2879    t.expect(
   2880      objectEquals(expected, got),
   2881      `${t.params.trait}.degreesInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2882    );
   2883  });
   2884 
   2885 // prettier-ignore
   2886 const kExpIntervalCases = {
   2887  f32: [
   2888    { input: 1, expected: [kValue.f32.positive.e, kPlusOneULPFunctions['f32'](kValue.f32.positive.e)] },
   2889    // exp(88) = 1.6516362549940018555283297962649e+38 = 0x7ef882b6/7.
   2890    { input: 88, expected: [reinterpretU32AsF32(0x7ef882b6), reinterpretU32AsF32(0x7ef882b7)] },
   2891    // exp(89) overflow f32.
   2892    { input: 89, expected: kUnboundedEndpoints },
   2893  ] as ScalarToIntervalCase[],
   2894  f16: [
   2895    { input: 1, expected: [kValue.f16.positive.e, kPlusOneULPFunctions['f16'](kValue.f16.positive.e)] },
   2896    // exp(11) = 59874.141715197818455326485792258 = 0x7b4f/0x7b50.
   2897    { input: 11, expected: [reinterpretU16AsF16(0x7b4f), reinterpretU16AsF16(0x7b50)] },
   2898    // exp(12) = 162754.79141900392080800520489849 overflow f16.
   2899    { input: 12, expected: kUnboundedEndpoints },
   2900  ] as ScalarToIntervalCase[],
   2901 } as const;
   2902 
   2903 g.test('expInterval')
   2904  .params(u =>
   2905    u
   2906      .combine('trait', ['f32', 'f16'] as const)
   2907      .beginSubcases()
   2908      .expandWithParams<ScalarToIntervalCase>(p => {
   2909        const trait = p.trait;
   2910        const constants = FP[trait].constants();
   2911        // prettier-ignore
   2912        return [
   2913          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2914          { input: 0, expected: 1 },
   2915          ...kExpIntervalCases[trait],
   2916        ];
   2917      })
   2918  )
   2919  .fn(t => {
   2920    const trait = FP[t.params.trait];
   2921    const error = (x: number): number => {
   2922      let ulp_error;
   2923      switch (t.params.trait) {
   2924        case 'f32': {
   2925          ulp_error = 3 + 2 * Math.abs(t.params.input);
   2926          break;
   2927        }
   2928        case 'f16': {
   2929          ulp_error = 1 + 2 * Math.abs(t.params.input);
   2930          break;
   2931        }
   2932      }
   2933      return ulp_error * trait.oneULP(x);
   2934    };
   2935 
   2936    const expected = trait.toInterval(applyError(t.params.expected, error));
   2937    const got = trait.expInterval(t.params.input);
   2938 
   2939    t.expect(
   2940      objectEquals(expected, got),
   2941      `${t.params.trait}.expInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   2942    );
   2943  });
   2944 
   2945 // prettier-ignore
   2946 const kExp2IntervalCases = {
   2947  f32: [
   2948    // exp2(127) = 1.7014118346046923173168730371588e+38 = 0x7f000000, 3 + 2 * 127 = 258 ulps.
   2949    { input: 127, expected: reinterpretU32AsF32(0x7f000000) },
   2950    // exp2(128) overflow f32.
   2951    { input: 128, expected: kUnboundedEndpoints },
   2952  ] as ScalarToIntervalCase[],
   2953  f16: [
   2954    // exp2(15) = 32768 = 0x7800, 1 + 2 * 15 = 31 ulps
   2955    { input: 15, expected: reinterpretU16AsF16(0x7800) },
   2956    // exp2(16) = 65536 overflow f16.
   2957    { input: 16, expected: kUnboundedEndpoints },
   2958  ] as ScalarToIntervalCase[],
   2959 } as const;
   2960 
   2961 g.test('exp2Interval')
   2962  .params(u =>
   2963    u
   2964      .combine('trait', ['f32', 'f16'] as const)
   2965      .beginSubcases()
   2966      .expandWithParams<ScalarToIntervalCase>(p => {
   2967        const trait = p.trait;
   2968        const constants = FP[trait].constants();
   2969        // prettier-ignore
   2970        return [
   2971          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   2972          { input: 0, expected: 1 },
   2973          { input: 1, expected: 2 },
   2974          ...kExp2IntervalCases[trait],
   2975        ];
   2976      })
   2977  )
   2978  .fn(t => {
   2979    const trait = FP[t.params.trait];
   2980    const error = (x: number): number => {
   2981      let ulp_error;
   2982      switch (t.params.trait) {
   2983        case 'f32': {
   2984          ulp_error = 3 + 2 * Math.abs(t.params.input);
   2985          break;
   2986        }
   2987        case 'f16': {
   2988          ulp_error = 1 + 2 * Math.abs(t.params.input);
   2989          break;
   2990        }
   2991      }
   2992      return ulp_error * trait.oneULP(x);
   2993    };
   2994 
   2995    const expected = trait.toInterval(applyError(t.params.expected, error));
   2996 
   2997    const got = trait.exp2Interval(t.params.input);
   2998    t.expect(
   2999      objectEquals(expected, got),
   3000      `${t.params.trait}.exp2Interval(${t.params.input}) returned ${got}. Expected ${expected}`
   3001    );
   3002  });
   3003 
   3004 // Large but still representable integer
   3005 const kFloorIntervalCases = {
   3006  f32: [
   3007    { input: 2 ** 30, expected: 2 ** 30 },
   3008    { input: -(2 ** 30), expected: -(2 ** 30) },
   3009    { input: 0x80000000, expected: 0x80000000 }, // https://github.com/gpuweb/cts/issues/2766
   3010  ],
   3011  f16: [
   3012    { input: 2 ** 14, expected: 2 ** 14 },
   3013    { input: -(2 ** 14), expected: -(2 ** 14) },
   3014    { input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
   3015  ],
   3016  abstract: [
   3017    { input: 2 ** 62, expected: 2 ** 62 },
   3018    { input: -(2 ** 62), expected: -(2 ** 62) },
   3019    {
   3020      input: 0x8000_0000_0000_0000,
   3021      expected: 0x8000_0000_0000_0000,
   3022    }, // https://github.com/gpuweb/cts/issues/2766
   3023  ],
   3024 } as const;
   3025 
   3026 g.test('floorInterval')
   3027  .params(u =>
   3028    u
   3029      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   3030      .beginSubcases()
   3031      .expandWithParams<ScalarToIntervalCase>(p => {
   3032        const constants = FP[p.trait].constants();
   3033        // prettier-ignore
   3034        return [
   3035          { input: 0, expected: 0 },
   3036          { input: 0.1, expected: 0 },
   3037          { input: 0.9, expected: 0 },
   3038          { input: 1.0, expected: 1 },
   3039          { input: 1.1, expected: 1 },
   3040          { input: 1.9, expected: 1 },
   3041          { input: -0.1, expected: -1 },
   3042          { input: -0.9, expected: -1 },
   3043          { input: -1.0, expected: -1 },
   3044          { input: -1.1, expected: -2 },
   3045          { input: -1.9, expected: -2 },
   3046 
   3047          // Edge cases
   3048          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3049          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3050          { input: constants.positive.max, expected: constants.positive.max },
   3051          { input: constants.positive.min, expected: 0 },
   3052          { input: constants.negative.min, expected: constants.negative.min },
   3053          { input: constants.negative.max, expected: -1 },
   3054          ...kFloorIntervalCases[p.trait],
   3055 
   3056          // Subnormals
   3057          { input: constants.positive.subnormal.max, expected: 0 },
   3058          { input: constants.positive.subnormal.min, expected: 0 },
   3059          { input: constants.negative.subnormal.min, expected: [-1, 0] },
   3060          { input: constants.negative.subnormal.max, expected: [-1, 0] },
   3061        ];
   3062      })
   3063  )
   3064  .fn(t => {
   3065    const trait = FP[t.params.trait];
   3066    const expected = trait.toInterval(t.params.expected);
   3067    const got = trait.floorInterval(t.params.input);
   3068    t.expect(
   3069      objectEquals(expected, got),
   3070      `${t.params.trait}.floorInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3071    );
   3072  });
   3073 
   3074 // prettier-ignore
   3075 const kFractIntervalCases = {
   3076  f32: [
   3077    { input: 0.1, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x3dcccccd)), reinterpretU32AsF32(0x3dcccccd)] }, // ~0.1
   3078    { input: 0.9, expected: [reinterpretU32AsF32(0x3f666666), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0x3f666666))] },  // ~0.9
   3079    { input: 1.1, expected: [reinterpretU32AsF32(0x3dccccc0), reinterpretU32AsF32(0x3dccccd0)] }, // ~0.1
   3080    { input: -0.1, expected: [reinterpretU32AsF32(0x3f666666), kPlusOneULPFunctions['f32'](reinterpretU32AsF32(0x3f666666))] },  // ~0.9
   3081    { input: -0.9, expected: [reinterpretU32AsF32(0x3dccccc8), reinterpretU32AsF32(0x3dccccd0)] }, // ~0.1
   3082    { input: -1.1, expected: [reinterpretU32AsF32(0x3f666666), reinterpretU32AsF32(0x3f666668)] }, // ~0.9
   3083 
   3084    // https://github.com/gpuweb/cts/issues/2766
   3085    { input: 0x80000000, expected: 0 },
   3086  ] as ScalarToIntervalCase[],
   3087  f16: [
   3088    { input: 0.1, expected: [reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0x2e67)] }, // ~0.1
   3089    { input: 0.9, expected: [reinterpretU16AsF16(0x3b33), reinterpretU16AsF16(0x3b34)] },  // ~0.9
   3090    { input: 1.1, expected: [reinterpretU16AsF16(0x2e60), reinterpretU16AsF16(0x2e70)] }, // ~0.1
   3091    { input: -0.1, expected: [reinterpretU16AsF16(0x3b33), reinterpretU16AsF16(0x3b34)] },  // ~0.9
   3092    { input: -0.9, expected: [reinterpretU16AsF16(0x2e60), reinterpretU16AsF16(0x2e68)] }, // ~0.1
   3093    { input: -1.1, expected: [reinterpretU16AsF16(0x3b32), reinterpretU16AsF16(0x3b34)] }, // ~0.9
   3094    { input: 658.5, expected: 0.5 },
   3095  ] as ScalarToIntervalCase[],
   3096  abstract: [
   3097    { input: 0.1, expected: reinterpretU64AsF64(0x3fb999999999999an) },
   3098    { input: 0.9, expected: reinterpretU64AsF64(0x3feccccccccccccdn) },
   3099    { input: 1.1, expected: reinterpretU64AsF64(0x3fb99999999999a0n) },
   3100    { input: -0.1, expected: reinterpretU64AsF64(0x3feccccccccccccdn) },
   3101    { input: -0.9, expected: reinterpretU64AsF64(0x3fb9999999999998n) },
   3102    { input: -1.1, expected: reinterpretU64AsF64(0x3fecccccccccccccn) },
   3103 
   3104    // https://github.com/gpuweb/cts/issues/2766
   3105    { input: 0x80000000, expected: 0 },
   3106 
   3107    // https://github.com/gpuweb/gpuweb/issues/4523
   3108    { input: 3937509.87755102, expected: [0, 0.75] },
   3109  ] as ScalarToIntervalCase[],
   3110 
   3111 } as const;
   3112 
   3113 g.test('fractInterval')
   3114  .params(u =>
   3115    u
   3116      .combine('trait', ['f32', 'f16'] as const)
   3117      .beginSubcases()
   3118      .expandWithParams<ScalarToIntervalCase>(p => {
   3119        const constants = FP[p.trait].constants();
   3120        // prettier-ignore
   3121        return [
   3122          { input: 0, expected: 0 },
   3123          { input: 1.0, expected: 0 },
   3124          { input: -1.0, expected: 0 },
   3125 
   3126          ...kFractIntervalCases[p.trait],
   3127 
   3128          // Edge cases
   3129          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3130          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3131          { input: constants.positive.max, expected: 0 },
   3132          { input: constants.positive.min, expected: constants.positive.min },
   3133          { input: constants.negative.min, expected: 0 },
   3134          { input: constants.negative.max, expected: [constants.positive.less_than_one, 1.0] },
   3135        ];
   3136      })
   3137  )
   3138  .fn(t => {
   3139    const trait = FP[t.params.trait];
   3140    const expected = trait.toInterval(t.params.expected);
   3141    const got = trait.fractInterval(t.params.input);
   3142    t.expect(
   3143      objectEquals(expected, got),
   3144      `${t.params.trait}.fractInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3145    );
   3146  });
   3147 
   3148 // prettier-ignore
   3149 const kInverseSqrtIntervalCases = {
   3150  f32: [
   3151    // 0.04 rounded to f32 0x3D23D70A or 0x3D23D70B,
   3152    // 1/sqrt(0x3D23D70B)=4.9999998230487200185270893769213 rounded to f32 0x409FFFFF or 0x40A00000,
   3153    // 1/sqrt(0x3D23D70A)=5.0000000558793553117506910583908 rounded to f32 0x40A00000 or 0x40A00001.
   3154    { input: 0.04, expected: [reinterpretU32AsF32(0x409FFFFF), reinterpretU32AsF32(0x40A00001)] },  // ~5.0
   3155    // Maximum f32 0x7F7FFFFF = 3.4028234663852886e+38,
   3156    // 1/sqrt(0x7F7FFFFF)=5.4210110239862427800382690921791e-20 rounded to f32 0x1F800000 or 0x1F800001
   3157    { input: kValue.f32.positive.max, expected: [reinterpretU32AsF32(0x1f800000), reinterpretU32AsF32(0x1f800001)] },  // ~5.421...e-20
   3158  ] as ScalarToIntervalCase[],
   3159  f16: [
   3160    // 0.04 rounded to f16 0x291E or 0x291F,
   3161    // 1/sqrt(0x291F)=4.9994660279328446295684795818427 rounded to f16 0x44FF or 0x4500,
   3162    // 1/sqrt(0x291E)=5.001373857053206453045376503367 rounded to f16 0x4500 or 0x4501.
   3163    { input: 0.04, expected: [reinterpretU16AsF16(0x44FF), reinterpretU16AsF16(0x4501)] },  // ~5.0
   3164    // Maximum f16 0x7BFF = 65504,
   3165    // 1/sqrt(0x7BFF)=0.00390720402370454101997160826062 rounded to f16 0x1C00 or 0x1C01
   3166    { input: kValue.f16.positive.max, expected: [reinterpretU16AsF16(0x1c00), reinterpretU16AsF16(0x1c01)] },  // ~3.9072...e-3
   3167  ] as ScalarToIntervalCase[],
   3168 } as const;
   3169 
   3170 g.test('inverseSqrtInterval')
   3171  .params(u =>
   3172    u
   3173      .combine('trait', ['f32', 'f16'] as const)
   3174      .beginSubcases()
   3175      .expandWithParams<ScalarToIntervalCase>(p => {
   3176        const trait = FP[p.trait];
   3177        const constants = trait.constants();
   3178        // Note that the 2 ULP error is not included here.
   3179        // prettier-ignore
   3180        return [
   3181          // Exactly representable cases
   3182          { input: 1, expected: 1 },
   3183          { input: 0.25, expected: 2 },
   3184          { input: 64, expected: 0.125 },
   3185 
   3186          // Cases that input and/or result not exactly representable
   3187          ...kInverseSqrtIntervalCases[p.trait],
   3188          // 1/sqrt(100.0)=0.1, rounded to corresponding trait
   3189          { input: 100, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   3190 
   3191          // Out of definition domain
   3192          { input: -1, expected: kUnboundedEndpoints },
   3193          { input: 0, expected: kUnboundedEndpoints },
   3194          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3195        ];
   3196      })
   3197  )
   3198  .fn(t => {
   3199    const trait = FP[t.params.trait];
   3200 
   3201    const error = (n: number): number => {
   3202      return 2 * trait.oneULP(n);
   3203    };
   3204 
   3205    const expected = trait.toInterval(applyError(t.params.expected, error));
   3206 
   3207    const got = trait.inverseSqrtInterval(t.params.input);
   3208    t.expect(
   3209      objectEquals(expected, got),
   3210      `${t.params.trait}.inverseSqrtInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3211    );
   3212  });
   3213 
   3214 // Expectation interval of 1/inverseSqrt(sum(x[i]^2)) on some special values array x for certain
   3215 // float traits, used as expectation for `length` and `distance`.
   3216 // These cases are hard coded, since the error intervals are difficult to express in a closed
   3217 // human-readable form due to the inherited nature of the errors.
   3218 // prettier-ignore
   3219 const kRootSumSquareExpectationInterval = {
   3220  f32: {
   3221    '[0.1]': [reinterpretU64AsF64(0x3fb9_9998_9000_0000n), reinterpretU64AsF64(0x3fb9_999a_7000_0000n)],  // ~0.1
   3222    '[1.0]' : [reinterpretU64AsF64(0x3fef_ffff_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_9000_0000n)],  // ~1.0
   3223    '[10]' : [reinterpretU64AsF64(0x4023_ffff_7000_0000n), reinterpretU64AsF64(0x4024_0000_b000_0000n)],  // ~10
   3224    '[1.0, 1.0]' : [reinterpretU64AsF64(0x3ff6_a09d_b000_0000n), reinterpretU64AsF64(0x3ff6_a09f_1000_0000n)],  // ~√2
   3225    '[1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3ffb_b67a_1000_0000n), reinterpretU64AsF64(0x3ffb_b67b_b000_0000n)],  // ~√3
   3226    '[1.0, 1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3fff_ffff_7000_0000n), reinterpretU64AsF64(0x4000_0000_9000_0000n)],  // ~2
   3227  } as {[s: string]: IntervalEndpoints},
   3228  f16: {
   3229    '[0.1]': [reinterpretU64AsF64(0x3fb9_7e00_0000_0000n), reinterpretU64AsF64(0x3fb9_b600_0000_0000n)],  // ~0.1
   3230    '[1.0]' : [reinterpretU64AsF64(0x3fef_ee00_0000_0000n), reinterpretU64AsF64(0x3ff0_1200_0000_0000n)],  // ~1.0
   3231    '[10]' : [reinterpretU64AsF64(0x4023_ea00_0000_0000n), reinterpretU64AsF64(0x4024_1200_0000_0000n)],  // ~10
   3232    '[1.0, 1.0]' : [reinterpretU64AsF64(0x3ff6_8a00_0000_0000n), reinterpretU64AsF64(0x3ff6_b600_0000_0000n)],  // ~√2
   3233    '[1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3ffb_9a00_0000_0000n), reinterpretU64AsF64(0x3ffb_d200_0000_0000n)],  // ~√3
   3234    '[1.0, 1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3fff_ee00_0000_0000n), reinterpretU64AsF64(0x4000_1200_0000_0000n)],  // ~2
   3235  } as {[s: string]: IntervalEndpoints},
   3236 } as const;
   3237 
   3238 g.test('lengthIntervalScalar')
   3239  .params(u =>
   3240    u
   3241      .combine('trait', ['f32', 'f16'] as const)
   3242      .beginSubcases()
   3243      .expandWithParams<ScalarToIntervalCase>(p => {
   3244        const trait = FP[p.trait];
   3245        const constants = trait.constants();
   3246        // prettier-ignore
   3247        return [
   3248          {input: 1.0, expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   3249          {input: -1.0, expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   3250          {input: 0.1, expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   3251          {input: -0.1, expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   3252          {input: 10.0, expected: kRootSumSquareExpectationInterval[p.trait]['[10]'] },  // ~10
   3253          {input: -10.0, expected: kRootSumSquareExpectationInterval[p.trait]['[10]'] },  // ~10
   3254 
   3255          // length(0) = kUnboundedEndpoints, because length uses sqrt, which is defined as 1/inversesqrt
   3256          {input: 0, expected: kUnboundedEndpoints },
   3257 
   3258          // Subnormal Cases
   3259          { input: constants.negative.subnormal.min, expected: kUnboundedEndpoints },
   3260          { input: constants.negative.subnormal.max, expected: kUnboundedEndpoints },
   3261          { input: constants.positive.subnormal.min, expected: kUnboundedEndpoints },
   3262          { input: constants.positive.subnormal.max, expected: kUnboundedEndpoints },
   3263 
   3264          // Edge cases
   3265          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3266          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3267          { input: constants.negative.min, expected: kUnboundedEndpoints },
   3268          { input: constants.negative.max, expected: kUnboundedEndpoints },
   3269          { input: constants.positive.min, expected: kUnboundedEndpoints },
   3270          { input: constants.positive.max, expected: kUnboundedEndpoints },
   3271        ];
   3272      })
   3273  )
   3274  .fn(t => {
   3275    const trait = FP[t.params.trait];
   3276    const expected = trait.toInterval(t.params.expected);
   3277    const got = trait.lengthInterval(t.params.input);
   3278    t.expect(
   3279      objectEquals(expected, got),
   3280      `${t.params.trait}.lengthInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3281    );
   3282  });
   3283 
   3284 // prettier-ignore
   3285 const kLogIntervalCases = {
   3286  f32: [
   3287    // kValue.f32.positive.e is 0x402DF854 = 2.7182817459106445,
   3288    // log(0x402DF854) = 0.99999996963214000677592342891704 rounded to f32 0x3F7FFFFF or 0x3F800000 = 1.0
   3289    { input: kValue.f32.positive.e, expected: [kMinusOneULPFunctions['f32'](1.0), 1.0] },
   3290    // kValue.f32.positive.max is 0x7F7FFFFF = 3.4028234663852886e+38,
   3291    // log(0x7F7FFFFF) = 88.72283905206835305421152826479 rounded to f32 0x42B17217 or 0x42B17218.
   3292    { input: kValue.f32.positive.max, expected: [kMinusOneULPFunctions['f32'](reinterpretU32AsF32(0x42b17218)), reinterpretU32AsF32(0x42b17218)] },
   3293  ] as ScalarToIntervalCase[],
   3294  f16: [
   3295    // kValue.f16.positive.e is 0x416F = 2.716796875,
   3296    // log(0x416F) = 0.99945356688393512460279716546501 rounded to f16 0x3BFE or 0x3BFF.
   3297    { input: kValue.f16.positive.e, expected: [reinterpretU16AsF16(0x3bfe), reinterpretU16AsF16(0x3bff)] },
   3298    // kValue.f16.positive.max is 0x7BFF = 65504,
   3299    // log(0x7BFF) = 11.089866488461016076210728979771 rounded to f16 0x498B or 0x498C.
   3300    { input: kValue.f16.positive.max, expected: [reinterpretU16AsF16(0x498b), reinterpretU16AsF16(0x498c)] },
   3301  ] as ScalarToIntervalCase[],
   3302 } as const;
   3303 
   3304 g.test('logInterval')
   3305  .params(u =>
   3306    u
   3307      .combine('trait', ['f32', 'f16'] as const)
   3308      .beginSubcases()
   3309      .expandWithParams<ScalarToIntervalCase>(p => {
   3310        // prettier-ignore
   3311        return [
   3312          { input: -1, expected: kUnboundedEndpoints },
   3313          { input: 0, expected: kUnboundedEndpoints },
   3314          { input: 1, expected: 0 },
   3315          ...kLogIntervalCases[p.trait],
   3316        ];
   3317      })
   3318  )
   3319  .fn(t => {
   3320    const trait = FP[t.params.trait];
   3321    const abs_error = t.params.trait === 'f32' ? 2 ** -21 : 2 ** -7;
   3322    const error = (n: number): number => {
   3323      if (t.params.input >= 0.5 && t.params.input <= 2.0) {
   3324        return abs_error;
   3325      }
   3326      return 3 * trait.oneULP(n);
   3327    };
   3328 
   3329    const expected = trait.toInterval(applyError(t.params.expected, error));
   3330 
   3331    const got = trait.logInterval(t.params.input);
   3332    t.expect(
   3333      objectEquals(expected, got),
   3334      `${t.params.trait}.logInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3335    );
   3336  });
   3337 
   3338 // prettier-ignore
   3339 const kLog2IntervalCases = {
   3340  f32: [
   3341    // kValue.f32.positive.max is 0x7F7FFFFF = 3.4028234663852886e+38,
   3342    // log2(0x7F7FFFFF) = 127.99999991400867200665269600978 rounded to f32 0x42FFFFFF or 0x43000000 = 128.0
   3343    { input: kValue.f32.positive.max, expected: [kMinusOneULPFunctions['f32'](128.0), 128.0] },
   3344  ] as ScalarToIntervalCase[],
   3345  f16: [
   3346    // kValue.f16.positive.max is 0x7BFF = 65504,
   3347    // log2(0x7BFF) = 15.999295387023410627258428389903 rounded to f16 0x4BFF or 0x4C00 = 16.0
   3348    { input: kValue.f16.positive.max, expected: [kMinusOneULPFunctions['f16'](16.0), 16.0] },
   3349  ] as ScalarToIntervalCase[],
   3350 } as const;
   3351 
   3352 g.test('log2Interval')
   3353  .params(u =>
   3354    u
   3355      .combine('trait', ['f32', 'f16'] as const)
   3356      .beginSubcases()
   3357      .expandWithParams<ScalarToIntervalCase>(p => {
   3358        // prettier-ignore
   3359        return [
   3360          { input: -1, expected: kUnboundedEndpoints },
   3361          { input: 0, expected: kUnboundedEndpoints },
   3362          { input: 1, expected: 0 },
   3363          { input: 2, expected: 1 },
   3364          { input: 16, expected: 4 },
   3365          ...kLog2IntervalCases[p.trait],
   3366        ];
   3367      })
   3368  )
   3369  .fn(t => {
   3370    const trait = FP[t.params.trait];
   3371    const abs_error = t.params.trait === 'f32' ? 2 ** -21 : 2 ** -7;
   3372    const error = (n: number): number => {
   3373      if (t.params.input >= 0.5 && t.params.input <= 2.0) {
   3374        return abs_error;
   3375      }
   3376      return 3 * trait.oneULP(n);
   3377    };
   3378 
   3379    const expected = trait.toInterval(applyError(t.params.expected, error));
   3380 
   3381    const got = trait.log2Interval(t.params.input);
   3382    t.expect(
   3383      objectEquals(expected, got),
   3384      `${t.params.trait}.log2Interval(${t.params.input}) returned ${got}. Expected ${expected}`
   3385    );
   3386  });
   3387 
   3388 g.test('negationInterval')
   3389  .params(u =>
   3390    u
   3391      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   3392      .beginSubcases()
   3393      .expandWithParams<ScalarToIntervalCase>(p => {
   3394        const trait = FP[p.trait];
   3395        const constants = trait.constants();
   3396        // prettier-ignore
   3397        return [
   3398          // Edge cases
   3399          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3400          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3401          { input: constants.positive.max, expected: constants.negative.min },
   3402          { input: constants.positive.min, expected: constants.negative.max },
   3403          { input: constants.negative.min, expected: constants.positive.max },
   3404          { input: constants.negative.max, expected: constants.positive.min },
   3405 
   3406          // Normals
   3407          { input: 0, expected: 0 },
   3408          { input: 1.0, expected: -1.0 },
   3409          { input: -1.0, expected: 1 },
   3410          { input: 0.1, expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] }, // ~-0.1
   3411          { input: 1.9, expected: kConstantCorrectlyRoundedExpectation[p.trait]['-1.9'] },  // ~-1.9
   3412          { input: -0.1, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] }, // ~0.1
   3413          { input: -1.9, expected: kConstantCorrectlyRoundedExpectation[p.trait]['1.9'] },  // ~1.9
   3414 
   3415          // Subnormals
   3416          { input: constants.positive.subnormal.max, expected: [constants.negative.subnormal.min, 0] },
   3417          { input: constants.positive.subnormal.min, expected: [constants.negative.subnormal.max, 0] },
   3418          { input: constants.negative.subnormal.min, expected: [0, constants.positive.subnormal.max] },
   3419          { input: constants.negative.subnormal.max, expected: [0, constants.positive.subnormal.min] },
   3420        ];
   3421      })
   3422  )
   3423  .fn(t => {
   3424    const trait = FP[t.params.trait];
   3425    const expected = trait.toInterval(t.params.expected);
   3426    const got = trait.negationInterval(t.params.input);
   3427    t.expect(
   3428      objectEquals(expected, got),
   3429      `${t.params.trait}.negationInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3430    );
   3431  });
   3432 
   3433 g.test('quantizeToF16Interval')
   3434  .paramsSubcasesOnly<ScalarToIntervalCase>(
   3435    // prettier-ignore
   3436    [
   3437      { input: kValue.f32.negative.infinity, expected: kUnboundedEndpoints },
   3438      { input: kValue.f32.negative.min, expected: kUnboundedEndpoints },
   3439      { input: kValue.f16.negative.min, expected: kValue.f16.negative.min },
   3440      { input: -1.9, expected: kConstantCorrectlyRoundedExpectation['f16']['-1.9'] },  // ~-1.9
   3441      { input: -1, expected: -1 },
   3442      { input: -0.1, expected: kConstantCorrectlyRoundedExpectation['f16']['-0.1'] },  // ~-0.1
   3443      { input: kValue.f16.negative.max, expected: kValue.f16.negative.max },
   3444      { input: kValue.f16.negative.subnormal.min, expected: [kValue.f16.negative.subnormal.min, 0] },
   3445      { input: kValue.f16.negative.subnormal.max, expected: [kValue.f16.negative.subnormal.max, 0] },
   3446      { input: kValue.f32.negative.subnormal.max, expected: [kValue.f16.negative.subnormal.max, 0] },
   3447      { input: 0, expected: 0 },
   3448      { input: kValue.f32.positive.subnormal.min, expected: [0, kValue.f16.positive.subnormal.min] },
   3449      { input: kValue.f16.positive.subnormal.min, expected: [0, kValue.f16.positive.subnormal.min] },
   3450      { input: kValue.f16.positive.subnormal.max, expected: [0, kValue.f16.positive.subnormal.max] },
   3451      { input: kValue.f16.positive.min, expected: kValue.f16.positive.min },
   3452      { input: 0.1, expected: kConstantCorrectlyRoundedExpectation['f16']['0.1'] },  // ~0.1
   3453      { input: 1, expected: 1 },
   3454      { input: 1.9, expected: kConstantCorrectlyRoundedExpectation['f16']['1.9'] },  // ~1.9
   3455      { input: kValue.f16.positive.max, expected: kValue.f16.positive.max },
   3456      { input: kValue.f32.positive.max, expected: kUnboundedEndpoints },
   3457      { input: kValue.f32.positive.infinity, expected: kUnboundedEndpoints },
   3458    ]
   3459  )
   3460  .fn(t => {
   3461    const expected = FP.f32.toInterval(t.params.expected);
   3462 
   3463    const got = FP.f32.quantizeToF16Interval(t.params.input);
   3464    t.expect(
   3465      objectEquals(expected, got),
   3466      `f32.quantizeToF16Interval(${t.params.input}) returned ${got}. Expected ${expected}`
   3467    );
   3468  });
   3469 
   3470 // prettier-ignore
   3471 const kRadiansIntervalCases = {
   3472  f32: [
   3473    { input: -180, expected: [kMinusOneULPFunctions['f32'](kValue.f32.negative.pi.whole), kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.whole)] },
   3474    { input: -135, expected: [kMinusOneULPFunctions['f32'](kValue.f32.negative.pi.three_quarters), kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.three_quarters)] },
   3475    { input: -90, expected: [kMinusOneULPFunctions['f32'](kValue.f32.negative.pi.half), kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.half)] },
   3476    { input: -60, expected: [kMinusOneULPFunctions['f32'](kValue.f32.negative.pi.third), kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.third)] },
   3477    { input: -45, expected: [kMinusOneULPFunctions['f32'](kValue.f32.negative.pi.quarter), kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.quarter)] },
   3478    { input: -30, expected: [kMinusOneULPFunctions['f32'](kValue.f32.negative.pi.sixth), kPlusOneULPFunctions['f32'](kValue.f32.negative.pi.sixth)] },
   3479    { input: 30, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.sixth), kPlusOneULPFunctions['f32'](kValue.f32.positive.pi.sixth)] },
   3480    { input: 45, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.quarter), kPlusOneULPFunctions['f32'](kValue.f32.positive.pi.quarter)] },
   3481    { input: 60, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.third), kPlusOneULPFunctions['f32'](kValue.f32.positive.pi.third)] },
   3482    { input: 90, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.half), kPlusOneULPFunctions['f32'](kValue.f32.positive.pi.half)] },
   3483    { input: 135, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.three_quarters), kPlusOneULPFunctions['f32'](kValue.f32.positive.pi.three_quarters)] },
   3484    { input: 180, expected: [kMinusOneULPFunctions['f32'](kValue.f32.positive.pi.whole), kPlusOneULPFunctions['f32'](kValue.f32.positive.pi.whole)] },
   3485  ] as ScalarToIntervalCase[],
   3486  f16: [
   3487    { input: -180, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.whole), kPlusOneULPFunctions['f16'](kValue.f16.negative.pi.whole)] },
   3488    { input: -135, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.three_quarters), kPlusOneULPFunctions['f16'](kValue.f16.negative.pi.three_quarters)] },
   3489    { input: -90, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.half), kPlusOneULPFunctions['f16'](kValue.f16.negative.pi.half)] },
   3490    { input: -60, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.third), kPlusOneULPFunctions['f16'](kValue.f16.negative.pi.third)] },
   3491    { input: -45, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.quarter), kPlusOneULPFunctions['f16'](kValue.f16.negative.pi.quarter)] },
   3492    { input: -30, expected: [kMinusOneULPFunctions['f16'](kValue.f16.negative.pi.sixth), kPlusOneULPFunctions['f16'](kValue.f16.negative.pi.sixth)] },
   3493    { input: 30, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.sixth), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.sixth)] },
   3494    { input: 45, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.quarter), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.quarter)] },
   3495    { input: 60, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.third), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.third)] },
   3496    { input: 90, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.half), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.half)] },
   3497    { input: 135, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.three_quarters), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.three_quarters)] },
   3498    { input: 180, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.whole), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.whole)] },
   3499  ] as ScalarToIntervalCase[],
   3500 } as const;
   3501 
   3502 g.test('radiansInterval')
   3503  .params(u =>
   3504    u
   3505      .combine('trait', ['f32', 'f16'] as const)
   3506      .beginSubcases()
   3507      .expandWithParams<ScalarToIntervalCase>(p => {
   3508        const trait = p.trait;
   3509        const constants = FP[trait].constants();
   3510        // prettier-ignore
   3511        return [
   3512          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3513          { input: 0, expected: 0 },
   3514          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3515          ...kRadiansIntervalCases[trait]
   3516        ];
   3517      })
   3518  )
   3519  .fn(t => {
   3520    const trait = FP[t.params.trait];
   3521    const expected = trait.toInterval(t.params.expected);
   3522    const got = trait.radiansInterval(t.params.input);
   3523    t.expect(
   3524      objectEquals(expected, got),
   3525      `${t.params.trait}.radiansInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3526    );
   3527  });
   3528 
   3529 // Large but still representable integer
   3530 const kRoundIntervalCases = {
   3531  f32: [
   3532    { input: 2 ** 30, expected: 2 ** 30 },
   3533    { input: -(2 ** 30), expected: -(2 ** 30) },
   3534    { input: 0x8000_0000, expected: 0x8000_0000 }, // https://github.com/gpuweb/cts/issues/2766
   3535  ],
   3536  f16: [
   3537    { input: 2 ** 14, expected: 2 ** 14 },
   3538    { input: -(2 ** 14), expected: -(2 ** 14) },
   3539    { input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
   3540  ],
   3541  abstract: [
   3542    { input: 2 ** 62, expected: 2 ** 62 },
   3543    { input: -(2 ** 62), expected: -(2 ** 62) },
   3544    {
   3545      input: 0x8000_0000_0000_0000,
   3546      expected: 0x8000_0000_0000_0000,
   3547    }, // https://github.com/gpuweb/cts/issues/2766
   3548  ],
   3549 } as const;
   3550 
   3551 g.test('roundInterval')
   3552  .params(u =>
   3553    u
   3554      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   3555      .beginSubcases()
   3556      .expandWithParams<ScalarToIntervalCase>(p => {
   3557        const constants = FP[p.trait].constants();
   3558        // prettier-ignore
   3559        return [
   3560          { input: 0, expected: 0 },
   3561          { input: 0.1, expected: 0 },
   3562          { input: 0.5, expected: 0 },  // Testing tie breaking
   3563          { input: 0.9, expected: 1 },
   3564          { input: 1.0, expected: 1 },
   3565          { input: 1.1, expected: 1 },
   3566          { input: 1.5, expected: 2 },  // Testing tie breaking
   3567          { input: 1.9, expected: 2 },
   3568          { input: -0.1, expected: 0 },
   3569          { input: -0.5, expected: 0 },  // Testing tie breaking
   3570          { input: -0.9, expected: -1 },
   3571          { input: -1.0, expected: -1 },
   3572          { input: -1.1, expected: -1 },
   3573          { input: -1.5, expected: -2 },  // Testing tie breaking
   3574          { input: -1.9, expected: -2 },
   3575 
   3576          // Edge cases
   3577          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3578          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3579          { input: constants.positive.max, expected: constants.positive.max },
   3580          { input: constants.positive.min, expected: 0 },
   3581          { input: constants.negative.min, expected: constants.negative.min },
   3582          { input: constants.negative.max, expected: 0 },
   3583          ...kRoundIntervalCases[p.trait],
   3584 
   3585          // Subnormals
   3586          { input: constants.positive.subnormal.max, expected: 0 },
   3587          { input: constants.positive.subnormal.min, expected: 0 },
   3588          { input: constants.negative.subnormal.min, expected: 0 },
   3589          { input: constants.negative.subnormal.max, expected: 0 },
   3590        ];
   3591      })
   3592  )
   3593  .fn(t => {
   3594    const trait = FP[t.params.trait];
   3595    const expected = trait.toInterval(t.params.expected);
   3596    const got = trait.roundInterval(t.params.input);
   3597    t.expect(
   3598      objectEquals(expected, got),
   3599      `${t.params.trait}.roundInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3600    );
   3601  });
   3602 
   3603 g.test('saturateInterval')
   3604  .params(u =>
   3605    u
   3606      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   3607      .beginSubcases()
   3608      .expandWithParams<ScalarToIntervalCase>(p => {
   3609        const constants = FP[p.trait].constants();
   3610        // prettier-ignore
   3611        return [
   3612          // Normals
   3613          { input: 0, expected: 0 },
   3614          { input: 0.1, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   3615          { input: 1, expected: 1.0 },
   3616          { input: -0.1, expected: 0 },
   3617          { input: -1, expected: 0 },
   3618          { input: -10, expected: 0 },
   3619          { input: 10, expected: 1.0 },
   3620          { input: 11.1, expected: 1.0 },
   3621          { input: constants.positive.max, expected: 1.0 },
   3622          { input: constants.positive.min, expected: constants.positive.min },
   3623          { input: constants.negative.max, expected: 0.0 },
   3624          { input: constants.negative.min, expected: 0.0 },
   3625 
   3626          // Subnormals
   3627          { input: constants.positive.subnormal.max, expected: [0.0, constants.positive.subnormal.max] },
   3628          { input: constants.positive.subnormal.min, expected: [0.0, constants.positive.subnormal.min] },
   3629          { input: constants.negative.subnormal.min, expected: [constants.negative.subnormal.min, 0.0] },
   3630          { input: constants.negative.subnormal.max, expected: [constants.negative.subnormal.max, 0.0] },
   3631 
   3632          // Infinities
   3633          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3634          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3635        ];
   3636      })
   3637  )
   3638  .fn(t => {
   3639    const trait = FP[t.params.trait];
   3640    const expected = trait.toInterval(t.params.expected);
   3641    const got = trait.saturateInterval(t.params.input);
   3642    t.expect(
   3643      objectEquals(expected, got),
   3644      `${t.params.trait}.saturationInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3645    );
   3646  });
   3647 
   3648 g.test('signInterval')
   3649  .params(u =>
   3650    u
   3651      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   3652      .beginSubcases()
   3653      .expandWithParams<ScalarToIntervalCase>(p => {
   3654        const constants = FP[p.trait].constants();
   3655        // prettier-ignore
   3656        return [
   3657          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3658          { input: constants.negative.min, expected: -1 },
   3659          { input: -10, expected: -1 },
   3660          { input: -1, expected: -1 },
   3661          { input: -0.1, expected: -1 },
   3662          { input: constants.negative.max, expected:  -1 },
   3663          { input: constants.negative.subnormal.min, expected: [-1, 0] },
   3664          { input: constants.negative.subnormal.max, expected: [-1, 0] },
   3665          { input: 0, expected: 0 },
   3666          { input: constants.positive.subnormal.max, expected: [0, 1] },
   3667          { input: constants.positive.subnormal.min, expected: [0, 1] },
   3668          { input: constants.positive.min, expected: 1 },
   3669          { input: 0.1, expected: 1 },
   3670          { input: 1, expected: 1 },
   3671          { input: 10, expected: 1 },
   3672          { input: constants.positive.max, expected: 1 },
   3673          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3674        ];
   3675      })
   3676  )
   3677  .fn(t => {
   3678    const trait = FP[t.params.trait];
   3679    const expected = trait.toInterval(t.params.expected);
   3680    const got = trait.signInterval(t.params.input);
   3681    t.expect(
   3682      objectEquals(expected, got),
   3683      `${t.params.trait}.signInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3684    );
   3685  });
   3686 
   3687 g.test('sinInterval')
   3688  .params(u =>
   3689    u
   3690      .combine('trait', ['f32', 'f16'] as const)
   3691      .beginSubcases()
   3692      .expandWithParams<ScalarToIntervalCase>(p => {
   3693        const constants = FP[p.trait].constants();
   3694        // prettier-ignore
   3695        return [
   3696          // This test does not include some common cases, i.e. f(x = -Ï€|Ï€) = 0,
   3697          // because the difference between true x and x as a f32 is sufficiently
   3698          // large, such that the high slope of f @ x causes the results to be
   3699          // substantially different, so instead of getting 0 you get a value on the
   3700          // order of 10^-8 away from it, thus difficult to express in a
   3701          // human-readable manner.
   3702          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3703          { input: constants.negative.min, expected: kUnboundedEndpoints },
   3704          { input: constants.negative.pi.half, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
   3705          { input: 0, expected: 0 },
   3706          { input: constants.positive.pi.half, expected: [kMinusOneULPFunctions[p.trait](1), 1] },
   3707          { input: constants.positive.max, expected: kUnboundedEndpoints },
   3708          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3709        ];
   3710      })
   3711  )
   3712  .fn(t => {
   3713    const trait = FP[t.params.trait];
   3714 
   3715    const error = (_: number): number => {
   3716      return t.params.trait === 'f32' ? 2 ** -11 : 2 ** -7;
   3717    };
   3718 
   3719    const expected = trait.toInterval(applyError(t.params.expected, error));
   3720 
   3721    const got = trait.sinInterval(t.params.input);
   3722    t.expect(
   3723      objectEquals(expected, got),
   3724      `${t.params.trait}.sinInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3725    );
   3726  });
   3727 
   3728 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   3729 // human-readable form due to the inherited nature of the errors.
   3730 // prettier-ignore
   3731 const kSinhIntervalCases = {
   3732  f32: [
   3733    { input: -1, expected: [reinterpretU32AsF32(0xbf966d05), reinterpretU32AsF32(0xbf966cf8)] },  // ~-1.175...
   3734    { input: 0, expected: [reinterpretU32AsF32(0xb4600000), reinterpretU32AsF32(0x34600000)] },  // ~0
   3735    { input: 1, expected: [reinterpretU32AsF32(0x3f966cf8), reinterpretU32AsF32(0x3f966d05)] },  // ~1.175...
   3736  ] as ScalarToIntervalCase[],
   3737  f16: [
   3738    { input: -1, expected: [reinterpretU16AsF16(0xbcb8), reinterpretU16AsF16(0xbcaf)] },  // ~-1.175...
   3739    { input: 0, expected: [reinterpretU16AsF16(0x9200), reinterpretU16AsF16(0x1200)] },  // ~0
   3740    { input: 1, expected: [reinterpretU16AsF16(0x3caf), reinterpretU16AsF16(0x3cb8)] },  // ~1.175...
   3741  ] as ScalarToIntervalCase[],
   3742 } as const;
   3743 
   3744 g.test('sinhInterval')
   3745  .params(u =>
   3746    u
   3747      .combine('trait', ['f32', 'f16'] as const)
   3748      .beginSubcases()
   3749      .expandWithParams<ScalarToIntervalCase>(p => {
   3750        const trait = FP[p.trait];
   3751        const constants = trait.constants();
   3752        // prettier-ignore
   3753        return [
   3754          ...kSinhIntervalCases[p.trait],
   3755 
   3756          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3757          { input: constants.negative.min, expected: kUnboundedEndpoints },
   3758          { input: constants.positive.max, expected: kUnboundedEndpoints },
   3759          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3760        ];
   3761      })
   3762  )
   3763  .fn(t => {
   3764    const trait = FP[t.params.trait];
   3765    const expected = trait.toInterval(t.params.expected);
   3766    const got = trait.sinhInterval(t.params.input);
   3767    t.expect(
   3768      objectEquals(expected, got),
   3769      `${t.params.trait}.sinhInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3770    );
   3771  });
   3772 
   3773 // For sqrt interval inherited from 1.0 / inverseSqrt(x), errors come from:
   3774 //   1. Rounding of input x, if any;
   3775 //   2. 2 ULP from inverseSqrt;
   3776 //   3. And 2.5 ULP from division.
   3777 // The last 2.5ULP is handled in test and not included in the expected values here.
   3778 // prettier-ignore
   3779 const kSqrtIntervalCases = {
   3780  f32: [
   3781    // 0.01 rounded to f32 0x3C23D70A or 0x3C23D70B.
   3782    // For inverseSqrt interval, floor_f32(1.0/sqrt(0x3C23D70B))-2ULP=0x411FFFFD,
   3783    // ceil_f32(1.0/sqrt(0x3C23D70A))+2ULP=0x41200003.
   3784    // For division, 1.0/0x41200003=0.09999997138977868544997855067803 rounded to f32 0x3DCCCCC8 or 0x3DCCCCC9,
   3785    // 1.0/0x411FFFFD=0.100000028610237685454662304067 rounded to f32 0x3DCCCCD0 or 0x3DCCCCD1.
   3786    { input: 0.01, expected: [reinterpretU32AsF32(0x3DCCCCC8), reinterpretU32AsF32(0x3DCCCCD1)] },  // ~0.1
   3787    // For inverseSqrt interval, 1.0/sqrt(1.0)-2ULP=0x3F7FFFFE, 1.0/sqrt(1.0)+2ULP=0x3F800001.
   3788    // For division, 1.0/0x3F800001=0.9999998807907246108530328709735 rounded to f32 0x3F7FFFFE or 0x3F7FFFFF,
   3789    // 1.0/0x3F7FFFFE=1.0000001192093038108564210027667 rounded to f32 0x3F800001 or 0x3F800002.
   3790    { input: 1, expected: [reinterpretU32AsF32(0x3F7FFFFE), reinterpretU32AsF32(0x3F800002)] },  // ~1
   3791    // For inverseSqrt interval, 1.0/sqrt(4.0)-2ULP=0x3EFFFFFE, 1.0/sqrt(4.0)+2ULP=0x3F000001.
   3792    // For division, 1.0/0x3F000001=1.999999761581449221706065741947 rounded to f32 0x3FFFFFFE or 0x3FFFFFFF,
   3793    // 1.0/0x3EFFFFFE=2.0000002384186076217128420055334 rounded to f32 0x40000001 or 0x40000002.
   3794    { input: 4, expected: [reinterpretU32AsF32(0x3FFFFFFE), reinterpretU32AsF32(0x40000002)] },  // ~2
   3795    // For inverseSqrt interval, floor_f32(1.0/sqrt(100.0))-2ULP=0x3DCCCCCA,
   3796    // ceil_f32(1.0/sqrt(100.0))+2ULP=0x3DCCCCCF.
   3797    // For division, 1.0/0x3DCCCCCF=9.9999983608725376739278142322684 rounded to f32 0x411FFFFE or 0x411FFFFF,
   3798    // 1.0/0x3DCCCCCA=10.000002086163002207516386565905 rounded to f32 0x41200002 or 0x41200003.
   3799    { input: 100, expected: [reinterpretU32AsF32(0x411FFFFE), reinterpretU32AsF32(0x41200003)] },  // ~10
   3800  ] as ScalarToIntervalCase[],
   3801  f16: [
   3802    // 0.01 rounded to f16 0x211E or 0x211F.
   3803    // For inverseSqrt interval, floor_f16(1.0/sqrt(0x211F))-2ULP=0x48FD,
   3804    // ceil_f16(1.0/sqrt(0x211E))+2ULP=0x4903.
   3805    // For division, 1.0/0x4903=0.09976617303195635229929851909587 rounded to f16 0x2E62 or 0x2E63,
   3806    // 1.0/0x48FD=0.10023492560689115113547376664056 rounded to f16 0x2E6A or 0x2E6B.
   3807    { input: 0.01, expected: [reinterpretU16AsF16(0x2E62), reinterpretU16AsF16(0x2E6B)] },  // ~0.1
   3808    // For inverseSqrt interval, 1.0/sqrt(1.0)-2ULP=0x3BFE, 1.0/sqrt(1.0)+2ULP=0x3C01.
   3809    // For division, 1.0/0x3C01=0.99902439024390243902439024390244 rounded to f16 0x3BFE or 0x3BFF,
   3810    // 1.0/0x3BFE=1.000977517106549364613880742913 rounded to f16 0x3C01 or 0x3C02.
   3811    { input: 1, expected: [reinterpretU16AsF16(0x3BFE), reinterpretU16AsF16(0x3C02)] },  // ~1
   3812    // For inverseSqrt interval, 1.0/sqrt(4.0)-2ULP=0x37FE, 1.0/sqrt(4.0)+2ULP=0x3801.
   3813    // For division, 1.0/0x3801=1.9980487804878048780487804878049 rounded to f16 0x3FFE or 0x3FFF,
   3814    // 1.0/0x37FE=2.001955034213098729227761485826 rounded to f16 0x4001 or 0x4002.
   3815    { input: 4, expected: [reinterpretU16AsF16(0x3FFE), reinterpretU16AsF16(0x4002)] },  // ~2
   3816    // For inverseSqrt interval, floor_f16(1.0/sqrt(100.0))-2ULP=0x2E64,
   3817    // ceil_f16(1.0/sqrt(100.0))+2ULP=0x2E69.
   3818    // For division, 1.0/0x2E69=9.9841560024374942258493264279108 rounded to f16 0x48FD or 0x48FE,
   3819    // 1.0/0x2E64=10.014669926650366748166259168704 rounded to f16 0x4901 or 0x4902.
   3820    { input: 100, expected: [reinterpretU16AsF16(0x48FD), reinterpretU16AsF16(0x4902)] },  // ~10
   3821  ] as ScalarToIntervalCase[],
   3822 } as const;
   3823 
   3824 g.test('sqrtInterval')
   3825  .params(u =>
   3826    u
   3827      .combine('trait', ['f32', 'f16'] as const)
   3828      .beginSubcases()
   3829      .expandWithParams<ScalarToIntervalCase>(p => {
   3830        const trait = FP[p.trait];
   3831        const constants = trait.constants();
   3832        // prettier-ignore
   3833        return [
   3834          // Cases that input and/or result not exactly representable
   3835          ...kSqrtIntervalCases[p.trait],
   3836 
   3837          // Cases out of definition domain
   3838          { input: -1, expected: kUnboundedEndpoints },
   3839          { input: 0, expected: kUnboundedEndpoints },
   3840          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3841        ];
   3842      })
   3843  )
   3844  .fn(t => {
   3845    const trait = FP[t.params.trait];
   3846 
   3847    // The expected error interval is inherited from 1.0 / inverseSqrt(x), the 2.5ULP for division
   3848    // is handled here.
   3849    const error = (n: number): number => {
   3850      return 2.5 * trait.oneULP(n);
   3851    };
   3852 
   3853    const expected = trait.toInterval(applyError(t.params.expected, error));
   3854 
   3855    const got = trait.sqrtInterval(t.params.input);
   3856    t.expect(
   3857      objectEquals(expected, got),
   3858      `FP.${t.params.trait}.sqrtInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3859    );
   3860  });
   3861 
   3862 // All of these are hard coded, since the error intervals are difficult to express in a closed
   3863 // human-readable form.
   3864 // Some easy looking cases like f(x = -Ï€|Ï€) = 0 are actually quite difficult. This is because the
   3865 // interval is calculated from the results of sin(x)/cos(x), which becomes very messy at x = -Ï€|Ï€,
   3866 // since π is irrational, thus does not have an exact representation as a float.
   3867 //
   3868 // Even at 0, which has a precise f32/f16 value, there is still the problem that result of sin(0)
   3869 // and cos(0) will be intervals due to the inherited nature of errors, so the proper interval will
   3870 // be an interval calculated from dividing an interval by another interval and applying an error
   3871 // function to that.
   3872 //
   3873 // This complexity is why the entire interval framework was developed.
   3874 //
   3875 // The examples here have been manually traced to confirm the expectation values are correct.
   3876 // prettier-ignore
   3877 const kTanIntervalCases = {
   3878  f32: [
   3879    { input: kValue.f32.negative.pi.whole, expected: [reinterpretU64AsF64(0xbf40_02bc_9000_0000n), reinterpretU64AsF64(0x3f40_0144_f000_0000n)] },  // ~0.0
   3880    { input: kValue.f32.negative.pi.three_quarters, expected: [reinterpretU64AsF64(0x3fef_f4b1_3000_0000n), reinterpretU64AsF64(0x3ff0_05a9_9000_0000n)] },  // ~1.0
   3881    { input: kValue.f32.negative.pi.third, expected: [reinterpretU64AsF64(0xbffb_c16b_d000_0000n), reinterpretU64AsF64(0xbffb_ab8f_9000_0000n)] },  // ~-√3
   3882    { input: kValue.f32.negative.pi.quarter, expected: [reinterpretU64AsF64(0xbff0_05a9_b000_0000n), reinterpretU64AsF64(0xbfef_f4b1_5000_0000n)] },  // ~-1.0
   3883    { input: kValue.f32.negative.pi.sixth, expected: [reinterpretU64AsF64(0xbfe2_80f1_f000_0000n), reinterpretU64AsF64(0xbfe2_725e_d000_0000n)] },  // ~-1/√3
   3884    { input: 0, expected: [reinterpretU64AsF64(0xbf40_0200_b000_0000n), reinterpretU64AsF64(0x3f40_0200_b000_0000n)] },  // ~0.0
   3885    { input: kValue.f32.positive.pi.sixth, expected: [reinterpretU64AsF64(0x3fe2_725e_d000_0000n), reinterpretU64AsF64(0x3fe2_80f1_f000_0000n)] },  // ~1/√3
   3886    { input: kValue.f32.positive.pi.quarter, expected: [reinterpretU64AsF64(0x3fef_f4b1_5000_0000n), reinterpretU64AsF64(0x3ff0_05a9_b000_0000n)] },  // ~1.0
   3887    { input: kValue.f32.positive.pi.third, expected: [reinterpretU64AsF64(0x3ffb_ab8f_9000_0000n), reinterpretU64AsF64(0x3ffb_c16b_d000_0000n)] },  // ~√3
   3888    { input: kValue.f32.positive.pi.three_quarters, expected: [reinterpretU64AsF64(0xbff0_05a9_9000_0000n), reinterpretU64AsF64(0xbfef_f4b1_3000_0000n)] },  // ~-1.0
   3889    { input: kValue.f32.positive.pi.whole, expected: [reinterpretU64AsF64(0xbf40_0144_f000_0000n), reinterpretU64AsF64(0x3f40_02bc_9000_0000n)] },  // ~0.0
   3890  ] as ScalarToIntervalCase[],
   3891  f16: [
   3892    { input: kValue.f16.negative.pi.whole, expected: [reinterpretU64AsF64(0xbf7c_5600_0000_0000n), reinterpretU64AsF64(0x3f82_2e00_0000_0000n)] },  // ~0.0
   3893    { input: kValue.f16.negative.pi.three_quarters, expected: [reinterpretU64AsF64(0x3fef_4600_0000_0000n), reinterpretU64AsF64(0x3ff0_7200_0000_0000n)] },  // ~1.0
   3894    { input: kValue.f16.negative.pi.third, expected: [reinterpretU64AsF64(0xbffc_7600_0000_0000n), reinterpretU64AsF64(0xbffa_f600_0000_0000n)] },  // ~-√3
   3895    { input: kValue.f16.negative.pi.quarter, expected: [reinterpretU64AsF64(0xbff0_6600_0000_0000n), reinterpretU64AsF64(0xbfef_3600_0000_0000n)] },  // ~-1.0
   3896    { input: kValue.f16.negative.pi.sixth, expected: [reinterpretU64AsF64(0xbfe2_fe00_0000_0000n), reinterpretU64AsF64(0xbfe1_f600_0000_0000n)] },  // ~-1/√3
   3897    { input: 0, expected: [reinterpretU64AsF64(0xbf80_2e00_0000_0000n), reinterpretU64AsF64(0x3f80_2e00_0000_0000n)] },  // ~0.0
   3898    { input: kValue.f16.positive.pi.sixth, expected: [reinterpretU64AsF64(0x3fe1_f600_0000_0000n), reinterpretU64AsF64(0x3fe2_fe00_0000_0000n)] },  // ~1/√3
   3899    { input: kValue.f16.positive.pi.quarter, expected: [reinterpretU64AsF64(0x3fef_3600_0000_0000n), reinterpretU64AsF64(0x3ff0_6600_0000_0000n)] },  // ~1.0
   3900    { input: kValue.f16.positive.pi.third, expected: [reinterpretU64AsF64(0x3ffa_f600_0000_0000n), reinterpretU64AsF64(0x3ffc_7600_0000_0000n)] },  // ~√3
   3901    { input: kValue.f16.positive.pi.three_quarters, expected: [reinterpretU64AsF64(0xbff0_7200_0000_0000n), reinterpretU64AsF64(0xbfef_4600_0000_0000n)] },  // ~-1.0
   3902    { input: kValue.f16.positive.pi.whole, expected: [reinterpretU64AsF64(0xbf82_2e00_0000_0000n), reinterpretU64AsF64(0x3f7c_5600_0000_0000n)] },  // ~0.0
   3903  ] as ScalarToIntervalCase[],
   3904 } as const;
   3905 
   3906 g.test('tanInterval')
   3907  .params(u =>
   3908    u
   3909      .combine('trait', ['f32', 'f16'] as const)
   3910      .beginSubcases()
   3911      .expandWithParams<ScalarToIntervalCase>(p => {
   3912        const trait = FP[p.trait];
   3913        const constants = trait.constants();
   3914        // prettier-ignore
   3915        return [
   3916          ...kTanIntervalCases[p.trait],
   3917 
   3918          // Cases that result in unbounded interval.
   3919          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3920          { input: constants.negative.min, expected: kUnboundedEndpoints },
   3921          { input: constants.negative.pi.half, expected: kUnboundedEndpoints },
   3922          { input: constants.positive.pi.half, expected: kUnboundedEndpoints },
   3923          { input: constants.positive.max, expected: kUnboundedEndpoints },
   3924          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3925        ];
   3926      })
   3927  )
   3928  .fn(t => {
   3929    const trait = FP[t.params.trait];
   3930    const expected = trait.toInterval(t.params.expected);
   3931    const got = trait.tanInterval(t.params.input);
   3932    t.expect(
   3933      objectEquals(expected, got),
   3934      `${t.params.trait}.tanInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3935    );
   3936  });
   3937 
   3938 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   3939 // human-readable form due to the inherited nature of the errors.
   3940 // prettier-ignore
   3941 const kTanhIntervalCases = {
   3942  f32: [
   3943    { input: -1, expected: [reinterpretU64AsF64(0xbfe8_5f0f_b8b_588e3n), reinterpretU64AsF64(0xbfe8_5ee5_a74a_771dn)] },  // ~-0.7615...
   3944    { input: 0, expected: [reinterpretU64AsF64(0xbee4_f8b5_88e3_68f1n), reinterpretU64AsF64(0x3ee4_f8b5_88e3_68f1n)] },  // ~0
   3945    { input: 1, expected: [reinterpretU64AsF64(0x3fe8_5ee5_a74a_771dn), reinterpretU64AsF64(0x3fe8_5f0f_b8b5_88e3n)] },  // ~0.7615...
   3946  ] as ScalarToIntervalCase[],
   3947  f16: [
   3948    { input: -1, expected: [reinterpretU64AsF64(0xbfe8_9600_0000_0000n), reinterpretU64AsF64(0xbfe8_2e00_0000_0000n)] },  // ~-0.7615...
   3949    { input: 0, expected: [reinterpretU64AsF64(0xbf48_0e00_0000_0000n), reinterpretU64AsF64(0x3f48_0e00_0000_0000n)] },  // ~0
   3950    { input: 1, expected: [reinterpretU64AsF64(0x3fe8_2e00_0000_0000n), reinterpretU64AsF64(0x3fe8_9600_0000_0000n)] },  // ~0.7615...
   3951  ] as ScalarToIntervalCase[],
   3952 } as const;
   3953 
   3954 g.test('tanhInterval')
   3955  .params(u =>
   3956    u
   3957      .combine('trait', ['f32', 'f16'] as const)
   3958      .beginSubcases()
   3959      .expandWithParams<ScalarToIntervalCase>(p => {
   3960        const trait = FP[p.trait];
   3961        const constants = trait.constants();
   3962        // prettier-ignore
   3963        return [
   3964          ...kTanhIntervalCases[p.trait],
   3965 
   3966          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   3967          { input: constants.negative.min, expected: kUnboundedEndpoints },
   3968          { input: constants.positive.max, expected: kUnboundedEndpoints },
   3969          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   3970        ];
   3971      })
   3972  )
   3973  .fn(t => {
   3974    const trait = FP[t.params.trait];
   3975    const expected = trait.toInterval(t.params.expected);
   3976    const got = trait.tanhInterval(t.params.input);
   3977 
   3978    t.expect(
   3979      objectEquals(expected, got),
   3980      `${t.params.trait}.tanhInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   3981    );
   3982  });
   3983 
   3984 g.test('truncInterval')
   3985  .params(u =>
   3986    u
   3987      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   3988      .beginSubcases()
   3989      .expandWithParams<ScalarToIntervalCase>(p => {
   3990        const trait = FP[p.trait];
   3991        const constants = trait.constants();
   3992        // prettier-ignore
   3993        return [
   3994          // Normals
   3995          { input: 0, expected: 0 },
   3996          { input: 0.1, expected: 0 },
   3997          { input: 0.9, expected: 0 },
   3998          { input: 1.0, expected: 1 },
   3999          { input: 1.1, expected: 1 },
   4000          { input: 1.9, expected: 1 },
   4001          { input: -0.1, expected: 0 },
   4002          { input: -0.9, expected: 0 },
   4003          { input: -1.0, expected: -1 },
   4004          { input: -1.1, expected: -1 },
   4005          { input: -1.9, expected: -1 },
   4006 
   4007          // Subnormals
   4008          { input: constants.positive.subnormal.max, expected: 0 },
   4009          { input: constants.positive.subnormal.min, expected: 0 },
   4010          { input: constants.negative.subnormal.min, expected: 0 },
   4011          { input: constants.negative.subnormal.max, expected: 0 },
   4012 
   4013          // Edge cases
   4014          { input: constants.positive.infinity, expected: kUnboundedEndpoints },
   4015          { input: constants.negative.infinity, expected: kUnboundedEndpoints },
   4016          { input: constants.positive.max, expected: constants.positive.max },
   4017          { input: constants.positive.min, expected: 0 },
   4018          { input: constants.negative.min, expected: constants.negative.min },
   4019          { input: constants.negative.max, expected: 0 },
   4020        ];
   4021      })
   4022  )
   4023  .fn(t => {
   4024    const trait = FP[t.params.trait];
   4025    const expected = trait.toInterval(t.params.expected);
   4026    const got = trait.truncInterval(t.params.input);
   4027    t.expect(
   4028      objectEquals(expected, got),
   4029      `FP.${t.params.trait}.truncInterval(${t.params.input}) returned ${got}. Expected ${expected}`
   4030    );
   4031  });
   4032 
   4033 interface ScalarPairToIntervalCase {
   4034  // input is a pair of independent values, not a range, so should not be
   4035  // converted to a FPInterval.
   4036  input: [number, number];
   4037  expected: number | IntervalEndpoints;
   4038 }
   4039 
   4040 // prettier-ignore
   4041 const kAdditionInterval64BitsNormalCases = {
   4042  f32: [
   4043    // 0.1 falls between f32 0x3DCCCCCC and 0x3DCCCCCD, -0.1 falls between f32 0xBDCCCCCD and 0xBDCCCCCC
   4044    // f32 0x3DCCCCCC+0x3DCCCCCC=0x3E4CCCCC, 0x3DCCCCCD+0x3DCCCCCD=0x3E4CCCCD
   4045    { input: [0.1, 0.1], expected: [reinterpretU32AsF32(0x3e4ccccc), reinterpretU32AsF32(0x3e4ccccd)] },  // ~0.2
   4046    // f32 0xBDCCCCCD+0xBDCCCCCD=0xBE4CCCCD, 0xBDCCCCCC+0xBDCCCCCC=0xBE4CCCCD
   4047    { input: [-0.1, -0.1], expected: [reinterpretU32AsF32(0xbe4ccccd), reinterpretU32AsF32(0xbe4ccccc)] },  // ~-0.2
   4048    // 0.1+(-0.1) expect f32 interval [0x3DCCCCCC+0xBDCCCCCD, 0x3DCCCCCD+0xBDCCCCCC]
   4049    { input: [0.1, -0.1], expected: [reinterpretU32AsF32(0x3dcccccc) + reinterpretU32AsF32(0xbdcccccd), reinterpretU32AsF32(0x3dcccccd) + reinterpretU32AsF32(0xbdcccccc)] },  // ~0.0
   4050    // -0.1+0.1 expect f32 interval [0xBDCCCCCD+0x3DCCCCCC, 0xBDCCCCCC+0x3DCCCCCD]
   4051    { input: [-0.1, 0.1], expected: [reinterpretU32AsF32(0xbdcccccd) + reinterpretU32AsF32(0x3dcccccc), reinterpretU32AsF32(0xbdcccccc) + reinterpretU32AsF32(0x3dcccccd)] },  // ~0.0
   4052    { input: [1, kValue.f32.positive.min], expected: [1.0, reinterpretU32AsF32(0x3f800001)] },
   4053    { input: [1, kValue.f32.negative.max], expected: [reinterpretU32AsF32(0x3f7fffff), 1.0] },
   4054    { input: [-1, kValue.f32.positive.min], expected: [-1.0, reinterpretU32AsF32(0xbf7fffff)] },
   4055    { input: [-1, kValue.f32.negative.max], expected: [reinterpretU32AsF32(0xbf800001), -1.0] },
   4056    { input: [1, kValue.f32.positive.max], expected: kUnboundedEndpoints },
   4057    { input: [1, kValue.f32.negative.min], expected: [reinterpretU32AsF32(0xff7fffff), reinterpretU32AsF32(0xff7ffffe)] },
   4058    { input: [-1, kValue.f32.positive.max], expected: [reinterpretU32AsF32(0x7f7ffffe), reinterpretU32AsF32(0x7f7fffff)] },
   4059    { input: [-1, kValue.f32.negative.min], expected: kUnboundedEndpoints },
   4060    // Symmetry with the above test cases.
   4061    { input: [kValue.f32.positive.min, 1], expected: [1.0, reinterpretU32AsF32(0x3f800001)] },
   4062    { input: [kValue.f32.negative.max, 1], expected: [reinterpretU32AsF32(0x3f7fffff), 1.0] },
   4063    { input: [kValue.f32.positive.min, -1], expected: [-1.0, reinterpretU32AsF32(0xbf7fffff)] },
   4064    { input: [kValue.f32.negative.max, -1], expected: [reinterpretU32AsF32(0xbf800001), -1.0] },
   4065    { input: [kValue.f32.positive.max, 1], expected: kUnboundedEndpoints },
   4066    { input: [kValue.f32.negative.min, 1], expected: [reinterpretU32AsF32(0xff7fffff), reinterpretU32AsF32(0xff7ffffe)] },
   4067    { input: [kValue.f32.positive.max, -1], expected: [reinterpretU32AsF32(0x7f7ffffe), reinterpretU32AsF32(0x7f7fffff)] },
   4068    { input: [kValue.f32.negative.min, -1], expected: kUnboundedEndpoints },
   4069  ] as ScalarPairToIntervalCase[],
   4070  f16: [
   4071    // 0.1 falls between f16 0x2E66 and 0x2E67, -0.1 falls between f16 0xAE67 and 0xAE66
   4072    // f16 0x2E66+0x2E66=0x3266, 0x2E67+0x2E67=0x3267
   4073    { input: [0.1, 0.1], expected: [reinterpretU16AsF16(0x3266), reinterpretU16AsF16(0x3267)] },  // ~0.2
   4074    // f16 0xAE67+0xAE67=0xB267, 0xAE66+0xAE66=0xB266
   4075    { input: [-0.1, -0.1], expected: [reinterpretU16AsF16(0xb267), reinterpretU16AsF16(0xb266)] },  // ~-0.2
   4076    // 0.1+(-0.1) expect f16 interval [0x2E66+0xAE67, 0x2E67+0xAE66]
   4077    { input: [0.1, -0.1], expected: [reinterpretU16AsF16(0x2e66) + reinterpretU16AsF16(0xae67), reinterpretU16AsF16(0x2e67) + reinterpretU16AsF16(0xae66)] },  // ~0.0
   4078    // -0.1+0.1 expect f16 interval [0xAE67+0x2E66, 0xAE66+0x2E67]
   4079    { input: [-0.1, 0.1], expected: [reinterpretU16AsF16(0xae67)+reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0xae66)+reinterpretU16AsF16(0x2e67)] },  // ~0.0
   4080  ] as ScalarPairToIntervalCase[],
   4081 } as const;
   4082 
   4083 g.test('additionInterval')
   4084  .params(u =>
   4085    u
   4086      .combine('trait', ['f32', 'f16'] as const)
   4087      .beginSubcases()
   4088      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4089        const trait = FP[p.trait];
   4090        const constants = trait.constants();
   4091        // prettier-ignore
   4092        return [
   4093          // Representable normals
   4094          { input: [0, 0], expected: 0 },
   4095          { input: [1, 0], expected: 1 },
   4096          { input: [0, 1], expected: 1 },
   4097          { input: [-1, 0], expected: -1 },
   4098          { input: [0, -1], expected: -1 },
   4099          { input: [1, 1], expected: 2 },
   4100          { input: [1, -1], expected: 0 },
   4101          { input: [-1, 1], expected: 0 },
   4102          { input: [-1, -1], expected: -2 },
   4103 
   4104          // 0.1 should be correctly rounded
   4105          { input: [0.1, 0], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   4106          { input: [0, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   4107          // -0.1 should be correctly rounded
   4108          { input: [-0.1, 0], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },
   4109          { input: [0, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },
   4110 
   4111          // 64-bit normals that can not be exactly represented
   4112          ...kAdditionInterval64BitsNormalCases[p.trait],
   4113 
   4114          // Subnormals
   4115          { input: [constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   4116          { input: [0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   4117          { input: [constants.positive.subnormal.min, 0], expected: [0, constants.positive.subnormal.min] },
   4118          { input: [0, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] },
   4119          { input: [constants.negative.subnormal.max, 0], expected: [constants.negative.subnormal.max, 0] },
   4120          { input: [0, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, 0] },
   4121          { input: [constants.negative.subnormal.min, 0], expected: [constants.negative.subnormal.min, 0] },
   4122          { input: [0, constants.negative.subnormal.min], expected: [constants.negative.subnormal.min, 0] },
   4123 
   4124          // Infinities
   4125          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   4126          { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
   4127          { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4128          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   4129          { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
   4130          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4131          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4132          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4133        ];
   4134      })
   4135  )
   4136  .fn(t => {
   4137    const [x, y] = t.params.input;
   4138    const trait = FP[t.params.trait];
   4139    const expected = trait.toInterval(t.params.expected);
   4140    const got = trait.additionInterval(x, y);
   4141    t.expect(
   4142      objectEquals(expected, got),
   4143      `${t.params.trait}.additionInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4144    );
   4145  });
   4146 
   4147 // Cases for Atan2Interval. The positive x & y quadrant is tested in more detail, and the other
   4148 // quadrants are spot checked that values are pointing in the right direction.
   4149 // Note: atan2's parameters are labelled (y, x) instead of (x, y)
   4150 // prettier-ignore
   4151 const kAtan2IntervalCases = {
   4152  // atan has 4096ULP error boundary for f32.
   4153  f32: [
   4154    // positive y, positive x
   4155    // √3 rounded to f32 0x3FDDB3D7, atan2(1, 0x3FDDB3D7)=0.52359877749051820266056630237827 ~ pi/6 rounded to f32 0x3F060A91 or 0x3F060A92,
   4156    // kValue.f32.positive.pi.sixth is 0x3F060A92.
   4157    { input: [1, reinterpretU32AsF32(0x3fddb3d7)], expected: [kMinusNULPFunctions['f32'](kValue.f32.positive.pi.sixth, 4097), kPlusNULPFunctions['f32'](kValue.f32.positive.pi.sixth, 4096)] },
   4158    // atan2(1, 1)=0.78539816339744830961566084581988 ~ pi/4 rounded to f32 0x3F490FDA or 0x3F490FDB,
   4159    // kValue.f32.positive.pi.quarter is 0x3F490FDB.
   4160    { input: [1, 1], expected: [kMinusNULPFunctions['f32'](kValue.f32.positive.pi.quarter, 4097), kPlusNULPFunctions['f32'](kValue.f32.positive.pi.quarter, 4096)] },
   4161    // √3 rounded to f32 0x3FDDB3D7, atan2(0x3FDDB3D7, 1) = 1.0471975493043784165707553892615 ~ pi/3 rounded to f32 0x3F860A91 or 0x3F860A92,
   4162    // kValue.f32.positive.pi.third is 0x3F860A92.
   4163    { input: [reinterpretU32AsF32(0x3fddb3d7), 1], expected: [kMinusNULPFunctions['f32'](kValue.f32.positive.pi.third, 4097), kPlusNULPFunctions['f32'](kValue.f32.positive.pi.third, 4096)] },
   4164 
   4165    // positive y, negative x
   4166    // atan2(1, -1)=pi*3/4=2.3561944901923449288469825374591 rounded to f32 0x4016CBE3 or 0x4016CBE4,
   4167    // kValue.f32.positive.pi.three_quarters is 0x4016CBE4.
   4168    { input: [1, -1], expected: [kMinusNULPFunctions['f32'](kValue.f32.positive.pi.three_quarters, 4097), kPlusNULPFunctions['f32'](kValue.f32.positive.pi.three_quarters, 4096)] },
   4169 
   4170    // negative y, negative x
   4171    // atan2(-1, -1)=-pi*3/4=-2.3561944901923449288469825374591 rounded to f32 0xC016CBE4 or 0xC016CBE3,
   4172    // kValue.f32.negative.pi.three_quarters is 0xC016CBE4.
   4173    { input: [-1, -1], expected: [kMinusNULPFunctions['f32'](kValue.f32.negative.pi.three_quarters, 4096), kPlusNULPFunctions['f32'](kValue.f32.negative.pi.three_quarters, 4097)] },
   4174 
   4175    // negative y, positive x
   4176    // atan2(-1, 1)=-pi/4=-0.78539816339744830961566084581988 rounded to f32 0xBF490FDB or 0xBF490FDA,
   4177    // kValue.f32.negative.pi.quarter is 0xBF490FDB.
   4178    { input: [-1, 1], expected: [kMinusNULPFunctions['f32'](kValue.f32.negative.pi.quarter, 4096), kPlusNULPFunctions['f32'](kValue.f32.negative.pi.quarter, 4097)] },
   4179 
   4180    // When y/x ~ 0, test that ULP applied to result of atan2, not the intermediate y/x value.
   4181    // y/x ~ 0, y<0, x<0, atan2(y,x) ~ -pi rounded to f32 0xC0490FDB or 0xC0490FDA,
   4182    // kValue.f32.negative.pi.whole is 0xC0490FDB.
   4183    {input: [kValue.f32.negative.max, -1], expected: [kMinusNULPFunctions['f32'](kValue.f32.negative.pi.whole, 4096), kPlusNULPFunctions['f32'](kValue.f32.negative.pi.whole, 4097)] },
   4184    // y/x ~ 0, y>0, x<0, atan2(y,x) ~ pi rounded to f32 0x40490FDA or 0x40490FDB,
   4185    // kValue.f32.positive.pi.whole is 0x40490FDB.
   4186    {input: [kValue.f32.positive.min, -1], expected: [kMinusNULPFunctions['f32'](kValue.f32.positive.pi.whole, 4097), kPlusNULPFunctions['f32'](kValue.f32.positive.pi.whole, 4096)] },
   4187  ] as ScalarPairToIntervalCase[],
   4188  // atan has 5ULP error boundary for f16.
   4189  f16: [
   4190    // positive y, positive x
   4191    // √3 rounded to f16 0x3EED, atan2(1, 0x3EED)=0.52375018906301191131992842392268 ~ pi/6 rounded to f16 0x3830 or 0x3831,
   4192    // kValue.f16.positive.pi.sixth is 0x3830.
   4193    { input: [1, reinterpretU16AsF16(0x3eed)], expected: [kMinusNULPFunctions['f16'](kValue.f16.positive.pi.sixth, 5), kPlusNULPFunctions['f16'](kValue.f16.positive.pi.sixth, 6)] },
   4194    // atan2(1, 1)=0.78539816339744830961566084581988 ~ pi/4 rounded to f16 0x3A48 or 0x3A49,
   4195    // kValue.f16.positive.pi.quarter is 0x3A48.
   4196    { input: [1, 1], expected: [kMinusNULPFunctions['f16'](kValue.f16.positive.pi.quarter, 5), kPlusNULPFunctions['f16'](kValue.f16.positive.pi.quarter, 6)] },
   4197    // √3 rounded to f16 0x3EED, atan2(0x3EED, 1) = 1.0470461377318847079113932677171 ~ pi/3 rounded to f16 0x3C30 or 0x3C31,
   4198    // kValue.f16.positive.pi.third is 0x3C30.
   4199    { input: [reinterpretU16AsF16(0x3eed), 1], expected: [kMinusNULPFunctions['f16'](kValue.f16.positive.pi.third, 5), kPlusNULPFunctions['f16'](kValue.f16.positive.pi.third, 6)] },
   4200 
   4201    // positive y, negative x
   4202    // atan2(1, -1)=pi*3/4=2.3561944901923449288469825374591 rounded to f16 0x40B6 or 0x40B7,
   4203    // kValue.f16.positive.pi.three_quarters is 0x40B6.
   4204    { input: [1, -1], expected: [kMinusNULPFunctions['f16'](kValue.f16.positive.pi.three_quarters, 5), kPlusNULPFunctions['f16'](kValue.f16.positive.pi.three_quarters, 6)] },
   4205 
   4206    // negative y, negative x
   4207    // atan2(-1, -1)=-pi*3/4=-2.3561944901923449288469825374591 rounded to f16 0xC0B7 or 0xC0B6,
   4208    // kValue.f16.negative.pi.three_quarters is 0xC0B6.
   4209    { input: [-1, -1], expected: [kMinusNULPFunctions['f16'](kValue.f16.negative.pi.three_quarters, 6), kPlusNULPFunctions['f16'](kValue.f16.negative.pi.three_quarters, 5)] },
   4210 
   4211    // negative y, positive x
   4212    // atan2(-1, 1)=-pi/4=-0.78539816339744830961566084581988 rounded to f16 0xBA49 or 0xBA48,
   4213    // kValue.f16.negative.pi.quarter is 0xBA48.
   4214    { input: [-1, 1], expected: [kMinusNULPFunctions['f16'](kValue.f16.negative.pi.quarter, 6), kPlusNULPFunctions['f16'](kValue.f16.negative.pi.quarter, 5)] },
   4215 
   4216    // When y/x ~ 0, test that ULP applied to result of atan2, not the intermediate y/x value.
   4217    // y/x ~ 0, y<0, x<0, atan2(y,x) ~ -pi rounded to f16 0xC249 or 0xC248,
   4218    // kValue.f16.negative.pi.whole is 0xC248.
   4219    {input: [kValue.f16.negative.max, -1], expected: [kMinusNULPFunctions['f16'](kValue.f16.negative.pi.whole, 6), kPlusNULPFunctions['f16'](kValue.f16.negative.pi.whole, 5)] },
   4220    // y/x ~ 0, y>0, x<0, atan2(y,x) ~ pi rounded to f16 0x4248 or 0x4249,
   4221    // kValue.f16.positive.pi.whole is 0x4248.
   4222    {input: [kValue.f16.positive.min, -1], expected: [kMinusNULPFunctions['f16'](kValue.f16.positive.pi.whole, 5), kPlusNULPFunctions['f16'](kValue.f16.positive.pi.whole, 6)] },
   4223  ] as ScalarPairToIntervalCase[],
   4224 } as const;
   4225 
   4226 g.test('atan2Interval')
   4227  .params(u =>
   4228    u
   4229      .combine('trait', ['f32', 'f16'] as const)
   4230      .beginSubcases()
   4231      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4232        const constants = FP[p.trait].constants();
   4233        // prettier-ignore
   4234        return [
   4235          ...kAtan2IntervalCases[p.trait],
   4236 
   4237          // Cases that y out of bound.
   4238          // positive y, positive x
   4239          { input: [Number.POSITIVE_INFINITY, 1], expected: kUnboundedEndpoints },
   4240          // positive y, negative x
   4241          { input: [Number.POSITIVE_INFINITY, -1], expected: kUnboundedEndpoints },
   4242          // negative y, negative x
   4243          { input: [Number.NEGATIVE_INFINITY, -1], expected: kUnboundedEndpoints },
   4244          // negative y, positive x
   4245          { input: [Number.NEGATIVE_INFINITY, 1], expected: kUnboundedEndpoints },
   4246 
   4247          // Discontinuity @ origin (0,0)
   4248          { input: [0, 0], expected: kUnboundedEndpoints },
   4249          { input: [0, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
   4250          { input: [0, constants.negative.subnormal.min], expected: kUnboundedEndpoints },
   4251          { input: [0, constants.positive.min], expected: kUnboundedEndpoints },
   4252          { input: [0, constants.negative.max], expected: kUnboundedEndpoints },
   4253          { input: [0, constants.positive.max], expected: kUnboundedEndpoints },
   4254          { input: [0, constants.negative.min], expected: kUnboundedEndpoints },
   4255          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   4256          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   4257          { input: [0, 1], expected: kUnboundedEndpoints },
   4258          { input: [constants.positive.subnormal.max, 1], expected: kUnboundedEndpoints },
   4259          { input: [constants.negative.subnormal.min, 1], expected: kUnboundedEndpoints },
   4260 
   4261          // Very large |x| values should cause kUnboundedEndpoints to be returned, due to the restrictions on division
   4262          { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
   4263          { input: [1, constants.positive.nearest_max], expected: kUnboundedEndpoints },
   4264          { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
   4265          { input: [1, constants.negative.nearest_min], expected: kUnboundedEndpoints },
   4266        ];
   4267      })
   4268  )
   4269  .fn(t => {
   4270    const trait = FP[t.params.trait];
   4271    const [y, x] = t.params.input;
   4272    const expected = trait.toInterval(t.params.expected);
   4273    const got = trait.atan2Interval(y, x);
   4274    t.expect(
   4275      objectEquals(expected, got),
   4276      `${t.params.trait}.atan2Interval(${y}, ${x}) returned ${got}]. Expected ${expected}`
   4277    );
   4278  });
   4279 
   4280 g.test('distanceIntervalScalar')
   4281  .params(u =>
   4282    u
   4283      .combine('trait', ['f32', 'f16'] as const)
   4284      .beginSubcases()
   4285      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4286        const trait = FP[p.trait];
   4287        const constants = trait.constants();
   4288        // prettier-ignore
   4289        return [
   4290          { input: [1.0, 0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   4291          { input: [0.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   4292          { input: [-0.0, -1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   4293          { input: [0.0, -1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   4294          { input: [0.1, 0], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   4295          { input: [0, 0.1], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   4296          { input: [-0.1, 0], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   4297          { input: [0, -0.1], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   4298          { input: [10.0, 0], expected: kRootSumSquareExpectationInterval[p.trait]['[10]'] },  // ~10
   4299          { input: [0, 10.0], expected: kRootSumSquareExpectationInterval[p.trait]['[10]'] },  // ~10
   4300          { input: [-10.0, 0], expected: kRootSumSquareExpectationInterval[p.trait]['[10]'] },  // ~10
   4301          { input: [0, -10.0], expected: kRootSumSquareExpectationInterval[p.trait]['[10]'] },  // ~10
   4302 
   4303          // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedEndpoints,
   4304          // because distance(x, y) = length(x - y), and length(0) = kUnboundedEndpoints
   4305          { input: [0, 0], expected: kUnboundedEndpoints },
   4306          { input: [1.0, 1.0], expected: kUnboundedEndpoints },
   4307          { input: [-1.0, -1.0], expected: kUnboundedEndpoints },
   4308 
   4309          // Subnormal Cases
   4310          { input: [constants.negative.subnormal.min, 0], expected: kUnboundedEndpoints },
   4311          { input: [constants.negative.subnormal.max, 0], expected: kUnboundedEndpoints },
   4312          { input: [constants.positive.subnormal.min, 0], expected: kUnboundedEndpoints },
   4313          { input: [constants.positive.subnormal.max, 0], expected: kUnboundedEndpoints },
   4314 
   4315          // Edge cases
   4316          { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
   4317          { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
   4318          { input: [constants.negative.min, 0], expected: kUnboundedEndpoints },
   4319          { input: [constants.negative.max, 0], expected: kUnboundedEndpoints },
   4320          { input: [constants.positive.min, 0], expected: kUnboundedEndpoints },
   4321          { input: [constants.positive.max, 0], expected: kUnboundedEndpoints },
   4322        ];
   4323      })
   4324  )
   4325  .fn(t => {
   4326    const trait = FP[t.params.trait];
   4327    const expected = trait.toInterval(t.params.expected);
   4328    const got = trait.distanceInterval(...t.params.input);
   4329    t.expect(
   4330      objectEquals(expected, got),
   4331      `${t.params.trait}.distanceInterval(${t.params.input[0]}, ${t.params.input[1]}) returned ${got}. Expected ${expected}`
   4332    );
   4333  });
   4334 
   4335 // prettier-ignore
   4336 const kDivisionInterval64BitsNormalCases = {
   4337  f32: [
   4338    // Zero divided by any non-zero finite value results in zero.
   4339    { input: [0, 0.1], expected: 0 },
   4340    { input: [0, -0.1], expected: 0 },
   4341    // 0.1 rounded to f32 0x3DCCCCCC or 0x3DCCCCCD,
   4342    // 1.0/0x3DCCCCCD = 9.9999998509883902204460179966303 rounded to f32 0x411FFFFF or 0x41200000,
   4343    // 1.0/0x3DCCCCCC = 10.000000596046483527138934924167 rounded to f32 0x41200000 or 0x41200001.
   4344    { input: [1, 0.1], expected: [reinterpretU32AsF32(0x411fffff), reinterpretU32AsF32(0x41200001)] },  // ~10.0
   4345    // The same for -1/-0.1
   4346    { input: [-1, -0.1], expected: [reinterpretU32AsF32(0x411fffff), reinterpretU32AsF32(0x41200001)] },  // ~10.0
   4347    // -10.000000596046483527138934924167 rounded to f32 0xC1200001 or 0xC1200000,
   4348    // -9.9999998509883902204460179966303 rounded to f32 0xC1200000 or 0xC11FFFFF.
   4349    { input: [-1, 0.1], expected: [reinterpretU32AsF32(0xc1200001), reinterpretU32AsF32(0xc11fffff)] },  // ~-10.0
   4350    { input: [1, -0.1], expected: [reinterpretU32AsF32(0xc1200001), reinterpretU32AsF32(0xc11fffff)] },  // ~-10.0
   4351    // Cases that expected interval larger than +-1ULP.
   4352    // 0.000001 rounded to f32 0x358637BD or 0x358637BE,
   4353    // 1.0/0x358637BE = 999999.88883793195700674522548684 rounded to f32 0x497423FE or 0x497423FF,
   4354    // 1.0/0x358637BD = 1000000.0025247573063743994399971 rounded to f32 0x49742400 or 0x49742401.
   4355    { input: [1, 0.000001], expected: [reinterpretU32AsF32(0x497423fe), reinterpretU32AsF32(0x49742401)] },  // ~1000000.0
   4356    { input: [1, -0.000001], expected: [reinterpretU32AsF32(0xc9742401), reinterpretU32AsF32(0xc97423fe)] },  // ~-1000000.0
   4357  ] as ScalarPairToIntervalCase[],
   4358  f16: [
   4359    // Zero divided by any non-zero finite value results in zero.
   4360    { input: [0, 0.1], expected: 0 },
   4361    { input: [0, -0.1], expected: 0 },
   4362    // 0.1 rounded to f16 0x2E66 or 0x2E67,
   4363    // 1.0/0x2E67 = 9.9963392312385600976205003050641 rounded to f16 0x48FF or 0x4900,
   4364    // 1.0/0x2E66 = 10.002442002442002442002442002442 rounded to f16 0x4900 or 0x4901.
   4365    { input: [1, 0.1], expected: [reinterpretU16AsF16(0x48ff), reinterpretU16AsF16(0x4901)] },  // ~10.0
   4366    // The same for -1/-0.1
   4367    { input: [-1, -0.1], expected: [reinterpretU16AsF16(0x48ff), reinterpretU16AsF16(0x4901)] },  // ~10.0
   4368    // -10.002442002442002442002442002442 rounded to f16 0xC901 or 0xC900,
   4369    // -9.9963392312385600976205003050641 rounded to f16 0xC900 or 0xC8FF.
   4370    { input: [-1, 0.1], expected: [reinterpretU16AsF16(0xc901), reinterpretU16AsF16(0xc8ff)] },  // ~-10.0
   4371    { input: [1, -0.1], expected: [reinterpretU16AsF16(0xc901), reinterpretU16AsF16(0xc8ff)] },  // ~-10.0
   4372    // Cases that expected interval larger than +-1ULP.
   4373    // 0.001 rounded to f16 0x1418 or 0x1419,
   4374    // 1.0/0x1419 = 999.59580552907535977846384072716 rounded to f16 0x63CF or 0x63D0,
   4375    // 1.0/0x1418 = 1000.5496183206106870229007633588 rounded to f16 0x63D1 or 0x63D2.
   4376    { input: [1, 0.001], expected: [reinterpretU16AsF16(0x63cf), reinterpretU16AsF16(0x63d2)] },  // ~1000.0
   4377    { input: [1, -0.001], expected: [reinterpretU16AsF16(0xe3d2), reinterpretU16AsF16(0xe3cf)] },  // ~-1000.0
   4378  ] as ScalarPairToIntervalCase[],
   4379 } as const;
   4380 
   4381 g.test('divisionInterval')
   4382  .params(u =>
   4383    u
   4384      .combine('trait', ['f32', 'f16'] as const)
   4385      .beginSubcases()
   4386      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4387        const fp = FP[p.trait];
   4388        const constants = fp.constants();
   4389        // prettier-ignore
   4390        return [
   4391          // Representable normals
   4392          { input: [0, 1], expected: 0 },
   4393          { input: [0, -1], expected: 0 },
   4394          { input: [1, 1], expected: 1 },
   4395          { input: [1, -1], expected: -1 },
   4396          { input: [-1, 1], expected: -1 },
   4397          { input: [-1, -1], expected: 1 },
   4398          { input: [4, 2], expected: 2 },
   4399          { input: [-4, 2], expected: -2 },
   4400          { input: [4, -2], expected: -2 },
   4401          { input: [-4, -2], expected: 2 },
   4402 
   4403          // 64-bit normals that can not be exactly represented
   4404          ...kDivisionInterval64BitsNormalCases[p.trait],
   4405 
   4406          // Denominator out of range
   4407          { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
   4408          { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
   4409          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4410          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4411          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4412          { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
   4413          { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
   4414          { input: [1, 0], expected: kUnboundedEndpoints },
   4415          { input: [1, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
   4416        ];
   4417      })
   4418  )
   4419  .fn(t => {
   4420    const fp = FP[t.params.trait];
   4421 
   4422    const error = (n: number): number => {
   4423      return 2.5 * fp.oneULP(n);
   4424    };
   4425 
   4426    const [x, y] = t.params.input;
   4427 
   4428    const expected = FP[t.params.trait].toInterval(applyError(t.params.expected, error));
   4429    const got = FP[t.params.trait].divisionInterval(x, y);
   4430    t.expect(
   4431      objectEquals(expected, got),
   4432      `${t.params.trait}.divisionInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4433    );
   4434  });
   4435 
   4436 const kLdexpIntervalCases = {
   4437  f32: [
   4438    // 64-bit normals
   4439    { input: [1.0000000001, 1], expected: [2, kPlusNULPFunctions['f32'](2, 2)] }, // ~2, additional ULP error due to first param not being f32 precise
   4440    { input: [-1.0000000001, 1], expected: [kMinusNULPFunctions['f32'](-2, 2), -2] }, // ~-2, additional ULP error due to first param not being f32 precise
   4441    // Edge Cases
   4442    // f32 0b0_01111111_11111111111111111111111 = 1.9999998807907104,
   4443    // 1.9999998807907104 * 2 ** 127 = f32.positive.max
   4444    { input: [1.9999998807907104, 127], expected: kValue.f32.positive.max },
   4445    // f32.positive.min = 1 * 2 ** -126
   4446    { input: [1, -126], expected: kValue.f32.positive.min },
   4447    // f32.positive.subnormal.max = 0.9999998807907104 * 2 ** -126
   4448    { input: [0.9999998807907104, -126], expected: [0, kValue.f32.positive.subnormal.max] },
   4449    // f32.positive.subnormal.min = 1.1920928955078125e-07 * 2 ** -126
   4450    { input: [1.1920928955078125e-7, -126], expected: [0, kValue.f32.positive.subnormal.min] },
   4451    { input: [-1.1920928955078125e-7, -126], expected: [kValue.f32.negative.subnormal.max, 0] },
   4452    { input: [-0.9999998807907104, -126], expected: [kValue.f32.negative.subnormal.min, 0] },
   4453    { input: [-1, -126], expected: kValue.f32.negative.max },
   4454    { input: [-1.9999998807907104, 127], expected: kValue.f32.negative.min },
   4455    // e2 + bias <= 0, expect correctly rounded intervals.
   4456    { input: [2 ** 120, -130], expected: 2 ** -10 },
   4457    // Out of Bounds
   4458    { input: [1, 128], expected: kUnboundedEndpoints },
   4459    { input: [-1, 128], expected: kUnboundedEndpoints },
   4460    { input: [100, 126], expected: kUnboundedEndpoints },
   4461    { input: [-100, 126], expected: kUnboundedEndpoints },
   4462    { input: [2 ** 100, 100], expected: kUnboundedEndpoints },
   4463  ] as ScalarPairToIntervalCase[],
   4464  f16: [
   4465    // 64-bit normals
   4466    { input: [1.0000000001, 1], expected: [2, kPlusNULPFunctions['f16'](2, 2)] }, // ~2, additional ULP error due to first param not being f16 precise
   4467    { input: [-1.0000000001, 1], expected: [kMinusNULPFunctions['f16'](-2, 2), -2] }, // ~-2, additional ULP error due to first param not being f16 precise
   4468    // Edge Cases
   4469    // f16 0b0_01111_1111111111 = 1.9990234375, 1.9990234375 * 2 ** 15 = f16.positive.max
   4470    { input: [1.9990234375, 15], expected: kValue.f16.positive.max },
   4471    // f16.positive.min = 1 * 2 ** -14
   4472    { input: [1, -14], expected: kValue.f16.positive.min },
   4473    // f16.positive.subnormal.max = 0.9990234375 * 2 ** -14
   4474    { input: [0.9990234375, -14], expected: [0, kValue.f16.positive.subnormal.max] },
   4475    // f16.positive.subnormal.min = 1 * 2 ** -10 * 2 ** -14 = 0.0009765625 * 2 ** -14
   4476    { input: [0.0009765625, -14], expected: [0, kValue.f16.positive.subnormal.min] },
   4477    { input: [-0.0009765625, -14], expected: [kValue.f16.negative.subnormal.max, 0] },
   4478    { input: [-0.9990234375, -14], expected: [kValue.f16.negative.subnormal.min, 0] },
   4479    { input: [-1, -14], expected: kValue.f16.negative.max },
   4480    { input: [-1.9990234375, 15], expected: kValue.f16.negative.min },
   4481    // e2 + bias <= 0, expect correctly rounded intervals.
   4482    { input: [2 ** 12, -18], expected: 2 ** -6 },
   4483    // Out of Bounds
   4484    { input: [1, 16], expected: kUnboundedEndpoints },
   4485    { input: [-1, 16], expected: kUnboundedEndpoints },
   4486    { input: [100, 14], expected: kUnboundedEndpoints },
   4487    { input: [-100, 14], expected: kUnboundedEndpoints },
   4488    { input: [2 ** 10, 10], expected: kUnboundedEndpoints },
   4489  ] as ScalarPairToIntervalCase[],
   4490  abstract: [
   4491    // Edge Cases
   4492    // 1.9999999999999997779553950749686919152736663818359375 * 2 ** 1023 = f64.positive.max
   4493    {
   4494      input: [1.9999999999999997779553950749686919152736663818359375, 1023],
   4495      expected: kValue.f64.positive.max,
   4496    },
   4497    // f64.positive.min = 1 * 2 ** -1022
   4498    { input: [1, -1022], expected: kValue.f64.positive.min },
   4499    // f64.positive.subnormal.max = 1.9999999999999997779553950749686919152736663818359375 * 2 ** -1022
   4500    {
   4501      input: [0.9999999999999997779553950749686919152736663818359375, -1022],
   4502      expected: [0, kValue.f64.positive.subnormal.max],
   4503    },
   4504    // f64.positive.subnormal.min = 0.0000000000000002220446049250313080847263336181640625 * 2 ** -1022
   4505    {
   4506      input: [0.0000000000000002220446049250313080847263336181640625, -1022],
   4507      expected: [0, kValue.f64.positive.subnormal.min],
   4508    },
   4509    {
   4510      input: [-0.0000000000000002220446049250313080847263336181640625, -1022],
   4511      expected: [kValue.f64.negative.subnormal.max, 0],
   4512    },
   4513    {
   4514      input: [-0.9999999999999997779553950749686919152736663818359375, -1022],
   4515      expected: [kValue.f64.negative.subnormal.min, 0],
   4516    },
   4517    { input: [-1, -1022], expected: kValue.f64.negative.max },
   4518    {
   4519      input: [-1.9999999999999997779553950749686919152736663818359375, 1023],
   4520      expected: kValue.f64.negative.min,
   4521    },
   4522    // e2 + bias <= 0, expect correctly rounded intervals.
   4523    { input: [2 ** 120, -130], expected: 2 ** -10 },
   4524    // Out of Bounds
   4525    { input: [1, 1024], expected: kUnboundedEndpoints },
   4526    { input: [-1, 1024], expected: kUnboundedEndpoints },
   4527    { input: [100, 1024], expected: kUnboundedEndpoints },
   4528    { input: [-100, 1024], expected: kUnboundedEndpoints },
   4529    { input: [2 ** 100, 1000], expected: kUnboundedEndpoints },
   4530  ] as ScalarPairToIntervalCase[],
   4531 } as const;
   4532 
   4533 g.test('ldexpInterval')
   4534  .params(u =>
   4535    u
   4536      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   4537      .beginSubcases()
   4538      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4539        const trait = FP[p.trait];
   4540        const constants = trait.constants();
   4541        // prettier-ignore
   4542        return [
   4543          // always exactly representable cases
   4544          { input: [0, 0], expected: 0 },
   4545          { input: [0, 1], expected: 0 },
   4546          { input: [0, -1], expected: 0 },
   4547          { input: [1, 1], expected: 2 },
   4548          { input: [1, -1], expected: 0.5 },
   4549          { input: [-1, 1], expected: -2 },
   4550          { input: [-1, -1], expected: -0.5 },
   4551 
   4552          ...kLdexpIntervalCases[p.trait],
   4553 
   4554          // Extremely negative e2, any float value should be scale to 0.0 as the ground truth
   4555          // f64 e1 * 2 ** e2 would be 0.0 for e2 = -2147483648.
   4556          { input: [constants.positive.max, kValue.i32.negative.min], expected: 0 },
   4557          { input: [constants.negative.min, kValue.i32.negative.min], expected: 0 },
   4558          // Out of Bounds
   4559          { input: [constants.positive.max, kValue.i32.positive.max], expected: kUnboundedEndpoints },
   4560          { input: [constants.negative.min, kValue.i32.positive.max], expected: kUnboundedEndpoints },
   4561        ];
   4562      })
   4563  )
   4564  .fn(t => {
   4565    const [x, y] = t.params.input;
   4566    const trait = FP[t.params.trait];
   4567    const expected = trait.toInterval(t.params.expected);
   4568    const got = trait.ldexpInterval(x, y);
   4569    t.expect(
   4570      objectEquals(expected, got),
   4571      `${t.params.trait}.ldexpInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4572    );
   4573  });
   4574 
   4575 g.test('maxInterval')
   4576  .params(u =>
   4577    u
   4578      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   4579      .beginSubcases()
   4580      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4581        const trait = FP[p.trait];
   4582        const constants = trait.constants();
   4583        // prettier-ignore
   4584        return [
   4585          // Representable normals
   4586          { input: [0, 0], expected: 0 },
   4587          { input: [1, 0], expected: 1 },
   4588          { input: [0, 1], expected: 1 },
   4589          { input: [-1, 0], expected: 0 },
   4590          { input: [0, -1], expected: 0 },
   4591          { input: [1, 1], expected: 1 },
   4592          { input: [1, -1], expected: 1 },
   4593          { input: [-1, 1], expected: 1 },
   4594          { input: [-1, -1], expected: -1 },
   4595 
   4596          // 0.1 and -0.1 should be correctly rounded
   4597          { input: [-0.1, 0], expected: 0 },
   4598          { input: [0, -0.1], expected: 0 },
   4599          { input: [0.1, 0], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   4600          { input: [0, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   4601          { input: [0.1, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   4602          { input: [0.1, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   4603          { input: [-0.1, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   4604          { input: [-0.1, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },  // ~-0.1
   4605 
   4606          // Representable subnormals
   4607          { input: [constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   4608          { input: [0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   4609          { input: [constants.positive.subnormal.min, 0], expected: [0, constants.positive.subnormal.min] },
   4610          { input: [0, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] },
   4611          { input: [constants.negative.subnormal.max, 0], expected: [constants.negative.subnormal.max, 0] },
   4612          { input: [0, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, 0] },
   4613          { input: [constants.negative.subnormal.min, 0], expected: [constants.negative.subnormal.min, 0] },
   4614          { input: [0, constants.negative.subnormal.min], expected: [constants.negative.subnormal.min, 0] },
   4615          { input: [1, constants.positive.subnormal.max], expected: 1 },
   4616          { input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
   4617 
   4618          // Infinities
   4619          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   4620          { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
   4621          { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4622          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   4623          { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
   4624          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4625          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4626          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4627        ];
   4628      })
   4629  )
   4630  .fn(t => {
   4631    const trait = FP[t.params.trait];
   4632    const [x, y] = t.params.input;
   4633    const expected = trait.toInterval(t.params.expected);
   4634    const got = trait.maxInterval(x, y);
   4635    t.expect(
   4636      objectEquals(expected, got),
   4637      `${t.params.trait}.maxInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4638    );
   4639  });
   4640 
   4641 g.test('minInterval')
   4642  .params(u =>
   4643    u
   4644      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   4645      .beginSubcases()
   4646      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4647        const trait = FP[p.trait];
   4648        const constants = trait.constants();
   4649        // prettier-ignore
   4650        return [
   4651          // Representable normals
   4652          { input: [0, 0], expected: 0 },
   4653          { input: [1, 0], expected: 0 },
   4654          { input: [0, 1], expected: 0 },
   4655          { input: [-1, 0], expected: -1 },
   4656          { input: [0, -1], expected: -1 },
   4657          { input: [1, 1], expected: 1 },
   4658          { input: [1, -1], expected: -1 },
   4659          { input: [-1, 1], expected: -1 },
   4660          { input: [-1, -1], expected: -1 },
   4661 
   4662          // 64-bit normals that not exactly representable
   4663          { input: [0.1, 0], expected: 0 },
   4664          { input: [0, 0.1], expected: 0 },
   4665          { input: [-0.1, 0], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },  // ~-0.1
   4666          { input: [0, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },  // ~-0.1
   4667          { input: [0.1, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },  // ~0.1
   4668          { input: [0.1, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },  // ~-0.1
   4669          { input: [-0.1, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },  // ~-0.1
   4670          { input: [-0.1, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },  // ~-0.1
   4671 
   4672          // Representable subnormals
   4673          { input: [constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   4674          { input: [0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   4675          { input: [constants.positive.subnormal.min, 0], expected: [0, constants.positive.subnormal.min] },
   4676          { input: [0, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] },
   4677          { input: [constants.negative.subnormal.max, 0], expected: [constants.negative.subnormal.max, 0] },
   4678          { input: [0, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, 0] },
   4679          { input: [constants.negative.subnormal.min, 0], expected: [constants.negative.subnormal.min, 0] },
   4680          { input: [0, constants.negative.subnormal.min], expected: [constants.negative.subnormal.min, 0] },
   4681          { input: [-1, constants.positive.subnormal.max], expected: -1 },
   4682          { input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
   4683 
   4684          // Infinities
   4685          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   4686          { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
   4687          { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4688          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   4689          { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
   4690          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4691          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4692          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4693        ];
   4694      })
   4695  )
   4696  .fn(t => {
   4697    const trait = FP[t.params.trait];
   4698    const [x, y] = t.params.input;
   4699    const expected = trait.toInterval(t.params.expected);
   4700    const got = trait.minInterval(x, y);
   4701    t.expect(
   4702      objectEquals(expected, got),
   4703      `${t.params.trait}.minInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4704    );
   4705  });
   4706 
   4707 // prettier-ignore
   4708 const kMultiplicationInterval64BitsNormalCases = {
   4709  f32: [
   4710    // 0.1*0.1, 0.1 falls between f32 0x3DCCCCCC and 0x3DCCCCCD,
   4711    // min result 0x3DCCCCCC*0x3DCCCCCC=0.00999999880790713952713681734167 rounded to f32 0x3C23D708 or 0x3C23D709,
   4712    // max result 0x3DCCCCCD*0x3DCCCCCD=0.01000000029802322622044605108385 rounded to f32 0x3C23D70A or 0x3C23D70B.
   4713    { input: [0.1, 0.1], expected: [reinterpretU32AsF32(0x3c23d708), reinterpretU32AsF32(0x3c23d70b)] },  // ~0.01
   4714    { input: [-0.1, -0.1], expected: [reinterpretU32AsF32(0x3c23d708), reinterpretU32AsF32(0x3c23d70b)] },  // ~0.01
   4715    // -0.01000000029802322622044605108385 rounded to f32 0xBC23D70B or 0xBC23D70A,
   4716    // -0.00999999880790713952713681734167 rounded to f32 0xBC23D709 or 0xBC23D708.
   4717    { input: [0.1, -0.1], expected: [reinterpretU32AsF32(0xbc23d70b), reinterpretU32AsF32(0xbc23d708)] },  // ~-0.01
   4718    { input: [-0.1, 0.1], expected: [reinterpretU32AsF32(0xbc23d70b), reinterpretU32AsF32(0xbc23d708)] },  // ~-0.01
   4719  ] as ScalarPairToIntervalCase[],
   4720  f16: [
   4721    // 0.1*0.1, 0.1 falls between f16 0x2E66 and 0x2E67,
   4722    // min result 0x2E66*0x2E66=0.00999511778354644775390625 rounded to f16 0x211E or 0x211F,
   4723    // max result 0x2E67*0x2E67=0.0100073255598545074462890625 rounded to f16 0x211F or 0x2120.
   4724    { input: [0.1, 0.1], expected: [reinterpretU16AsF16(0x211e), reinterpretU16AsF16(0x2120)] },  // ~0.01
   4725    { input: [-0.1, -0.1], expected: [reinterpretU16AsF16(0x211e), reinterpretU16AsF16(0x2120)] },  // ~0.01
   4726    // -0.0100073255598545074462890625 rounded to f16 0xA120 or 0xA11F,
   4727    // -0.00999511778354644775390625 rounded to f16 0xA11F or 0xA11E.
   4728    { input: [0.1, -0.1], expected: [reinterpretU16AsF16(0xa120), reinterpretU16AsF16(0xa11e)] },  // ~-0.01
   4729    { input: [-0.1, 0.1], expected: [reinterpretU16AsF16(0xa120), reinterpretU16AsF16(0xa11e)] },  // ~-0.01
   4730  ] as ScalarPairToIntervalCase[],
   4731 } as const;
   4732 
   4733 g.test('multiplicationInterval')
   4734  .params(u =>
   4735    u
   4736      .combine('trait', ['f32', 'f16'] as const)
   4737      .beginSubcases()
   4738      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4739        const trait = FP[p.trait];
   4740        const constants = trait.constants();
   4741        // prettier-ignore
   4742        return [
   4743          // Representable normals
   4744          { input: [0, 0], expected: 0 },
   4745          { input: [1, 0], expected: 0 },
   4746          { input: [0, 1], expected: 0 },
   4747          { input: [-1, 0], expected: 0 },
   4748          { input: [0, -1], expected: 0 },
   4749          { input: [1, 1], expected: 1 },
   4750          { input: [1, -1], expected: -1 },
   4751          { input: [-1, 1], expected: -1 },
   4752          { input: [-1, -1], expected: 1 },
   4753          { input: [2, 1], expected: 2 },
   4754          { input: [1, -2], expected: -2 },
   4755          { input: [-2, 1], expected: -2 },
   4756          { input: [-2, -1], expected: 2 },
   4757          { input: [2, 2], expected: 4 },
   4758          { input: [2, -2], expected: -4 },
   4759          { input: [-2, 2], expected: -4 },
   4760          { input: [-2, -2], expected: 4 },
   4761 
   4762          // 64-bit normals that can not be exactly represented
   4763          // Finite values multiply zero result in zero
   4764          { input: [0.1, 0], expected: 0 },
   4765          { input: [0, 0.1], expected: 0 },
   4766          { input: [-0.1, 0], expected: 0 },
   4767          { input: [0, -0.1], expected: 0 },
   4768          // Finite value multiply +/-1.0
   4769          { input: [0.1, 1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   4770          { input: [-1, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   4771          { input: [-0.1, 1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },
   4772          { input: [-1, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },
   4773          // Other cases
   4774          ...kMultiplicationInterval64BitsNormalCases[p.trait],
   4775 
   4776          // Infinities
   4777          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   4778          { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
   4779          { input: [-1, constants.positive.infinity], expected: kUnboundedEndpoints },
   4780          { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4781          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   4782          { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
   4783          { input: [-1, constants.negative.infinity], expected: kUnboundedEndpoints },
   4784          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4785          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4786          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4787 
   4788          // Edges
   4789          { input: [constants.positive.max, constants.positive.max], expected: kUnboundedEndpoints },
   4790          { input: [constants.negative.min, constants.negative.min], expected: kUnboundedEndpoints },
   4791          { input: [constants.positive.max, constants.negative.min], expected: kUnboundedEndpoints },
   4792          { input: [constants.negative.min, constants.positive.max], expected: kUnboundedEndpoints },
   4793        ];
   4794      })
   4795  )
   4796  .fn(t => {
   4797    const [x, y] = t.params.input;
   4798    const trait = FP[t.params.trait];
   4799    const expected = trait.toInterval(t.params.expected);
   4800    const got = trait.multiplicationInterval(x, y);
   4801    t.expect(
   4802      objectEquals(expected, got),
   4803      `${t.params.trait}.multiplicationInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4804    );
   4805  });
   4806 
   4807 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   4808 // human-readable form due to the inherited nature of the errors.
   4809 // prettier-ignore
   4810 const kPowIntervalCases = {
   4811  f32 : [
   4812    { input: [1, 0], expected: [kMinusNULPFunctions['f32'](1, 3), reinterpretU64AsF64(0x3ff0_0000_3000_0000n)] },  // ~1
   4813    { input: [2, 0], expected: [kMinusNULPFunctions['f32'](1, 3), reinterpretU64AsF64(0x3ff0_0000_3000_0000n)] },  // ~1
   4814    { input: [kValue.f32.positive.max, 0], expected: [kMinusNULPFunctions['f32'](1, 3), reinterpretU64AsF64(0x3ff0_0000_3000_0000n)] },  // ~1
   4815    { input: [1, 1], expected: [reinterpretU64AsF64(0x3fef_fffe_dfff_fe00n), reinterpretU64AsF64(0x3ff0_0000_c000_0200n)] },  // ~1
   4816    { input: [1, 100], expected: [reinterpretU64AsF64(0x3fef_ffba_3fff_3800n), reinterpretU64AsF64(0x3ff0_0023_2000_c800n)] },  // ~1
   4817    { input: [2, 1], expected: [reinterpretU64AsF64(0x3fff_fffe_a000_0200n), reinterpretU64AsF64(0x4000_0001_0000_0200n)] },  // ~2
   4818    { input: [2, 2], expected: [reinterpretU64AsF64(0x400f_fffd_a000_0400n), reinterpretU64AsF64(0x4010_0001_a000_0400n)] },  // ~4
   4819    { input: [10, 10], expected: [reinterpretU64AsF64(0x4202_a04f_51f7_7000n), reinterpretU64AsF64(0x4202_a070_ee08_e000n)] },  // ~10000000000
   4820    { input: [10, 1], expected: [reinterpretU64AsF64(0x4023_fffe_0b65_8b00n), reinterpretU64AsF64(0x4024_0002_149a_7c00n)] },  // ~10
   4821  ] as ScalarPairToIntervalCase[],
   4822  f16 : [
   4823    { input: [1, 0], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0200_0000_0000n)] },  // ~1
   4824    { input: [2, 0], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0200_0000_0000n)] },  // ~1
   4825    { input: [kValue.f16.positive.max, 0], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0200_0000_0000n)] },  // ~1
   4826    { input: [1, 1], expected: [reinterpretU64AsF64(0x3fef_cbf0_0000_0000n), reinterpretU64AsF64(0x3ff0_1c10_0000_0000n)] },  // ~1
   4827    { input: [1, 100], expected: [reinterpretU64AsF64(0x3fe2_91c0_0000_0000n), reinterpretU64AsF64(0x3ffb_8a40_0000_0000n)] },  // ~1
   4828    { input: [2, 1], expected: [reinterpretU64AsF64(0x3fff_c410_0000_0000n), reinterpretU64AsF64(0x4000_2410_0000_0000n)] },  // ~2
   4829    { input: [2, 2], expected: [reinterpretU64AsF64(0x400f_9020_0000_0000n), reinterpretU64AsF64(0x4010_4420_0000_0000n)] },  // ~4
   4830    { input: [5, 5], expected: [reinterpretU64AsF64(0x40a7_5f70_0000_0000n), reinterpretU64AsF64(0x40a9_5520_0000_0000n)] },  // ~3125
   4831    { input: [10, 1], expected: [reinterpretU64AsF64(0x4023_c57c_0000_0000n), reinterpretU64AsF64(0x4024_36a0_0000_0000n)] },  // ~10
   4832  ] as ScalarPairToIntervalCase[],
   4833 } as const;
   4834 
   4835 g.test('powInterval')
   4836  .params(u =>
   4837    u
   4838      .combine('trait', ['f32', 'f16'] as const)
   4839      .beginSubcases()
   4840      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4841        const trait = FP[p.trait];
   4842        const constants = trait.constants();
   4843        // prettier-ignore
   4844        return [
   4845          { input: [-1, 0], expected: kUnboundedEndpoints },
   4846          { input: [0, 0], expected: kUnboundedEndpoints },
   4847          { input: [0, 1], expected: kUnboundedEndpoints },
   4848          { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
   4849          { input: [constants.positive.max, 1], expected: kUnboundedEndpoints },
   4850 
   4851          ...kPowIntervalCases[p.trait],
   4852        ];
   4853      })
   4854  )
   4855  .fn(t => {
   4856    const [x, y] = t.params.input;
   4857    const trait = FP[t.params.trait];
   4858    const expected = trait.toInterval(t.params.expected);
   4859    const got = trait.powInterval(x, y);
   4860    t.expect(
   4861      objectEquals(expected, got),
   4862      `${t.params.trait}.powInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4863    );
   4864  });
   4865 
   4866 // prettier-ignore
   4867 const kRemainderCases = {
   4868  f32: [
   4869    { input: [1, 0.1], expected: [reinterpretU32AsF32(0xb4000000), reinterpretU32AsF32(0x3dccccd8)] }, // ~[0, 0.1]
   4870    { input: [-1, 0.1], expected: [reinterpretU32AsF32(0xbdccccd8), reinterpretU32AsF32(0x34000000)] }, // ~[-0.1, 0]
   4871    { input: [1, -0.1], expected: [reinterpretU32AsF32(0xb4000000), reinterpretU32AsF32(0x3dccccd8)] }, // ~[0, 0.1]
   4872    { input: [-1, -0.1], expected: [reinterpretU32AsF32(0xbdccccd8), reinterpretU32AsF32(0x34000000)] }, // ~[-0.1, 0]
   4873  ] as ScalarPairToIntervalCase[],
   4874  f16: [
   4875    { input: [1, 0.1], expected: [reinterpretU16AsF16(0x9400), reinterpretU16AsF16(0x2e70)] }, // ~[0, 0.1]
   4876    { input: [-1, 0.1], expected: [reinterpretU16AsF16(0xae70), reinterpretU16AsF16(0x1400)] }, // ~[-0.1, 0]
   4877    { input: [1, -0.1], expected: [reinterpretU16AsF16(0x9400), reinterpretU16AsF16(0x2e70)] }, // ~[0, 0.1]
   4878    { input: [-1, -0.1], expected: [reinterpretU16AsF16(0xae70), reinterpretU16AsF16(0x1400)] }, // ~[-0.1, 0]
   4879  ] as ScalarPairToIntervalCase[],
   4880 } as const;
   4881 
   4882 g.test('remainderInterval')
   4883  .params(u =>
   4884    u
   4885      .combine('trait', ['f32', 'f16'] as const)
   4886      .beginSubcases()
   4887      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4888        const trait = kFPTraitForULP[p.trait];
   4889        const constants = FP[trait].constants();
   4890 
   4891        // prettier-ignore
   4892        return [
   4893          ...kRemainderCases[trait],
   4894          // Normals
   4895          { input: [0, 1], expected: 0 },
   4896          { input: [0, -1], expected: 0 },
   4897          { input: [1, 1], expected: [0, 1] },
   4898          { input: [1, -1], expected: [0, 1] },
   4899          { input: [-1, 1], expected: [-1, 0] },
   4900          { input: [-1, -1], expected: [-1, 0] },
   4901          { input: [4, 2], expected: [0, 2] },
   4902          { input: [-4, 2], expected: [-2, 0] },
   4903          { input: [4, -2], expected: [0, 2] },
   4904          { input: [-4, -2], expected: [-2, 0] },
   4905          { input: [2, 4], expected: [2, 2] },
   4906          { input: [-2, 4], expected: -2 },
   4907          { input: [2, -4], expected: 2 },
   4908          { input: [-2, -4], expected: [-2, -2] },
   4909          { input: [0, 0.1], expected: 0 },
   4910          { input: [0, -0.1], expected: 0 },
   4911          { input: [8.5, 2], expected: 0.5 },
   4912          { input: [1.125, 1], expected: 0.125 },
   4913 
   4914          // Denominator out of range
   4915          { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
   4916          { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
   4917          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4918          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   4919          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   4920          { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
   4921          { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
   4922          { input: [1, 0], expected: kUnboundedEndpoints },
   4923          { input: [1, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
   4924        ];
   4925      })
   4926  )
   4927  .fn(t => {
   4928    const trait = FP[t.params.trait];
   4929    const [x, y] = t.params.input;
   4930    const expected = trait.toInterval(t.params.expected);
   4931    const got = trait.remainderInterval(x, y);
   4932    t.expect(
   4933      objectEquals(expected, got),
   4934      `${t.params.trait}.remainderInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   4935    );
   4936  });
   4937 
   4938 g.test('stepInterval')
   4939  .params(u =>
   4940    u
   4941      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   4942      .beginSubcases()
   4943      .expandWithParams<ScalarPairToIntervalCase>(p => {
   4944        const constants = FP[p.trait].constants();
   4945        // prettier-ignore
   4946        return [
   4947          // 32-bit normals
   4948          { input: [0, 0], expected: 1 },
   4949          { input: [1, 1], expected: 1 },
   4950          { input: [0, 1], expected: 1 },
   4951          { input: [1, 0], expected: 0 },
   4952          { input: [-1, -1], expected: 1 },
   4953          { input: [0, -1], expected: 0 },
   4954          { input: [-1, 0], expected: 1 },
   4955          { input: [-1, 1], expected: 1 },
   4956          { input: [1, -1], expected: 0 },
   4957 
   4958          // 64-bit normals
   4959          // number is f64 internally, so the value representing the literal
   4960          // 0.1/-0.1 will always be exactly representable in AbstractFloat,
   4961          // since AF is also f64 internally.
   4962          // It is impossible with normals to cause the rounding ambiguity that
   4963          // causes the 0 or 1 result.
   4964          { input: [0.1, 0.1], expected: p.trait === 'abstract' ? 1 : [0, 1] },
   4965          { input: [0, 0.1], expected: 1 },
   4966          { input: [0.1, 0], expected: 0 },
   4967          { input: [0.1, 1], expected: 1 },
   4968          { input: [1, 0.1], expected: 0 },
   4969          { input: [-0.1, -0.1], expected: p.trait === 'abstract' ? 1 : [0, 1] },
   4970          { input: [0, -0.1], expected: 0 },
   4971          { input: [-0.1, 0], expected: 1 },
   4972          { input: [-0.1, -1], expected: 0 },
   4973          { input: [-1, -0.1], expected: 1 },
   4974 
   4975          // Subnormals
   4976          { input: [0, constants.positive.subnormal.max], expected: 1 },
   4977          { input: [0, constants.positive.subnormal.min], expected: 1 },
   4978          { input: [0, constants.negative.subnormal.max], expected: [0, 1] },
   4979          { input: [0, constants.negative.subnormal.min], expected: [0, 1] },
   4980          { input: [1, constants.positive.subnormal.max], expected: 0 },
   4981          { input: [1, constants.positive.subnormal.min], expected: 0 },
   4982          { input: [1, constants.negative.subnormal.max], expected: 0 },
   4983          { input: [1, constants.negative.subnormal.min], expected: 0 },
   4984          { input: [-1, constants.positive.subnormal.max], expected: 1 },
   4985          { input: [-1, constants.positive.subnormal.min], expected: 1 },
   4986          { input: [-1, constants.negative.subnormal.max], expected: 1 },
   4987          { input: [-1, constants.negative.subnormal.min], expected: 1 },
   4988          { input: [constants.positive.subnormal.max, 0], expected: [0, 1] },
   4989          { input: [constants.positive.subnormal.min, 0], expected: [0, 1] },
   4990          { input: [constants.negative.subnormal.max, 0], expected: 1 },
   4991          { input: [constants.negative.subnormal.min, 0], expected: 1 },
   4992          { input: [constants.positive.subnormal.max, 1], expected: 1 },
   4993          { input: [constants.positive.subnormal.min, 1], expected: 1 },
   4994          { input: [constants.negative.subnormal.max, 1], expected: 1 },
   4995          { input: [constants.negative.subnormal.min, 1], expected: 1 },
   4996          { input: [constants.positive.subnormal.max, -1], expected: 0 },
   4997          { input: [constants.positive.subnormal.min, -1], expected: 0 },
   4998          { input: [constants.negative.subnormal.max, -1], expected: 0 },
   4999          { input: [constants.negative.subnormal.min, -1], expected: 0 },
   5000          { input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: 1 },
   5001          { input: [constants.positive.subnormal.max, constants.negative.subnormal.min], expected: [0, 1] },
   5002 
   5003          // Infinities
   5004          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   5005          { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
   5006          { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5007          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   5008          { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
   5009          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5010          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5011          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5012        ];
   5013      })
   5014  )
   5015  .fn(t => {
   5016    const trait = FP[t.params.trait];
   5017    const [edge, x] = t.params.input;
   5018    const expected = trait.toInterval(t.params.expected);
   5019    const got = trait.stepInterval(edge, x);
   5020    t.expect(
   5021      objectEquals(expected, got),
   5022      `${t.params.trait}.stepInterval(${edge}, ${x}) returned ${got}. Expected ${expected}`
   5023    );
   5024  });
   5025 
   5026 // prettier-ignore
   5027 const kSubtractionInterval64BitsNormalCases = {
   5028  f32: [
   5029    // 0.1 falls between f32 0x3DCCCCCC and 0x3DCCCCCD, -0.1 falls between f32 0xBDCCCCCD and 0xBDCCCCCC
   5030    // Expect f32 interval [0x3DCCCCCC-0x3DCCCCCD, 0x3DCCCCCD-0x3DCCCCCC]
   5031    { input: [0.1, 0.1], expected: [reinterpretU32AsF32(0x3dcccccc)-reinterpretU32AsF32(0x3dcccccd), reinterpretU32AsF32(0x3dcccccd)-reinterpretU32AsF32(0x3dcccccc)] },
   5032    // Expect f32 interval [0xBDCCCCCD-0xBDCCCCCC, 0xBDCCCCCC-0xBDCCCCCD]
   5033    { input: [-0.1, -0.1], expected: [reinterpretU32AsF32(0xbdcccccd)-reinterpretU32AsF32(0xbdcccccc), reinterpretU32AsF32(0xbdcccccc)-reinterpretU32AsF32(0xbdcccccd)] },
   5034    // Expect f32 interval [0x3DCCCCCC-0xBDCCCCCC, 0x3DCCCCCD-0xBDCCCCCD]
   5035    { input: [0.1, -0.1], expected: [reinterpretU32AsF32(0x3dcccccc)-reinterpretU32AsF32(0xbdcccccc), reinterpretU32AsF32(0x3dcccccd)-reinterpretU32AsF32(0xbdcccccd)] },
   5036    // Expect f32 interval [0xBDCCCCCD-0x3DCCCCCD, 0xBDCCCCCC-0x3DCCCCCC]
   5037    { input: [-0.1, 0.1], expected: [reinterpretU32AsF32(0xbdcccccd)-reinterpretU32AsF32(0x3dcccccd), reinterpretU32AsF32(0xbdcccccc)-reinterpretU32AsF32(0x3dcccccc)] },
   5038    { input: [1, kValue.f32.positive.min], expected: [reinterpretU32AsF32(0x3f7fffff), 1.0] },
   5039    { input: [1, kValue.f32.negative.max], expected: [ 1.0, reinterpretU32AsF32(0x3f800001)] },
   5040    { input: [-1, kValue.f32.positive.min], expected: [reinterpretU32AsF32(0xbf800001), -1.0] },
   5041    { input: [-1, kValue.f32.negative.max], expected: [-1.0, reinterpretU32AsF32(0xbf7fffff)] },
   5042    { input: [1, kValue.f32.positive.max], expected:  [reinterpretU32AsF32(0xff7fffff), reinterpretU32AsF32(0xff7ffffe)]},
   5043    { input: [1, kValue.f32.negative.min], expected: kUnboundedEndpoints},
   5044    { input: [-1, kValue.f32.positive.max], expected:kUnboundedEndpoints},
   5045    { input: [-1, kValue.f32.negative.min], expected:  [reinterpretU32AsF32(0x7f7ffffe) , reinterpretU32AsF32(0x7f7fffff)]  },
   5046    // Symmetric with above (expected will be anti-symmetric)
   5047    { input: [kValue.f32.positive.min, 1], expected: [-1.0 , reinterpretU32AsF32(0xbf7fffff)] },
   5048    { input: [kValue.f32.negative.max, 1], expected: [reinterpretU32AsF32(0xbf800001), -1.0] },
   5049    { input: [kValue.f32.positive.min, -1], expected: [1.0, reinterpretU32AsF32(0x3f800001)] },
   5050    { input: [kValue.f32.negative.max, -1], expected: [reinterpretU32AsF32(0x3f7fffff), 1.0] },
   5051    { input: [kValue.f32.positive.max, 1], expected:  [reinterpretU32AsF32(0x7f7ffffe), reinterpretU32AsF32(0x7f7fffff)]},
   5052    { input: [kValue.f32.negative.min, 1], expected: kUnboundedEndpoints},
   5053    { input: [kValue.f32.positive.max, -1], expected:kUnboundedEndpoints},
   5054    { input: [kValue.f32.negative.min, -1], expected:  [reinterpretU32AsF32(0xff7fffff) , reinterpretU32AsF32(0xff7ffffe)]  },
   5055  ] as ScalarPairToIntervalCase[],
   5056  f16: [
   5057    // 0.1 falls between f16 0x2E66 and 0x2E67, -0.1 falls between f16 0xAE67 and 0xAE66
   5058    // Expect f16 interval [0x2E66-0x2E67, 0x2E67-0x2E66]
   5059    { input: [0.1, 0.1], expected: [reinterpretU16AsF16(0x2e66)-reinterpretU16AsF16(0x2e67), reinterpretU16AsF16(0x2e67)-reinterpretU16AsF16(0x2e66)] },
   5060    // Expect f16 interval [0xAE67-0xAE66, 0xAE66-0xAE67]
   5061    { input: [-0.1, -0.1], expected: [reinterpretU16AsF16(0xae67)-reinterpretU16AsF16(0xae66), reinterpretU16AsF16(0xae66)-reinterpretU16AsF16(0xae67)] },
   5062    // Expect f16 interval [0x2E66-0xAE66, 0x2E67-0xAE67]
   5063    { input: [0.1, -0.1], expected: [reinterpretU16AsF16(0x2e66)-reinterpretU16AsF16(0xae66), reinterpretU16AsF16(0x2e67)-reinterpretU16AsF16(0xae67)] },
   5064    // Expect f16 interval [0xAE67-0x2E67, 0xAE66-0x2E66]
   5065    { input: [-0.1, 0.1], expected: [reinterpretU16AsF16(0xae67)-reinterpretU16AsF16(0x2e67), reinterpretU16AsF16(0xae66)-reinterpretU16AsF16(0x2e66)] },
   5066  ] as ScalarPairToIntervalCase[],
   5067 } as const;
   5068 
   5069 g.test('subtractionInterval')
   5070  .params(u =>
   5071    u
   5072      .combine('trait', ['f32', 'f16'] as const)
   5073      .beginSubcases()
   5074      .expandWithParams<ScalarPairToIntervalCase>(p => {
   5075        const trait = FP[p.trait];
   5076        const constants = trait.constants();
   5077        // prettier-ignore
   5078        return [
   5079          // Representable normals
   5080          { input: [0, 0], expected: 0 },
   5081          { input: [1, 0], expected: 1 },
   5082          { input: [0, 1], expected: -1 },
   5083          { input: [-1, 0], expected: -1 },
   5084          { input: [0, -1], expected: 1 },
   5085          { input: [1, 1], expected: 0 },
   5086          { input: [1, -1], expected: 2 },
   5087          { input: [-1, 1], expected: -2 },
   5088          { input: [-1, -1], expected: 0 },
   5089 
   5090          // 64-bit normals that can not be exactly represented in f32/f16
   5091          { input: [0.1, 0], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   5092          { input: [0, -0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] },
   5093          { input: [-0.1, 0], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },
   5094          { input: [0, 0.1], expected: kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'] },
   5095          ...kSubtractionInterval64BitsNormalCases[p.trait],
   5096 
   5097          // Subnormals
   5098          { input: [constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   5099          { input: [0, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, 0] },
   5100          { input: [constants.positive.subnormal.min, 0], expected: [0, constants.positive.subnormal.min] },
   5101          { input: [0, constants.positive.subnormal.min], expected: [constants.negative.subnormal.max, 0] },
   5102          { input: [constants.negative.subnormal.max, 0], expected: [constants.negative.subnormal.max, 0] },
   5103          { input: [0, constants.negative.subnormal.max], expected: [0, constants.positive.subnormal.min] },
   5104          { input: [constants.negative.subnormal.min, 0], expected: [constants.negative.subnormal.min, 0] },
   5105          { input: [0, constants.negative.subnormal.min], expected: [0, constants.positive.subnormal.max] },
   5106 
   5107          // Infinities
   5108          { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
   5109          { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
   5110          { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5111          { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
   5112          { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
   5113          { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5114          { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5115          { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5116        ];
   5117      })
   5118  )
   5119  .fn(t => {
   5120    const [x, y] = t.params.input;
   5121    const trait = FP[t.params.trait];
   5122    const expected = trait.toInterval(t.params.expected);
   5123    const got = trait.subtractionInterval(x, y);
   5124    t.expect(
   5125      objectEquals(expected, got),
   5126      `${t.params.trait}.subtractionInterval(${x}, ${y}) returned ${got}. Expected ${expected}`
   5127    );
   5128  });
   5129 
   5130 interface ScalarTripleToIntervalCase {
   5131  input: [number, number, number];
   5132  expected: number | IntervalEndpoints;
   5133 }
   5134 
   5135 g.test('clampMedianInterval')
   5136  .params(u =>
   5137    u
   5138      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   5139      .beginSubcases()
   5140      .expandWithParams<ScalarTripleToIntervalCase>(p => {
   5141        const trait = FP[p.trait];
   5142        const constants = trait.constants();
   5143        // prettier-ignore
   5144        return [
   5145          // Normals
   5146          { input: [0, 0, 0], expected: 0 },
   5147          { input: [1, 0, 0], expected: 0 },
   5148          { input: [0, 1, 0], expected: 0 },
   5149          { input: [0, 0, 1], expected: 0 },
   5150          { input: [1, 0, 1], expected: 1 },
   5151          { input: [1, 1, 0], expected: 1 },
   5152          { input: [0, 1, 1], expected: 1 },
   5153          { input: [1, 1, 1], expected: 1 },
   5154          { input: [1, 10, 100], expected: 10 },
   5155          { input: [10, 1, 100], expected: 10 },
   5156          { input: [100, 1, 10], expected: 10 },
   5157          { input: [-10, 1, 100], expected: 1 },
   5158          { input: [10, 1, -100], expected: 1 },
   5159          { input: [-10, 1, -100], expected: -10 },
   5160          { input: [-10, -10, -10], expected: -10 },
   5161 
   5162          // Subnormals
   5163          { input: [constants.positive.subnormal.max, 0, 0], expected: 0 },
   5164          { input: [0, constants.positive.subnormal.max, 0], expected: 0 },
   5165          { input: [0, 0, constants.positive.subnormal.max], expected: 0 },
   5166          { input: [constants.positive.subnormal.max, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5167          { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   5168          { input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5169          { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5170          { input: [constants.positive.subnormal.max, constants.positive.subnormal.min, constants.negative.subnormal.max], expected: [0, constants.positive.subnormal.min] },
   5171          { input: [constants.positive.subnormal.max, constants.negative.subnormal.min, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, 0] },
   5172          { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: constants.positive.max },
   5173 
   5174          // Infinities
   5175          { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
   5176          { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5177          { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5178          { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5179        ];
   5180      })
   5181  )
   5182  .fn(t => {
   5183    const [x, y, z] = t.params.input;
   5184    const trait = FP[t.params.trait];
   5185    const expected = trait.toInterval(t.params.expected);
   5186    const got = trait.clampMedianInterval(x, y, z);
   5187    t.expect(
   5188      objectEquals(expected, got),
   5189      `${t.params.trait}.clampMedianInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}`
   5190    );
   5191  });
   5192 
   5193 g.test('clampMinMaxInterval')
   5194  .params(u =>
   5195    u
   5196      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   5197      .beginSubcases()
   5198      .expandWithParams<ScalarTripleToIntervalCase>(p => {
   5199        const trait = FP[p.trait];
   5200        const constants = trait.constants();
   5201        // prettier-ignore
   5202        return [
   5203          // Normals
   5204          { input: [0, 0, 0], expected: 0 },
   5205          { input: [1, 0, 0], expected: 0 },
   5206          { input: [0, 1, 0], expected: 0 },
   5207          { input: [0, 0, 1], expected: 0 },
   5208          { input: [1, 0, 1], expected: 1 },
   5209          { input: [1, 1, 0], expected: 0 },
   5210          { input: [0, 1, 1], expected: 1 },
   5211          { input: [1, 1, 1], expected: 1 },
   5212          { input: [1, 10, 100], expected: 10 },
   5213          { input: [10, 1, 100], expected: 10 },
   5214          { input: [100, 1, 10], expected: 10 },
   5215          { input: [-10, 1, 100], expected: 1 },
   5216          { input: [10, 1, -100], expected: -100 },
   5217          { input: [-10, 1, -100], expected: -100 },
   5218          { input: [-10, -10, -10], expected: -10 },
   5219 
   5220          // Subnormals
   5221          { input: [constants.positive.subnormal.max, 0, 0], expected: [0, constants.positive.subnormal.max] },
   5222          { input: [0, constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   5223          { input: [0, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5224          { input: [constants.positive.subnormal.max, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5225          { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, 0], expected: [0, constants.positive.subnormal.max] },
   5226          { input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5227          { input: [constants.positive.subnormal.max, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5228          { input: [constants.positive.subnormal.max, constants.positive.subnormal.min, constants.negative.subnormal.max], expected: [constants.negative.subnormal.max, constants.positive.subnormal.max] },
   5229          { input: [constants.positive.subnormal.max, constants.negative.subnormal.min, constants.negative.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
   5230          { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] },
   5231 
   5232          // Infinities
   5233          { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
   5234          { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5235          { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5236          { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5237        ];
   5238      })
   5239  )
   5240  .fn(t => {
   5241    const [x, y, z] = t.params.input;
   5242    const trait = FP[t.params.trait];
   5243    const expected = trait.toInterval(t.params.expected);
   5244    const got = trait.clampMinMaxInterval(x, y, z);
   5245    t.expect(
   5246      objectEquals(expected, got),
   5247      `${t.params.trait}.clampMinMaxInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}`
   5248    );
   5249  });
   5250 
   5251 // prettier-ignore
   5252 const kFmaIntervalCases = {
   5253  f32: [
   5254    // positive.subnormal.max * positive.subnormal.max is much smaller than positive.subnormal.min but larger than 0, rounded to [0, positive.subnormal.min]
   5255    { input: [kValue.f32.positive.subnormal.max, kValue.f32.positive.subnormal.max, 0], expected: [0, kValue.f32.positive.subnormal.min] },
   5256    // positive.subnormal.max * positive.subnormal.max rounded to 0 or positive.subnormal.min,
   5257    // 0 + constants.positive.subnormal.max rounded to [0, constants.positive.subnormal.max],
   5258    // positive.subnormal.min + constants.positive.subnormal.max = constants.positive.min.
   5259    { input: [kValue.f32.positive.subnormal.max, kValue.f32.positive.subnormal.max, kValue.f32.positive.subnormal.max], expected: [0, kValue.f32.positive.min] },
   5260    // positive.subnormal.max * positive.subnormal.max rounded to 0 or positive.subnormal.min,
   5261    // negative.subnormal.max may flushed to 0,
   5262    // minimum case: 0 + negative.subnormal.max rounded to [negative.subnormal.max, 0],
   5263    // maximum case: positive.subnormal.min + 0 rounded to [0, positive.subnormal.min].
   5264    { input: [kValue.f32.positive.subnormal.max, kValue.f32.positive.subnormal.min, kValue.f32.negative.subnormal.max], expected: [kValue.f32.negative.subnormal.max, kValue.f32.positive.subnormal.min] },
   5265    // positive.subnormal.max * negative.subnormal.min rounded to -0.0 or negative.subnormal.max = -1 * [subnormal ulp],
   5266    // negative.subnormal.max = -1 * [subnormal ulp] may flushed to -0.0,
   5267    // minimum case: -1 * [subnormal ulp] + -1 * [subnormal ulp] rounded to [-2 * [subnormal ulp], 0],
   5268    // maximum case: -0.0 + -0.0 = 0.
   5269    { input: [kValue.f32.positive.subnormal.max, kValue.f32.negative.subnormal.min, kValue.f32.negative.subnormal.max], expected: [-2 * FP['f32'].oneULP(0, 'no-flush'), 0] },
   5270  ] as ScalarTripleToIntervalCase[],
   5271  f16: [
   5272    // positive.subnormal.max * positive.subnormal.max is much smaller than positive.subnormal.min but larger than 0, rounded to [0, positive.subnormal.min]
   5273    { input: [kValue.f16.positive.subnormal.max, kValue.f16.positive.subnormal.max, 0], expected: [0, kValue.f16.positive.subnormal.min] },
   5274    // positive.subnormal.max * positive.subnormal.max rounded to 0 or positive.subnormal.min,
   5275    // 0 + constants.positive.subnormal.max rounded to [0, constants.positive.subnormal.max],
   5276    // positive.subnormal.min + constants.positive.subnormal.max = constants.positive.min.
   5277    { input: [kValue.f16.positive.subnormal.max, kValue.f16.positive.subnormal.max, kValue.f16.positive.subnormal.max], expected: [0, kValue.f16.positive.min] },
   5278    // positive.subnormal.max * positive.subnormal.max rounded to 0 or positive.subnormal.min,
   5279    // negative.subnormal.max may flushed to 0,
   5280    // minimum case: 0 + negative.subnormal.max rounded to [negative.subnormal.max, 0],
   5281    // maximum case: positive.subnormal.min + 0 rounded to [0, positive.subnormal.min].
   5282    { input: [kValue.f16.positive.subnormal.max, kValue.f16.positive.subnormal.min, kValue.f16.negative.subnormal.max], expected: [kValue.f16.negative.subnormal.max, kValue.f16.positive.subnormal.min] },
   5283    // positive.subnormal.max * negative.subnormal.min rounded to -0.0 or negative.subnormal.max = -1 * [subnormal ulp],
   5284    // negative.subnormal.max = -1 * [subnormal ulp] may flushed to -0.0,
   5285    // minimum case: -1 * [subnormal ulp] + -1 * [subnormal ulp] rounded to [-2 * [subnormal ulp], 0],
   5286    // maximum case: -0.0 + -0.0 = 0.
   5287    { input: [kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.min, kValue.f16.negative.subnormal.max], expected: [-2 * FP['f16'].oneULP(0, 'no-flush'), 0] },  ] as ScalarTripleToIntervalCase[],
   5288 } as const;
   5289 
   5290 g.test('fmaInterval')
   5291  .params(u =>
   5292    u
   5293      .combine('trait', ['f32', 'f16'] as const)
   5294      .beginSubcases()
   5295      .expandWithParams<ScalarTripleToIntervalCase>(p => {
   5296        const trait = FP[p.trait];
   5297        const constants = trait.constants();
   5298        // prettier-ignore
   5299        return [
   5300          // Normals
   5301          { input: [0, 0, 0], expected: 0 },
   5302          { input: [1, 0, 0], expected: 0 },
   5303          { input: [0, 1, 0], expected: 0 },
   5304          { input: [0, 0, 1], expected: 1 },
   5305          { input: [1, 0, 1], expected: 1 },
   5306          { input: [1, 1, 0], expected: 1 },
   5307          { input: [0, 1, 1], expected: 1 },
   5308          { input: [1, 1, 1], expected: 2 },
   5309          { input: [1, 10, 100], expected: 110 },
   5310          { input: [10, 1, 100], expected: 110 },
   5311          { input: [100, 1, 10], expected: 110 },
   5312          { input: [-10, 1, 100], expected: 90 },
   5313          { input: [10, 1, -100], expected: -90 },
   5314          { input: [-10, 1, -100], expected: -110 },
   5315          { input: [-10, -10, -10], expected: 90 },
   5316 
   5317          // Subnormals
   5318          { input: [constants.positive.subnormal.max, 0, 0], expected: 0 },
   5319          { input: [0, constants.positive.subnormal.max, 0], expected: 0 },
   5320          { input: [0, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5321          { input: [constants.positive.subnormal.max, 0, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5322          { input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
   5323 
   5324          // Infinities
   5325          { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
   5326          { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5327          { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
   5328          { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
   5329          { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: kUnboundedEndpoints },
   5330          ...kFmaIntervalCases[p.trait],
   5331        ];
   5332      })
   5333  )
   5334  .fn(t => {
   5335    const trait = FP[t.params.trait];
   5336    const expected = trait.toInterval(t.params.expected);
   5337    const got = trait.fmaInterval(...t.params.input);
   5338    t.expect(
   5339      objectEquals(expected, got),
   5340      `${t.params.trait}.fmaInterval(${t.params.input.join(
   5341        ','
   5342      )}) returned ${got}. Expected ${expected}`
   5343    );
   5344  });
   5345 
   5346 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   5347 // human-readable form due to the inherited nature of the errors.
   5348 // prettier-ignore
   5349 const kMixImpreciseIntervalCases = {
   5350  f32: [
   5351    // [0.0, 1.0] cases
   5352    { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9999_8000_0000n), reinterpretU64AsF64(0x3fb9_9999_a000_0000n)] },  // ~0.1
   5353    { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] },  // ~0.9
   5354    // [1.0, 0.0] cases
   5355    { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] },  // ~0.9
   5356    { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_9999_0000_0000n), reinterpretU64AsF64(0x3fb9_999a_0000_0000n)] },  // ~0.1
   5357    // [0.0, 10.0] cases
   5358    { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_ffff_e000_0000n), reinterpretU64AsF64(0x3ff0_0000_2000_0000n)] },  // ~1
   5359    { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_ffff_e000_0000n), reinterpretU64AsF64(0x4022_0000_2000_0000n)] },  // ~9
   5360    // [2.0, 10.0] cases
   5361    { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6666_6000_0000n), reinterpretU64AsF64(0x4006_6666_8000_0000n)] },  // ~2.8
   5362    { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6666_6000_0000n), reinterpretU64AsF64(0x4022_6666_8000_0000n)] },  // ~9.2
   5363    // [-1.0, 1.0] cases
   5364    { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_a000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] },  // ~-0.8
   5365    { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] },  // ~0.8
   5366 
   5367    // Showing how precise and imprecise versions diff
   5368    // Note that this expectation is unbounded [-inf,inf] in f32 as |10.0| is much smaller than
   5369    // |f32.negative.min|. The implementation of imprecise is is := x + (y - x) * z
   5370    // So that 10 - f32.negative.min == [f32.positive.max, inf] due to correctly rounding of unbounded precision.
   5371    { input: [kValue.f32.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints},
   5372    // -10.0 is the same, however due to  smaller than f32.negative.min we end up with [ULP_neg(f32.positive.max), f32.positive.max]
   5373    { input: [kValue.f32.negative.min, -10.0, 1.0], expected:  [reinterpretU32AsF32(0xf3800000), 0.0]  },
   5374    { input: [kValue.f32.negative.min,  10.0, 5.0], expected: kUnboundedEndpoints },
   5375    { input: [kValue.f32.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
   5376    { input: [kValue.f32.negative.min, 10.0, 0.5], expected: kUnboundedEndpoints },
   5377    { input: [kValue.f32.negative.min, -10.0, 0.5], expected:  [reinterpretU32AsF32(0xff000000),  reinterpretU32AsF32(0xfeffffff) ]},
   5378    // Test the case of unbounded precision of mix when implementing the subtraction intermediate value.
   5379    { input: [kValue.f32.positive.min, 1.0, kValue.f32.positive.min], expected: [reinterpretU32AsF32(0x00800000),reinterpretU32AsF32(0x01000000)]},
   5380  ] as ScalarTripleToIntervalCase[],
   5381  f16: [
   5382    // [0.0, 1.0] cases
   5383    { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9800_0000_0000n), reinterpretU64AsF64(0x3fb9_9c00_0000_0000n)] },  // ~0.1
   5384    { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] },  // ~0.9
   5385    // [1.0, 0.0] cases
   5386    { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] },  // ~0.9
   5387    { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_8000_0000_0000n), reinterpretU64AsF64(0x3fb9_a000_0000_0000n)] },  // ~0.1
   5388    // [0.0, 10.0] cases
   5389    { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0400_0000_0000n)] },  // ~1
   5390    { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_fc00_0000_0000n), reinterpretU64AsF64(0x4022_0400_0000_0000n)] },  // ~9
   5391    // [2.0, 10.0] cases
   5392    { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6400_0000_0000n), reinterpretU64AsF64(0x4006_6800_0000_0000n)] },  // ~2.8
   5393    { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6400_0000_0000n), reinterpretU64AsF64(0x4022_6800_0000_0000n)] },  // ~9.2
   5394    // [-1.0, 1.0] cases
   5395    { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9c00_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] },  // ~-0.8
   5396    { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] },  // ~0.8
   5397 
   5398    // Showing how precise and imprecise versions diff
   5399    // In imprecise version, we compute (y - x), where y = 10 and x = -65504, the result is 65514
   5400    // and cause an overflow in f16.
   5401    { input: [kValue.f16.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints },
   5402    // (y - x) * 1.0, where y = -10 and x = -65504, the result is 65494 rounded to 65472 or 65504.
   5403    // The result is -65504 + 65472 = -32 or -65504 + 65504 = 0.
   5404    { input: [kValue.f16.negative.min, -10.0, 1.0], expected: [-32, 0] },
   5405    { input: [kValue.f16.negative.min,  10.0, 5.0], expected: kUnboundedEndpoints },
   5406    { input: [kValue.f16.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
   5407    { input: [kValue.f16.negative.min,  10.0, 0.5], expected: kUnboundedEndpoints },
   5408    { input: [kValue.f16.negative.min, -10.0, 0.5], expected: [-32768.0, -32752.0] },
   5409  ] as ScalarTripleToIntervalCase[],
   5410 } as const;
   5411 
   5412 g.test('mixImpreciseInterval')
   5413  .params(u =>
   5414    u
   5415      .combine('trait', ['f32', 'f16'] as const)
   5416      .beginSubcases()
   5417      .expandWithParams<ScalarTripleToIntervalCase>(p => {
   5418        const trait = FP[p.trait];
   5419        const constants = trait.constants();
   5420        // prettier-ignore
   5421        return [
   5422          ...kMixImpreciseIntervalCases[p.trait],
   5423 
   5424          // [0.0, 1.0] cases
   5425          { input: [0.0, 1.0, -1.0], expected: -1.0 },
   5426          { input: [0.0, 1.0, 0.0], expected: 0.0 },
   5427          { input: [0.0, 1.0, 0.5], expected: 0.5 },
   5428          { input: [0.0, 1.0, 1.0], expected: 1.0 },
   5429          { input: [0.0, 1.0, 2.0], expected: 2.0 },
   5430 
   5431          // [1.0, 0.0] cases
   5432          { input: [1.0, 0.0, -1.0], expected: 2.0 },
   5433          { input: [1.0, 0.0, 0.0], expected: 1.0 },
   5434          { input: [1.0, 0.0, 0.5], expected: 0.5 },
   5435          { input: [1.0, 0.0, 1.0], expected: 0.0 },
   5436          { input: [1.0, 0.0, 2.0], expected: -1.0 },
   5437 
   5438          // [0.0, 10.0] cases
   5439          { input: [0.0, 10.0, -1.0], expected: -10.0 },
   5440          { input: [0.0, 10.0, 0.0], expected: 0.0 },
   5441          { input: [0.0, 10.0, 0.5], expected: 5.0 },
   5442          { input: [0.0, 10.0, 1.0], expected: 10.0 },
   5443          { input: [0.0, 10.0, 2.0], expected: 20.0 },
   5444 
   5445          // [2.0, 10.0] cases
   5446          { input: [2.0, 10.0, -1.0], expected: -6.0 },
   5447          { input: [2.0, 10.0, 0.0], expected: 2.0 },
   5448          { input: [2.0, 10.0, 0.5], expected: 6.0 },
   5449          { input: [2.0, 10.0, 1.0], expected: 10.0 },
   5450          { input: [2.0, 10.0, 2.0], expected: 18.0 },
   5451 
   5452          // [-1.0, 1.0] cases
   5453          { input: [-1.0, 1.0, -2.0], expected: -5.0 },
   5454          { input: [-1.0, 1.0, 0.0], expected: -1.0 },
   5455          { input: [-1.0, 1.0, 0.5], expected: 0.0 },
   5456          { input: [-1.0, 1.0, 1.0], expected: 1.0 },
   5457          { input: [-1.0, 1.0, 2.0], expected: 3.0 },
   5458 
   5459          // Infinities
   5460          { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
   5461          { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedEndpoints },
   5462          { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedEndpoints },
   5463          { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
   5464          { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
   5465          { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
   5466          { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedEndpoints },
   5467          { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedEndpoints },
   5468          { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedEndpoints },
   5469          { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedEndpoints },
   5470        ];
   5471      })
   5472  )
   5473  .fn(t => {
   5474    const [x, y, z] = t.params.input;
   5475    const trait = FP[t.params.trait];
   5476    const expected = trait.toInterval(t.params.expected);
   5477    const got = trait.mixImpreciseInterval(x, y, z);
   5478    t.expect(
   5479      objectEquals(expected, got),
   5480      `${t.params.trait}.mixImpreciseInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}`
   5481    );
   5482  });
   5483 
   5484 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   5485 // human-readable form due to the inherited nature of the errors.
   5486 // prettier-ignore
   5487 const kMixPreciseIntervalCases = {
   5488  f32: [
   5489    // [0.0, 1.0] cases
   5490    { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9999_8000_0000n), reinterpretU64AsF64(0x3fb9_9999_a000_0000n)] },  // ~0.1
   5491    { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] },  // ~0.9
   5492    // [1.0, 0.0] cases
   5493    { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] },  // ~0.9
   5494    { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_9999_0000_0000n), reinterpretU64AsF64(0x3fb9_999a_0000_0000n)] },  // ~0.1
   5495    // [0.0, 10.0] cases
   5496    { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_ffff_e000_0000n), reinterpretU64AsF64(0x3ff0_0000_2000_0000n)] },  // ~1
   5497    { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_ffff_e000_0000n), reinterpretU64AsF64(0x4022_0000_2000_0000n)] },  // ~9
   5498    // [2.0, 10.0] cases
   5499    { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6666_4000_0000n), reinterpretU64AsF64(0x4006_6666_8000_0000n)] },  // ~2.8
   5500    { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6666_4000_0000n), reinterpretU64AsF64(0x4022_6666_a000_0000n)] },  // ~9.2
   5501    // [-1.0, 1.0] cases
   5502    { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_c000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] },  // ~-0.8
   5503    { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] },  // ~0.8
   5504 
   5505    // Showing how precise and imprecise versions diff
   5506    { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 10 },
   5507    { input: [kValue.f32.negative.min, -10.0, 1.0], expected: -10 },
   5508    { input: [kValue.f32.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
   5509    { input: [kValue.f32.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
   5510    { input: [kValue.f32.negative.min, 10.0, 0.5], expected: [reinterpretU32AsF32(0xfeffffff), reinterpretU32AsF32(0xfefffffe) ] },
   5511    { input: [kValue.f32.negative.min, -10.0, 0.5], expected: [reinterpretU32AsF32(0xff000000),reinterpretU32AsF32(0xfeffffff) ] },
   5512 
   5513    // Intermediate OOB
   5514    { input: [1.0, 2.0,  kPlusOneULPFunctions['f32'](kValue.f32.positive.max / 2)], expected: kUnboundedEndpoints },
   5515  ] as ScalarTripleToIntervalCase[],
   5516  f16: [
   5517    // [0.0, 1.0] cases
   5518    { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9800_0000_0000n), reinterpretU64AsF64(0x3fb9_9c00_0000_0000n)] },  // ~0.1
   5519    { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] },  // ~0.9
   5520    // [1.0, 0.0] cases
   5521    { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] },  // ~0.9
   5522    { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_8000_0000_0000n), reinterpretU64AsF64(0x3fb9_a000_0000_0000n)] },  // ~0.1
   5523    // [0.0, 10.0] cases
   5524    { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0400_0000_0000n)] },  // ~1
   5525    { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_fc00_0000_0000n), reinterpretU64AsF64(0x4022_0400_0000_0000n)] },  // ~9
   5526    // [2.0, 10.0] cases
   5527    { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6400_0000_0000n), reinterpretU64AsF64(0x4006_6c00_0000_0000n)] },  // ~2.8
   5528    { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6000_0000_0000n), reinterpretU64AsF64(0x4022_6c00_0000_0000n)] },  // ~9.2
   5529    // [-1.0, 1.0] cases
   5530    { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_a000_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] },  // ~-0.8
   5531    { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] },  // ~0.8
   5532 
   5533    // Showing how precise and imprecise versions diff
   5534    { input: [kValue.f64.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints },
   5535    { input: [kValue.f64.negative.min, -10.0, 1.0], expected: kUnboundedEndpoints },
   5536    { input: [kValue.f64.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
   5537    { input: [kValue.f64.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
   5538    { input: [kValue.f64.negative.min, 10.0, 0.5], expected: kUnboundedEndpoints },
   5539    { input: [kValue.f64.negative.min, -10.0, 0.5], expected: kUnboundedEndpoints },
   5540 
   5541    // Intermediate OOB
   5542    { input: [1.0, 2.0,  kPlusOneULPFunctions['f16'](kValue.f16.positive.max / 2)], expected: kUnboundedEndpoints },
   5543  ] as ScalarTripleToIntervalCase[],
   5544 } as const;
   5545 
   5546 g.test('mixPreciseInterval')
   5547  .params(u =>
   5548    u
   5549      .combine('trait', ['f32', 'f16'] as const)
   5550      .beginSubcases()
   5551      .expandWithParams<ScalarTripleToIntervalCase>(p => {
   5552        const trait = FP[p.trait];
   5553        const constants = trait.constants();
   5554        // prettier-ignore
   5555        return [
   5556          ...kMixPreciseIntervalCases[p.trait],
   5557 
   5558          // [0.0, 1.0] cases
   5559          { input: [0.0, 1.0, -1.0], expected: -1.0 },
   5560          { input: [0.0, 1.0, 0.0], expected: 0.0 },
   5561          { input: [0.0, 1.0, 0.5], expected: 0.5 },
   5562          { input: [0.0, 1.0, 1.0], expected: 1.0 },
   5563          { input: [0.0, 1.0, 2.0], expected: 2.0 },
   5564 
   5565          // [1.0, 0.0] cases
   5566          { input: [1.0, 0.0, -1.0], expected: 2.0 },
   5567          { input: [1.0, 0.0, 0.0], expected: 1.0 },
   5568          { input: [1.0, 0.0, 0.5], expected: 0.5 },
   5569          { input: [1.0, 0.0, 1.0], expected: 0.0 },
   5570          { input: [1.0, 0.0, 2.0], expected: -1.0 },
   5571 
   5572          // [0.0, 10.0] cases
   5573          { input: [0.0, 10.0, -1.0], expected: -10.0 },
   5574          { input: [0.0, 10.0, 0.0], expected: 0.0 },
   5575          { input: [0.0, 10.0, 0.5], expected: 5.0 },
   5576          { input: [0.0, 10.0, 1.0], expected: 10.0 },
   5577          { input: [0.0, 10.0, 2.0], expected: 20.0 },
   5578 
   5579          // [2.0, 10.0] cases
   5580          { input: [2.0, 10.0, -1.0], expected: -6.0 },
   5581          { input: [2.0, 10.0, 0.0], expected: 2.0 },
   5582          { input: [2.0, 10.0, 0.5], expected: 6.0 },
   5583          { input: [2.0, 10.0, 1.0], expected: 10.0 },
   5584          { input: [2.0, 10.0, 2.0], expected: 18.0 },
   5585 
   5586          // [-1.0, 1.0] cases
   5587          { input: [-1.0, 1.0, -2.0], expected: -5.0 },
   5588          { input: [-1.0, 1.0, 0.0], expected: -1.0 },
   5589          { input: [-1.0, 1.0, 0.5], expected: 0.0 },
   5590          { input: [-1.0, 1.0, 1.0], expected: 1.0 },
   5591          { input: [-1.0, 1.0, 2.0], expected: 3.0 },
   5592 
   5593          // Infinities
   5594          { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
   5595          { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedEndpoints },
   5596          { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedEndpoints },
   5597          { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
   5598          { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
   5599          { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
   5600          { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedEndpoints },
   5601          { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedEndpoints },
   5602          { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedEndpoints },
   5603          { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedEndpoints },
   5604        ];
   5605      })
   5606  )
   5607  .fn(t => {
   5608    const [x, y, z] = t.params.input;
   5609    const trait = FP[t.params.trait];
   5610    const expected = trait.toInterval(t.params.expected);
   5611    const got = trait.mixPreciseInterval(x, y, z);
   5612    t.expect(
   5613      objectEquals(expected, got),
   5614      `${t.params.trait}.mixPreciseInterval(${x}, ${y}, ${z}) returned ${got}. Expected ${expected}`
   5615    );
   5616  });
   5617 
   5618 // Some of these are hard coded, since the error intervals are difficult to express in a closed
   5619 // human-readable form due to the inherited nature of the errors.
   5620 // prettier-ignore
   5621 const kSmoothStepIntervalCases = {
   5622  f32: [
   5623    // Normals
   5624    { input: [0, 1, 0], expected: [0, kValue.f32.positive.subnormal.min] },
   5625    { input: [0, 1, 1], expected: [reinterpretU32AsF32(0x3f7ffffa), reinterpretU32AsF32(0x3f800003)] },  // ~1
   5626    { input: [0, 2, 1], expected: [reinterpretU32AsF32(0x3efffff8), reinterpretU32AsF32(0x3f000007)] },  // ~0.5
   5627    { input: [0, 2, 0.5], expected: [reinterpretU32AsF32(0x3e1ffffb), reinterpretU32AsF32(0x3e200007)] },  // ~0.15625...
   5628    { input: [2, 0, 1], expected: [reinterpretU32AsF32(0x3efffff8), reinterpretU32AsF32(0x3f000007)] },  // ~0.5
   5629    { input: [2, 0, 1.5], expected: [reinterpretU32AsF32(0x3e1ffffb), reinterpretU32AsF32(0x3e200007)] },  // ~0.15625...
   5630    { input: [0, 100, 50], expected: [reinterpretU32AsF32(0x3efffff8), reinterpretU32AsF32(0x3f000007)] },  // ~0.5
   5631    { input: [0, 100, 25], expected: [reinterpretU32AsF32(0x3e1ffffb), reinterpretU32AsF32(0x3e200007)] },  // ~0.15625...
   5632    { input: [0, -2, -1], expected: [reinterpretU32AsF32(0x3efffff8), reinterpretU32AsF32(0x3f000007)] },  // ~0.5
   5633    { input: [0, -2, -0.5], expected: [reinterpretU32AsF32(0x3e1ffffb), reinterpretU32AsF32(0x3e200007)] },  // ~0.15625...
   5634    // Subnormals
   5635    { input: [kValue.f32.positive.subnormal.max, 2, 1], expected: [reinterpretU32AsF32(0x3efffff4), reinterpretU32AsF32(0x3f00000b)] },  // ~0.5
   5636    { input: [kValue.f32.positive.subnormal.min, 2, 1], expected: [reinterpretU32AsF32(0x3efffff4), reinterpretU32AsF32(0x3f00000b)] },  // ~0.5
   5637    { input: [kValue.f32.negative.subnormal.max, 2, 1], expected: [reinterpretU32AsF32(0x3efffff2), reinterpretU32AsF32(0x3f00000c)] },  // ~0.5
   5638    { input: [kValue.f32.negative.subnormal.min, 2, 1], expected: [reinterpretU32AsF32(0x3efffff2), reinterpretU32AsF32(0x3f00000c)] },  // ~0.5
   5639    { input: [0, 2, kValue.f32.positive.subnormal.max], expected: [0, kValue.f32.positive.subnormal.min] },
   5640    { input: [0, 2, kValue.f32.positive.subnormal.min], expected: [0, kValue.f32.positive.subnormal.min] },
   5641    { input: [0, 2, kValue.f32.negative.subnormal.max], expected: [0, kValue.f32.positive.subnormal.min] },
   5642    { input: [0, 2, kValue.f32.negative.subnormal.min], expected: [0, kValue.f32.positive.subnormal.min] },
   5643    // Extra cases for low > high
   5644    // Normals
   5645    { input: [1, 0, 1], expected: [0, kValue.f32.positive.subnormal.min] },
   5646    { input: [1, 0, 0], expected: [reinterpretU32AsF32(0x3f7ffffa), reinterpretU32AsF32(0x3f800003)] },  // ~1
   5647    // Subnormals
   5648    { input: [2, kValue.f32.positive.subnormal.max, 1], expected: [reinterpretU32AsF32(0x3efffff6), reinterpretU32AsF32(0x3f00000b)] },  // ~0.5
   5649    { input: [2, kValue.f32.positive.subnormal.min, 1], expected: [reinterpretU32AsF32(0x3efffff6), reinterpretU32AsF32(0x3f00000b)] },  // ~0.5
   5650    { input: [2, kValue.f32.negative.subnormal.max, 1], expected: [reinterpretU32AsF32(0x3efffff4), reinterpretU32AsF32(0x3f000008)] },  // ~0.5
   5651    { input: [2, kValue.f32.negative.subnormal.min, 1], expected: [reinterpretU32AsF32(0x3efffff4), reinterpretU32AsF32(0x3f000008)] },  // ~0.5
   5652  ] as ScalarTripleToIntervalCase[],
   5653  f16: [
   5654    // Normals
   5655    { input: [0, 1, 0], expected: [0, reinterpretU16AsF16(0x0002)] },
   5656    { input: [0, 1, 1], expected: [reinterpretU16AsF16(0x3bfa), reinterpretU16AsF16(0x3c03)] },  // ~1
   5657    { input: [0, 2, 1], expected: [reinterpretU16AsF16(0x37f8), reinterpretU16AsF16(0x3807)] },  // ~0.5
   5658    { input: [0, 2, 0.5], expected: [reinterpretU16AsF16(0x30fb), reinterpretU16AsF16(0x3107)] },  // ~0.15625...
   5659    { input: [2, 0, 1], expected: [reinterpretU16AsF16(0x37f8), reinterpretU16AsF16(0x3807)] },  // ~0.5
   5660    { input: [2, 0, 1.5], expected: [reinterpretU16AsF16(0x30fb), reinterpretU16AsF16(0x3107)] },  // ~0.15625...
   5661    { input: [0, 100, 50], expected: [reinterpretU16AsF16(0x37f8), reinterpretU16AsF16(0x3807)] },  // ~0.5
   5662    { input: [0, 100, 25], expected: [reinterpretU16AsF16(0x30fb), reinterpretU16AsF16(0x3107)] },  // ~0.15625...
   5663    { input: [0, -2, -1], expected: [reinterpretU16AsF16(0x37f8), reinterpretU16AsF16(0x3807)] },  // ~0.5
   5664    { input: [0, -2, -0.5], expected: [reinterpretU16AsF16(0x30fb), reinterpretU16AsF16(0x3107)] },  // ~0.15625...
   5665    // Subnormals
   5666    { input: [kValue.f16.positive.subnormal.max, 2, 1], expected: [reinterpretU16AsF16(0x37f4), reinterpretU16AsF16(0x380b)] },  // ~0.5
   5667    { input: [kValue.f16.positive.subnormal.min, 2, 1], expected: [reinterpretU16AsF16(0x37f4), reinterpretU16AsF16(0x380b)] },  // ~0.5
   5668    { input: [kValue.f16.negative.subnormal.max, 2, 1], expected: [reinterpretU16AsF16(0x37f2), reinterpretU16AsF16(0x380c)] },  // ~0.5
   5669    { input: [kValue.f16.negative.subnormal.min, 2, 1], expected: [reinterpretU16AsF16(0x37f2), reinterpretU16AsF16(0x380c)] },  // ~0.5
   5670    { input: [0, 2, kValue.f16.positive.subnormal.max], expected: [0, reinterpretU16AsF16(0x0002)] },
   5671    { input: [0, 2, kValue.f16.positive.subnormal.min], expected: [0, reinterpretU16AsF16(0x0002)] },
   5672    { input: [0, 2, kValue.f32.negative.subnormal.max], expected: [0, reinterpretU16AsF16(0x0002)] },
   5673    { input: [0, 2, kValue.f32.negative.subnormal.min], expected: [0, reinterpretU16AsF16(0x0002)] },
   5674    // Extra cases for low > high
   5675    // Normals
   5676    { input: [1, 0, 1], expected: [0, reinterpretU16AsF16(0x0002)] },
   5677    { input: [1, 0, 0], expected: [reinterpretU16AsF16(0x3bfa), reinterpretU16AsF16(0x3c03)] },  // ~1
   5678    // Subnormals
   5679    { input: [2, kValue.f16.positive.subnormal.max, 1], expected: [reinterpretU16AsF16(0x37f6), reinterpretU16AsF16(0x380b)] },  // ~0.5
   5680    { input: [2, kValue.f16.positive.subnormal.min, 1], expected: [reinterpretU16AsF16(0x37f6), reinterpretU16AsF16(0x380b)] },  // ~0.5
   5681    { input: [2, kValue.f16.negative.subnormal.max, 1], expected: [reinterpretU16AsF16(0x37f4), reinterpretU16AsF16(0x3808)] },  // ~0.5
   5682    { input: [2, kValue.f16.negative.subnormal.min, 1], expected: [reinterpretU16AsF16(0x37f4), reinterpretU16AsF16(0x3808)] },  // ~0.5
   5683  ] as ScalarTripleToIntervalCase[],
   5684 } as const;
   5685 
   5686 g.test('smoothStepInterval')
   5687  .params(u =>
   5688    u
   5689      .combine('trait', ['f32', 'f16'] as const)
   5690      .beginSubcases()
   5691      .expandWithParams<ScalarTripleToIntervalCase>(p => {
   5692        const trait = FP[p.trait];
   5693        const constants = trait.constants();
   5694        // prettier-ignore
   5695        return [
   5696          ...kSmoothStepIntervalCases[p.trait],
   5697 
   5698          // Normals
   5699          { input: [0, 1, 10], expected: 1 },
   5700          { input: [0, 1, -10], expected: 0 },
   5701 
   5702          // Subnormals
   5703          { input: [0, constants.positive.subnormal.max, 1], expected: kUnboundedEndpoints },
   5704          { input: [0, constants.positive.subnormal.min, 1], expected: kUnboundedEndpoints },
   5705          { input: [0, constants.negative.subnormal.max, 1], expected: kUnboundedEndpoints },
   5706          { input: [0, constants.negative.subnormal.min, 1], expected: kUnboundedEndpoints },
   5707 
   5708          // Infinities
   5709          { input: [0, 2, constants.positive.infinity], expected: kUnboundedEndpoints },
   5710          { input: [0, 2, constants.negative.infinity], expected: kUnboundedEndpoints },
   5711          { input: [constants.positive.infinity, 2, 1], expected: kUnboundedEndpoints },
   5712          { input: [constants.negative.infinity, 2, 1], expected: kUnboundedEndpoints },
   5713          { input: [0, constants.positive.infinity, 1], expected: kUnboundedEndpoints },
   5714          { input: [0, constants.negative.infinity, 1], expected: kUnboundedEndpoints },
   5715        ];
   5716      })
   5717  )
   5718  .fn(t => {
   5719    const [low, high, x] = t.params.input;
   5720    const trait = FP[t.params.trait];
   5721    const expected = trait.toInterval(t.params.expected);
   5722    const got = trait.smoothStepInterval(low, high, x);
   5723    t.expect(
   5724      objectEquals(expected, got),
   5725      `${t.params.trait}.smoothStepInterval(${low}, ${high}, ${x}) returned ${got}. Expected ${expected}`
   5726    );
   5727  });
   5728 
   5729 interface ScalarToVectorCase {
   5730  input: number;
   5731  expected: (number | IntervalEndpoints)[];
   5732 }
   5733 
   5734 g.test('unpack2x16floatInterval')
   5735  .paramsSubcasesOnly<ScalarToVectorCase>(
   5736    // prettier-ignore
   5737    [
   5738      // f16 normals
   5739      { input: 0x00000000, expected: [0, 0] },
   5740      { input: 0x80000000, expected: [0, 0] },
   5741      { input: 0x00008000, expected: [0, 0] },
   5742      { input: 0x80008000, expected: [0, 0] },
   5743      { input: 0x00003c00, expected: [1, 0] },
   5744      { input: 0x3c000000, expected: [0, 1] },
   5745      { input: 0x3c003c00, expected: [1, 1] },
   5746      { input: 0xbc00bc00, expected: [-1, -1] },
   5747      { input: 0x49004900, expected: [10, 10] },
   5748      { input: 0xc900c900, expected: [-10, -10] },
   5749 
   5750      // f16 subnormals
   5751      { input: 0x000003ff, expected: [[0, kValue.f16.positive.subnormal.max], 0] },
   5752      { input: 0x000083ff, expected: [[kValue.f16.negative.subnormal.min, 0], 0] },
   5753 
   5754      // f16 out of bounds
   5755      { input: 0x7c000000, expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
   5756      { input: 0xffff0000, expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
   5757    ]
   5758  )
   5759  .fn(t => {
   5760    const expected = FP.f32.toVector(t.params.expected);
   5761    const got = FP.f32.unpack2x16floatInterval(t.params.input);
   5762    t.expect(
   5763      objectEquals(expected, got),
   5764      `unpack2x16floatInterval(${t.params.input}) returned [${got}]. Expected [${expected}]`
   5765    );
   5766  });
   5767 
   5768 // Scope for unpack2x16snormInterval tests so that they can have constants for
   5769 // magic numbers that don't pollute the global namespace or have unwieldy long
   5770 // names.
   5771 {
   5772  const kZeroEndpoints: IntervalEndpoints = [
   5773    reinterpretU32AsF32(0x81400000),
   5774    reinterpretU32AsF32(0x01400000),
   5775  ];
   5776  const kOneEndpointsSnorm: IntervalEndpoints = [
   5777    reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
   5778    reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
   5779  ];
   5780  const kNegOneEndpointsSnorm: IntervalEndpoints = [
   5781    reinterpretU64AsF64(0xbff0_0000_3000_0000n),
   5782    reinterpretU64AsF64(0xbfef_ffff_a000_0000n),
   5783  ];
   5784 
   5785  const kHalfEndpoints2x16snorm: IntervalEndpoints = [
   5786    reinterpretU64AsF64(0x3fe0_001f_a000_0000n),
   5787    reinterpretU64AsF64(0x3fe0_0020_8000_0000n),
   5788  ]; // ~0.5..., due to lack of precision in i16
   5789  const kNegHalfEndpoints2x16snorm: IntervalEndpoints = [
   5790    reinterpretU64AsF64(0xbfdf_ffc0_6000_0000n),
   5791    reinterpretU64AsF64(0xbfdf_ffbf_8000_0000n),
   5792  ]; // ~-0.5..., due to lack of precision in i16
   5793 
   5794  g.test('unpack2x16snormInterval')
   5795    .paramsSubcasesOnly<ScalarToVectorCase>(
   5796      // prettier-ignore
   5797      [
   5798        { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints] },
   5799        { input: 0x00007fff, expected: [kOneEndpointsSnorm, kZeroEndpoints] },
   5800        { input: 0x7fff0000, expected: [kZeroEndpoints, kOneEndpointsSnorm] },
   5801        { input: 0x7fff7fff, expected: [kOneEndpointsSnorm, kOneEndpointsSnorm] },
   5802        { input: 0x80018001, expected: [kNegOneEndpointsSnorm, kNegOneEndpointsSnorm] },
   5803        { input: 0x40004000, expected: [kHalfEndpoints2x16snorm, kHalfEndpoints2x16snorm] },
   5804        { input: 0xc001c001, expected: [kNegHalfEndpoints2x16snorm, kNegHalfEndpoints2x16snorm] },
   5805      ]
   5806    )
   5807    .fn(t => {
   5808      const expected = FP.f32.toVector(t.params.expected);
   5809      const got = FP.f32.unpack2x16snormInterval(t.params.input);
   5810      t.expect(
   5811        objectEquals(expected, got),
   5812        `unpack2x16snormInterval(${t.params.input}) returned [${got}]. Expected [${expected}]`
   5813      );
   5814    });
   5815 }
   5816 
   5817 // Scope for unpack2x16unormInterval tests so that they can have constants for
   5818 // magic numbers that don't pollute the global namespace or have unwieldy long
   5819 // names.
   5820 {
   5821  const kZeroEndpoints: IntervalEndpoints = [
   5822    reinterpretU32AsF32(0x8140_0000),
   5823    reinterpretU32AsF32(0x0140_0000),
   5824  ]; // ~0
   5825  const kOneEndpoints: IntervalEndpoints = [
   5826    reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
   5827    reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
   5828  ]; // ~1
   5829  const kHalfEndpoints: IntervalEndpoints = [
   5830    reinterpretU64AsF64(0x3fe0_000f_a000_0000n),
   5831    reinterpretU64AsF64(0x3fe0_0010_8000_0000n),
   5832  ]; // ~0.5..., due to the lack of accuracy in u16
   5833 
   5834  g.test('unpack2x16unormInterval')
   5835    .paramsSubcasesOnly<ScalarToVectorCase>(
   5836      // prettier-ignore
   5837      [
   5838        { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints] },
   5839        { input: 0x0000ffff, expected: [kOneEndpoints, kZeroEndpoints] },
   5840        { input: 0xffff0000, expected: [kZeroEndpoints, kOneEndpoints] },
   5841        { input: 0xffffffff, expected: [kOneEndpoints, kOneEndpoints] },
   5842        { input: 0x80008000, expected: [kHalfEndpoints, kHalfEndpoints] },
   5843      ]
   5844    )
   5845    .fn(t => {
   5846      const expected = FP.f32.toVector(t.params.expected);
   5847      const got = FP.f32.unpack2x16unormInterval(t.params.input);
   5848      t.expect(
   5849        objectEquals(expected, got),
   5850        `unpack2x16unormInterval(${t.params.input})\n\tReturned [${got}]\n\tExpected [${expected}]`
   5851      );
   5852    });
   5853 }
   5854 
   5855 // Scope for unpack4x8snormInterval tests so that they can have constants for
   5856 // magic numbers that don't pollute the global namespace or have unwieldy long
   5857 // names.
   5858 {
   5859  const kZeroEndpoints: IntervalEndpoints = [
   5860    reinterpretU32AsF32(0x8140_0000),
   5861    reinterpretU32AsF32(0x0140_0000),
   5862  ]; // ~0
   5863  const kOneEndpoints: IntervalEndpoints = [
   5864    reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
   5865    reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
   5866  ]; // ~1
   5867  const kNegOneEndpoints: IntervalEndpoints = [
   5868    reinterpretU64AsF64(0xbff0_0000_3000_0000n),
   5869    reinterpretU64AsF64(0xbfef_ffff_a0000_000n),
   5870  ]; // ~-1
   5871  const kHalfEndpoints: IntervalEndpoints = [
   5872    reinterpretU64AsF64(0x3fe0_2040_2000_0000n),
   5873    reinterpretU64AsF64(0x3fe0_2041_0000_0000n),
   5874  ]; // ~0.50196..., due to lack of precision in i8
   5875  const kNegHalfEndpoints: IntervalEndpoints = [
   5876    reinterpretU64AsF64(0xbfdf_bf7f_6000_0000n),
   5877    reinterpretU64AsF64(0xbfdf_bf7e_8000_0000n),
   5878  ]; // ~-0.49606..., due to lack of precision in i8
   5879 
   5880  g.test('unpack4x8snormInterval')
   5881    .paramsSubcasesOnly<ScalarToVectorCase>(
   5882      // prettier-ignore
   5883      [
   5884        { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5885        { input: 0x0000007f, expected: [kOneEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5886        { input: 0x00007f00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5887        { input: 0x007f0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
   5888        { input: 0x7f000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kOneEndpoints] },
   5889        { input: 0x00007f7f, expected: [kOneEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5890        { input: 0x7f7f0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kOneEndpoints] },
   5891        { input: 0x7f007f00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kOneEndpoints] },
   5892        { input: 0x007f007f, expected: [kOneEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
   5893        { input: 0x7f7f7f7f, expected: [kOneEndpoints, kOneEndpoints, kOneEndpoints, kOneEndpoints] },
   5894        {
   5895          input: 0x81818181,
   5896          expected: [kNegOneEndpoints, kNegOneEndpoints, kNegOneEndpoints, kNegOneEndpoints]
   5897        },
   5898        {
   5899          input: 0x40404040,
   5900          expected: [kHalfEndpoints, kHalfEndpoints, kHalfEndpoints, kHalfEndpoints]
   5901        },
   5902        {
   5903          input: 0xc1c1c1c1,
   5904          expected: [kNegHalfEndpoints, kNegHalfEndpoints, kNegHalfEndpoints, kNegHalfEndpoints]
   5905        },
   5906      ]
   5907    )
   5908    .fn(t => {
   5909      const expected = FP.f32.toVector(t.params.expected);
   5910      const got = FP.f32.unpack4x8snormInterval(t.params.input);
   5911      t.expect(
   5912        objectEquals(expected, got),
   5913        `unpack4x8snormInterval(${t.params.input})\n\tReturned [${got}]\n\tExpected [${expected}]`
   5914      );
   5915    });
   5916 }
   5917 
   5918 // Scope for unpack4x8unormInterval tests so that they can have constants for
   5919 // magic numbers that don't pollute the global namespace or have unwieldy long
   5920 // names.
   5921 {
   5922  const kZeroEndpoints: IntervalEndpoints = [
   5923    reinterpretU32AsF32(0x8140_0000),
   5924    reinterpretU32AsF32(0x0140_0000),
   5925  ]; // ~0
   5926  const kOneEndpoints: IntervalEndpoints = [
   5927    reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
   5928    reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
   5929  ]; // ~1
   5930  const kHalfEndpoints: IntervalEndpoints = [
   5931    reinterpretU64AsF64(0x3fe0_100f_a000_0000n),
   5932    reinterpretU64AsF64(0x3fe0_1010_8000_0000n),
   5933  ]; // ~0.50196..., due to lack of precision in u8
   5934 
   5935  g.test('unpack4x8unormInterval')
   5936    .paramsSubcasesOnly<ScalarToVectorCase>(
   5937      // prettier-ignore
   5938      [
   5939        { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5940        { input: 0x000000ff, expected: [kOneEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5941        { input: 0x0000ff00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5942        { input: 0x00ff0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
   5943        { input: 0xff000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kOneEndpoints] },
   5944        { input: 0x0000ffff, expected: [kOneEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
   5945        { input: 0xffff0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kOneEndpoints] },
   5946        { input: 0xff00ff00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kOneEndpoints] },
   5947        { input: 0x00ff00ff, expected: [kOneEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
   5948        { input: 0xffffffff, expected: [kOneEndpoints, kOneEndpoints, kOneEndpoints, kOneEndpoints] },
   5949        {
   5950          input: 0x80808080,
   5951          expected: [kHalfEndpoints, kHalfEndpoints, kHalfEndpoints, kHalfEndpoints]
   5952        },
   5953      ]
   5954    )
   5955    .fn(t => {
   5956      const expected = FP.f32.toVector(t.params.expected);
   5957      const got = FP.f32.unpack4x8unormInterval(t.params.input);
   5958      t.expect(
   5959        objectEquals(expected, got),
   5960        `unpack4x8unormInterval(${t.params.input})\n\tReturned [${got}]\n\tExpected [${expected}]`
   5961      );
   5962    });
   5963 }
   5964 
   5965 interface VectorToIntervalCase {
   5966  input: number[];
   5967  expected: number | IntervalEndpoints;
   5968 }
   5969 
   5970 g.test('lengthIntervalVector')
   5971  .params(u =>
   5972    u
   5973      .combine('trait', ['f32', 'f16'] as const)
   5974      .beginSubcases()
   5975      .expandWithParams<VectorToIntervalCase>(p => {
   5976        const trait = FP[p.trait];
   5977        const constants = trait.constants();
   5978        // prettier-ignore
   5979        return [
   5980          // vec2
   5981          {input: [1.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   5982          {input: [0.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   5983          {input: [1.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0]'] },  // ~√2
   5984          {input: [-1.0, -1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0]'] },  // ~√2
   5985          {input: [-1.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0]'] },  // ~√2
   5986          {input: [0.1, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   5987 
   5988          // vec3
   5989          {input: [1.0, 0.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   5990          {input: [0.0, 1.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   5991          {input: [0.0, 0.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   5992          {input: [1.0, 1.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   5993          {input: [-1.0, -1.0, -1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   5994          {input: [1.0, -1.0, -1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   5995          {input: [0.1, 0.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   5996 
   5997          // vec4
   5998          {input: [1.0, 0.0, 0.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   5999          {input: [0.0, 1.0, 0.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6000          {input: [0.0, 0.0, 1.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6001          {input: [0.0, 0.0, 0.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6002          {input: [1.0, 1.0, 1.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6003          {input: [-1.0, -1.0, -1.0, -1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6004          {input: [-1.0, 1.0, -1.0, 1.0], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6005          {input: [0.1, 0.0, 0.0, 0.0], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   6006 
   6007          // Test that dot going OOB in the intermediate calculations propagates
   6008          { input: [constants.positive.nearest_max, constants.positive.max, constants.negative.min], expected: kUnboundedEndpoints },
   6009          { input: [constants.positive.max, constants.positive.nearest_max, constants.negative.min], expected: kUnboundedEndpoints },
   6010          { input: [constants.negative.min, constants.positive.max, constants.positive.nearest_max], expected: kUnboundedEndpoints },
   6011        ];
   6012      })
   6013  )
   6014  .fn(t => {
   6015    const trait = FP[t.params.trait];
   6016    const expected = trait.toInterval(t.params.expected);
   6017    const got = trait.lengthInterval(t.params.input);
   6018    t.expect(
   6019      objectEquals(expected, got),
   6020      `${t.params.trait}.lengthInterval([${t.params.input}]) returned ${got}. Expected ${expected}`
   6021    );
   6022  });
   6023 
   6024 interface VectorPairToIntervalCase {
   6025  input: [number[], number[]];
   6026  expected: number | IntervalEndpoints;
   6027 }
   6028 
   6029 g.test('distanceIntervalVector')
   6030  .params(u =>
   6031    u
   6032      .combine('trait', ['f32', 'f16'] as const)
   6033      .beginSubcases()
   6034      .expandWithParams<VectorPairToIntervalCase>(p => {
   6035        // prettier-ignore
   6036        return [
   6037          // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedEndpoints,
   6038          // because distance(x, y) = length(x - y), and length(0) = kUnboundedEndpoints.
   6039 
   6040          // vec2
   6041          { input: [[1.0, 0.0], [1.0, 0.0]], expected: kUnboundedEndpoints },
   6042          { input: [[1.0, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6043          { input: [[0.0, 0.0], [1.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6044          { input: [[-1.0, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6045          { input: [[0.0, 0.0], [-1.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6046          { input: [[0.0, 1.0], [-1.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0]'] },  // ~√2
   6047          { input: [[0.1, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   6048 
   6049          // vec3
   6050          { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kUnboundedEndpoints },
   6051          { input: [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6052          { input: [[0.0, 1.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6053          { input: [[0.0, 0.0, 1.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6054          { input: [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6055          { input: [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6056          { input: [[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6057          { input: [[1.0, 1.0, 1.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   6058          { input: [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   6059          { input: [[-1.0, -1.0, -1.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   6060          { input: [[0.0, 0.0, 0.0], [-1.0, -1.0, -1.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0]'] },  // ~√3
   6061          { input: [[0.1, 0.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   6062          { input: [[0.0, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   6063 
   6064          // vec4
   6065          { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kUnboundedEndpoints },
   6066          { input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6067          { input: [[0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6068          { input: [[0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6069          { input: [[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6070          { input: [[0.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6071          { input: [[0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6072          { input: [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6073          { input: [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0]'] },  // ~1
   6074          { input: [[1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6075          { input: [[0.0, 0.0, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6076          { input: [[-1.0, 1.0, -1.0, 1.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6077          { input: [[0.0, 0.0, 0.0, 0.0], [1.0, -1.0, 1.0, -1.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] },  // ~2
   6078          { input: [[0.1, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   6079          { input: [[0.0, 0.0, 0.0, 0.0], [0.1, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectationInterval[p.trait]['[0.1]'] },  // ~0.1
   6080        ];
   6081      })
   6082  )
   6083  .fn(t => {
   6084    const trait = FP[t.params.trait];
   6085    const expected = trait.toInterval(t.params.expected);
   6086    const got = trait.distanceInterval(...t.params.input);
   6087    t.expect(
   6088      objectEquals(expected, got),
   6089      `${t.params.trait}.lengthInterval([${t.params.input[0]}, ${t.params.input[1]}]) returned ${got}. Expected ${expected}`
   6090    );
   6091  });
   6092 
   6093 // prettier-ignore
   6094 const kDotIntervalCases = {
   6095  f32: [
   6096    // Due to unbounded precision, intermediate correctly rounded computations could result in intervals that include infinity.
   6097    // This is because the computation kValue.f32.negative.min - 4.0 = [-inf, kValue.f32.negative.min]
   6098    // due to the ULP_neg(kValue.f32.negative.min) => -inf
   6099    // See: https://www.w3.org/TR/WGSL/#floating-point-accuracy
   6100    { input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f32.positive.max, -2.0, -3.0]], expected: kUnboundedEndpoints},
   6101    { input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f32.negative.min, 2.0, 3.0]], expected: kUnboundedEndpoints },
   6102    // Exactly as above but simply 2 ulp magnitude smaller (away from kValue.f32.positive.max).
   6103    // This avoids intermediate intervals that overflow into infinity and we end up with large but finite intervals.
   6104    { input: [[reinterpretU32AsF32(0x7f7ffffd), 1.0, 2.0, 3.0], [-1.0, kValue.f32.positive.max, -2.0, -3.0]], expected:  [0.0, reinterpretU32AsF32(0x74000000)]},
   6105    { input: [[reinterpretU32AsF32(0x7f7ffffd), 1.0, 2.0, 3.0], [1.0, kValue.f32.negative.min, 2.0, 3.0]], expected: [reinterpretU32AsF32(0xf4000000), 0.0] },
   6106  ] as VectorPairToIntervalCase[],
   6107  f16: [
   6108    // Inputs with large values but cancel out to finite result. In these cases, 2.0*2.0 = 4.0 and
   6109    // 3.0*3.0 = 9.0 is not small enough comparing to kValue.f16.positive.max = 65504, as a result
   6110    // kValue.f16.positive.max + 9.0 = 65513 is exactly representable in f32 and f64. So, if the
   6111    // positive and negative large number don't cancel each other first, the computation will
   6112    // overflow f16 and result in unbounded endpoints.
   6113    // https://github.com/gpuweb/cts/issues/2155
   6114    { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f16.positive.max, -2.0, -3.0]], expected: kUnboundedEndpoints },
   6115    { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f16.negative.min, 2.0, 3.0]], expected: kUnboundedEndpoints },
   6116  ] as VectorPairToIntervalCase[],
   6117 } as const;
   6118 
   6119 g.test('dotInterval')
   6120  .params(u =>
   6121    u
   6122      .combine('trait', ['f32', 'f16'] as const)
   6123      .beginSubcases()
   6124      .expandWithParams<VectorPairToIntervalCase>(p => {
   6125        const trait = FP[p.trait];
   6126        const constants = trait.constants();
   6127        // prettier-ignore
   6128        return [
   6129          // vec2
   6130          { input: [[1.0, 0.0], [1.0, 0.0]], expected: 1.0 },
   6131          { input: [[0.0, 1.0], [0.0, 1.0]], expected: 1.0 },
   6132          { input: [[1.0, 1.0], [1.0, 1.0]], expected: 2.0 },
   6133          { input: [[-1.0, -1.0], [-1.0, -1.0]], expected: 2.0 },
   6134          { input: [[-1.0, 1.0], [1.0, -1.0]], expected: -2.0 },
   6135          { input: [[0.1, 0.0], [1.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']},  // correctly rounded of 0.1
   6136 
   6137          // vec3
   6138          { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: 1.0 },
   6139          { input: [[0.0, 1.0, 0.0], [0.0, 1.0, 0.0]], expected: 1.0 },
   6140          { input: [[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], expected: 1.0 },
   6141          { input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: 3.0 },
   6142          { input: [[-1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]], expected: 3.0 },
   6143          { input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: -1.0 },
   6144          { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']},  // correctly rounded of 0.1
   6145 
   6146          // vec4
   6147          { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: 1.0 },
   6148          { input: [[0.0, 1.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]], expected: 1.0 },
   6149          { input: [[0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: 1.0 },
   6150          { input: [[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0]], expected: 1.0 },
   6151          { input: [[1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 1.0]], expected: 4.0 },
   6152          { input: [[-1.0, -1.0, -1.0, -1.0], [-1.0, -1.0, -1.0, -1.0]], expected: 4.0 },
   6153          { input: [[-1.0, 1.0, -1.0, 1.0], [1.0, -1.0, 1.0, -1.0]], expected: -4.0 },
   6154          { input: [[0.1, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']},  // correclt rounded of 0.1
   6155 
   6156          ...kDotIntervalCases[p.trait],
   6157 
   6158          // Test that going out of bounds in the intermediate calculations is caught correctly.
   6159          { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
   6160          { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
   6161          { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
   6162          { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
   6163          { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
   6164          { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
   6165        ];
   6166      })
   6167  )
   6168  .fn(t => {
   6169    const [x, y] = t.params.input;
   6170    const trait = FP[t.params.trait];
   6171    const expected = trait.toInterval(t.params.expected);
   6172    const got = trait.dotInterval(x, y);
   6173    t.expect(
   6174      objectEquals(expected, got),
   6175      `${t.params.trait}.dotInterval([${x}], [${y}]) returned ${got}. Expected ${expected}`
   6176    );
   6177  });
   6178 
   6179 interface VectorToVectorCase {
   6180  input: number[];
   6181  expected: (number | IntervalEndpoints)[];
   6182 }
   6183 
   6184 // prettier-ignore
   6185 const kNormalizeIntervalCases = {
   6186  f32: [
   6187    // vec2
   6188    { input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~1.0, ~0.0]
   6189    { input: [0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] },  // [ ~0.0, ~1.0]
   6190    { input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~1.0, ~0.0]
   6191    { input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)], [reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)]] },  // [ ~1/√2, ~1/√2]
   6192 
   6193    // vec3
   6194    { input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~1.0, ~0.0, ~0.0]
   6195    { input: [0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~0.0, ~1.0, ~0.0]
   6196    { input: [0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] },  // [ ~0.0, ~0.0, ~1.0]
   6197    { input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~1.0, ~0.0, ~0.0]
   6198    { input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)]] },  // [ ~1/√3, ~1/√3, ~1/√3]
   6199 
   6200    // vec4
   6201    { input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~1.0, ~0.0, ~0.0, ~0.0]
   6202    { input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~0.0, ~1.0, ~0.0, ~0.0]
   6203    { input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~0.0, ~0.0, ~1.0, ~0.0]
   6204    { input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] },  // [ ~0.0, ~0.0, ~0.0, ~1.0]
   6205    { input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] },  // [ ~1.0, ~0.0, ~0.0, ~0.0]
   6206    { input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)]] },  // [ ~1/√4, ~1/√4, ~1/√4]
   6207  ] as VectorToVectorCase[],
   6208  f16: [
   6209    // vec2
   6210    { input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~1.0, ~0.0]
   6211    { input: [0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] },  // [ ~0.0, ~1.0]
   6212    { input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~1.0, ~0.0]
   6213    { input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)], [reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)]] },  // [ ~1/√2, ~1/√2]
   6214 
   6215    // vec3
   6216    { input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~1.0, ~0.0, ~0.0]
   6217    { input: [0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~0.0, ~1.0, ~0.0]
   6218    { input: [0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] },  // [ ~0.0, ~0.0, ~1.0]
   6219    { input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~1.0, ~0.0, ~0.0]
   6220    { input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)]] },  // [ ~1/√3, ~1/√3, ~1/√3]
   6221 
   6222    // vec4
   6223    { input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~1.0, ~0.0, ~0.0, ~0.0]
   6224    { input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~0.0, ~1.0, ~0.0, ~0.0]
   6225    { input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~0.0, ~0.0, ~1.0, ~0.0]
   6226    { input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] },  // [ ~0.0, ~0.0, ~0.0, ~1.0]
   6227    { input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] },  // [ ~1.0, ~0.0, ~0.0, ~0.0]
   6228    { input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)]] },  // [ ~1/√4, ~1/√4, ~1/√4]
   6229  ] as VectorToVectorCase[],
   6230 } as const;
   6231 
   6232 g.test('normalizeInterval')
   6233  .params(u =>
   6234    u
   6235      .combine('trait', ['f32', 'f16'] as const)
   6236      .beginSubcases()
   6237      .expandWithParams<VectorToVectorCase>(p => {
   6238        const trait = FP[p.trait];
   6239        const constants = trait.constants();
   6240        // prettier-ignore
   6241        return [
   6242          ...kNormalizeIntervalCases[p.trait],
   6243 
   6244          // Very small vectors go OOB due to division
   6245          { input: [constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [kUnboundedEndpoints, kUnboundedEndpoints], },
   6246 
   6247          // Very large vectors go OOB due to overflow
   6248          { input: [constants.positive.max, constants.positive.max], expected: [kUnboundedEndpoints, kUnboundedEndpoints], },
   6249        ];
   6250      })
   6251  )
   6252  .fn(t => {
   6253    const x = t.params.input;
   6254    const trait = FP[t.params.trait];
   6255    const expected = trait.toVector(t.params.expected);
   6256    const got = trait.normalizeInterval(x);
   6257    t.expect(
   6258      objectEquals(expected, got),
   6259      `${t.params.trait}.normalizeInterval([${x}]) returned ${got}. Expected ${expected}`
   6260    );
   6261  });
   6262 
   6263 interface VectorPairToVectorCase {
   6264  input: [number[], number[]];
   6265  expected: (number | IntervalEndpoints)[];
   6266 }
   6267 
   6268 // prettier-ignore
   6269 const kCrossIntervalCases = {
   6270  f32: [
   6271    { input: [
   6272        [kValue.f32.positive.subnormal.max, kValue.f32.negative.subnormal.max, kValue.f32.negative.subnormal.min],
   6273        [kValue.f32.negative.subnormal.min, kValue.f32.positive.subnormal.min, kValue.f32.negative.subnormal.max]
   6274      ],
   6275      expected: [
   6276        [0.0, reinterpretU32AsF32(0x00000002)], // ~0
   6277        [0.0, reinterpretU32AsF32(0x00000002)], // ~0
   6278        [kValue.f32.negative.subnormal.max, kValue.f32.positive.subnormal.min] // ~0
   6279      ]
   6280    },
   6281    { input: [
   6282        [0.1, -0.1, -0.1],
   6283        [-0.1, 0.1, -0.1]
   6284      ],
   6285      expected: [
   6286        [reinterpretU32AsF32(0x3ca3d708), reinterpretU32AsF32(0x3ca3d70b)], // ~0.02
   6287        [reinterpretU32AsF32(0x3ca3d708), reinterpretU32AsF32(0x3ca3d70b)], // ~0.02
   6288        [reinterpretU32AsF32(0xb1400000), reinterpretU32AsF32(0x31400000)], // ~0
   6289      ]
   6290    },
   6291  ] as VectorPairToVectorCase[],
   6292  f16: [
   6293    { input: [
   6294        [kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.max, kValue.f16.negative.subnormal.min],
   6295        [kValue.f16.negative.subnormal.min, kValue.f16.positive.subnormal.min, kValue.f16.negative.subnormal.max]
   6296      ],
   6297      expected: [
   6298        [0.0, reinterpretU16AsF16(0x0002)], // ~0
   6299        [0.0, reinterpretU16AsF16(0x0002)], // ~0
   6300        [kValue.f16.negative.subnormal.max, kValue.f16.positive.subnormal.min] // ~0
   6301      ]
   6302    },
   6303    { input: [
   6304        [0.1, -0.1, -0.1],
   6305        [-0.1, 0.1, -0.1]
   6306      ],
   6307      expected: [
   6308        [reinterpretU16AsF16(0x251e), reinterpretU16AsF16(0x2520)], // ~0.02
   6309        [reinterpretU16AsF16(0x251e), reinterpretU16AsF16(0x2520)], // ~0.02
   6310        [reinterpretU16AsF16(0x8100), reinterpretU16AsF16(0x0100)] // ~0
   6311      ]
   6312    },
   6313  ] as VectorPairToVectorCase[],
   6314 } as const;
   6315 
   6316 g.test('crossInterval')
   6317  .params(u =>
   6318    u
   6319      .combine('trait', ['f32', 'f16'] as const)
   6320      .beginSubcases()
   6321      .expandWithParams<VectorPairToVectorCase>(p => {
   6322        const trait = FP[p.trait];
   6323        const constants = trait.constants();
   6324        // prettier-ignore
   6325        return [
   6326          // parallel vectors, AXB == 0
   6327          { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [0.0, 0.0, 0.0] },
   6328          { input: [[0.0, 1.0, 0.0], [0.0, 1.0, 0.0]], expected: [0.0, 0.0, 0.0] },
   6329          { input: [[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], expected: [0.0, 0.0, 0.0] },
   6330          { input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [0.0, 0.0, 0.0] },
   6331          { input: [[-1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]], expected: [0.0, 0.0, 0.0] },
   6332          { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [0.0, 0.0, 0.0] },
   6333          { input: [[constants.positive.subnormal.max, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [0.0, 0.0, 0.0] },
   6334 
   6335          // non-parallel vectors, AXB != 0
   6336          { input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: [2.0, 2.0, 0.0] },
   6337          { input: [[1.0, 2, 3], [1.0, 5.0, 7.0]], expected: [-1, -4, 3] },
   6338          ...kCrossIntervalCases[p.trait],
   6339 
   6340          // OOB
   6341          { input: [[constants.positive.max, 1.0, 1.0], [1.0, constants.positive.max, -1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6342        ];
   6343      })
   6344  )
   6345  .fn(t => {
   6346    const [x, y] = t.params.input;
   6347    const trait = FP[t.params.trait];
   6348    const expected = trait.toVector(t.params.expected);
   6349    const got = trait.crossInterval(x, y);
   6350    t.expect(
   6351      objectEquals(expected, got),
   6352      `${t.params.trait}.crossInterval([${x}], [${y}]) returned ${got}. Expected ${expected}`
   6353    );
   6354  });
   6355 
   6356 // prettier-ignore
   6357 const kReflectIntervalCases = {
   6358  f32: [
   6359    // vec2s
   6360    { input: [[0.1, 0.1], [1.0, 1.0]], expected: [[reinterpretU32AsF32(0xbe99999a), reinterpretU32AsF32(0xbe999998)], [reinterpretU32AsF32(0xbe99999a), reinterpretU32AsF32(0xbe999998)]] },  // [~-0.3, ~-0.3]
   6361    { input: [[kValue.f32.positive.subnormal.max, kValue.f32.negative.subnormal.max], [1.0, 1.0]], expected: [[reinterpretU32AsF32(0x80fffffe), reinterpretU32AsF32(0x00800001)], [reinterpretU32AsF32(0x80ffffff), reinterpretU32AsF32(0x00000002)]] },  // [~0.0, ~0.0]
   6362    // vec3s
   6363    { input: [[0.1, 0.1, 0.1], [1.0, 1.0, 1.0]], expected: [[reinterpretU32AsF32(0xbf000001), reinterpretU32AsF32(0xbefffffe)], [reinterpretU32AsF32(0xbf000001), reinterpretU32AsF32(0xbefffffe)], [reinterpretU32AsF32(0xbf000001), reinterpretU32AsF32(0xbefffffe)]] },  // [~-0.5, ~-0.5, ~-0.5]
   6364    { input: [[kValue.f32.positive.subnormal.max, kValue.f32.negative.subnormal.max, 0.0], [1.0, 1.0, 1.0]], expected: [[reinterpretU32AsF32(0x80fffffe), reinterpretU32AsF32(0x00800001)], [reinterpretU32AsF32(0x80ffffff), reinterpretU32AsF32(0x00000002)], [reinterpretU32AsF32(0x80fffffe), reinterpretU32AsF32(0x00000002)]] },  // [~0.0, ~0.0, ~0.0]
   6365    // vec4s
   6366    { input: [[0.1, 0.1, 0.1, 0.1], [1.0, 1.0, 1.0, 1.0]], expected: [[reinterpretU32AsF32(0xbf333335), reinterpretU32AsF32(0xbf333332)], [reinterpretU32AsF32(0xbf333335), reinterpretU32AsF32(0xbf333332)], [reinterpretU32AsF32(0xbf333335), reinterpretU32AsF32(0xbf333332)], [reinterpretU32AsF32(0xbf333335), reinterpretU32AsF32(0xbf333332)]] },  // [~-0.7, ~-0.7, ~-0.7, ~-0.7]
   6367    { input: [[kValue.f32.positive.subnormal.max, kValue.f32.negative.subnormal.max, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0]], expected: [[reinterpretU32AsF32(0x80fffffe), reinterpretU32AsF32(0x00800001)], [reinterpretU32AsF32(0x80ffffff), reinterpretU32AsF32(0x00000002)], [reinterpretU32AsF32(0x80fffffe), reinterpretU32AsF32(0x00000002)], [reinterpretU32AsF32(0x80fffffe), reinterpretU32AsF32(0x00000002)]] },  // [~0.0, ~0.0, ~0.0, ~0.0]
   6368  ] as VectorPairToVectorCase[],
   6369  f16: [
   6370    // vec2s
   6371    { input: [[0.1, 0.1], [1.0, 1.0]], expected: [[reinterpretU16AsF16(0xb4ce), reinterpretU16AsF16(0xb4cc)], [reinterpretU16AsF16(0xb4ce), reinterpretU16AsF16(0xb4cc)]] },  // [~-0.3, ~-0.3]
   6372    { input: [[kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.max], [1.0, 1.0]], expected: [[reinterpretU16AsF16(0x87fe), reinterpretU16AsF16(0x0401)], [reinterpretU16AsF16(0x87ff), reinterpretU16AsF16(0x0002)]] },  // [~0.0, ~0.0]
   6373    // vec3s
   6374    { input: [[0.1, 0.1, 0.1], [1.0, 1.0, 1.0]], expected: [[reinterpretU16AsF16(0xb802), reinterpretU16AsF16(0xb7fe)], [reinterpretU16AsF16(0xb802), reinterpretU16AsF16(0xb7fe)], [reinterpretU16AsF16(0xb802), reinterpretU16AsF16(0xb7fe)]] },  // [~-0.5, ~-0.5, ~-0.5]
   6375    { input: [[kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.max, 0.0], [1.0, 1.0, 1.0]], expected: [[reinterpretU16AsF16(0x87fe), reinterpretU16AsF16(0x0401)], [reinterpretU16AsF16(0x87ff), reinterpretU16AsF16(0x0002)], [reinterpretU16AsF16(0x87fe), reinterpretU16AsF16(0x0002)]] },  // [~0.0, ~0.0, ~0.0]
   6376    // vec4s
   6377    { input: [[0.1, 0.1, 0.1, 0.1], [1.0, 1.0, 1.0, 1.0]], expected: [[reinterpretU16AsF16(0xb99c), reinterpretU16AsF16(0xb998)], [reinterpretU16AsF16(0xb99c), reinterpretU16AsF16(0xb998)], [reinterpretU16AsF16(0xb99c), reinterpretU16AsF16(0xb998)], [reinterpretU16AsF16(0xb99c), reinterpretU16AsF16(0xb998)]] },  // [~-0.7, ~-0.7, ~-0.7, ~-0.7]
   6378    { input: [[kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.max, 0.0, 0.0], [1.0, 1.0, 1.0, 1.0]], expected: [[reinterpretU16AsF16(0x87fe), reinterpretU16AsF16(0x0401)], [reinterpretU16AsF16(0x87ff), reinterpretU16AsF16(0x0002)], [reinterpretU16AsF16(0x87fe), reinterpretU16AsF16(0x0002)], [reinterpretU16AsF16(0x87fe), reinterpretU16AsF16(0x0002)]] },  // [~0.0, ~0.0, ~0.0, ~0.0]
   6379  ] as VectorPairToVectorCase[],
   6380 } as const;
   6381 
   6382 g.test('reflectInterval')
   6383  .params(u =>
   6384    u
   6385      .combine('trait', ['f32', 'f16'] as const)
   6386      .beginSubcases()
   6387      .expandWithParams<VectorPairToVectorCase>(p => {
   6388        const trait = FP[p.trait];
   6389        const constants = trait.constants();
   6390        // prettier-ignore
   6391        return [
   6392          ...kReflectIntervalCases[p.trait],
   6393 
   6394          // vec2s
   6395          { input: [[1.0, 0.0], [1.0, 0.0]], expected: [-1.0, 0.0] },
   6396          { input: [[1.0, 0.0], [0.0, 1.0]], expected: [1.0, 0.0] },
   6397          { input: [[0.0, 1.0], [0.0, 1.0]], expected: [0.0, -1.0] },
   6398          { input: [[0.0, 1.0], [1.0, 0.0]], expected: [0.0, 1.0] },
   6399          { input: [[1.0, 1.0], [1.0, 1.0]], expected: [-3.0, -3.0] },
   6400          { input: [[-1.0, -1.0], [1.0, 1.0]], expected: [3.0, 3.0] },
   6401 
   6402          // vec3s
   6403          { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [-1.0, 0.0, 0.0] },
   6404          { input: [[0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], expected: [0.0, 1.0, 0.0] },
   6405          { input: [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], expected: [0.0, 0.0, 1.0] },
   6406          { input: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], expected: [1.0, 0.0, 0.0] },
   6407          { input: [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], expected: [1.0, 0.0, 0.0] },
   6408          { input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [-5.0, -5.0, -5.0] },
   6409          { input: [[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]], expected: [5.0, 5.0, 5.0] },
   6410 
   6411          // vec4s
   6412          { 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] },
   6413          { 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] },
   6414          { 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] },
   6415          { 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] },
   6416          { 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] },
   6417          { 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] },
   6418          { 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] },
   6419          { 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] },
   6420 
   6421          // Test that dot going OOB in the intermediate calculations propagates
   6422          { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6423          { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6424          { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6425          { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6426          { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6427          { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6428 
   6429          // Test that post-dot going OOB propagates
   6430          { input: [[constants.positive.max, 1.0, 2.0, 3.0], [-1.0, constants.positive.max, -2.0, -3.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   6431        ];
   6432      })
   6433  )
   6434  .fn(t => {
   6435    const [x, y] = t.params.input;
   6436    const trait = FP[t.params.trait];
   6437    const expected = trait.toVector(t.params.expected);
   6438    const got = trait.reflectInterval(x, y);
   6439    t.expect(
   6440      objectEquals(expected, got),
   6441      `${t.params.trait}.reflectInterval([${x}], [${y}]) returned ${JSON.stringify(
   6442        got
   6443      )}. Expected ${JSON.stringify(expected)}`
   6444    );
   6445  });
   6446 
   6447 interface MatrixToScalarCase {
   6448  input: number[][];
   6449  expected: number | IntervalEndpoints;
   6450 }
   6451 
   6452 g.test('determinantInterval')
   6453  .params(u =>
   6454    u
   6455      .combine('trait', ['f32', 'f16'] as const)
   6456      .beginSubcases()
   6457      .combineWithParams<MatrixToScalarCase>([
   6458        // Extreme values, i.e. subnormals, very large magnitudes, and those lead to
   6459        // non-precise products, are intentionally not tested, since the accuracy of
   6460        // determinant is restricted to well behaving inputs. Handling all cases
   6461        // requires ~23! options to be calculated in the 4x4 case, so is not
   6462        // feasible.
   6463        {
   6464          input: [
   6465            [1, 2],
   6466            [3, 4],
   6467          ],
   6468          expected: -2,
   6469        },
   6470        {
   6471          input: [
   6472            [-1, 2],
   6473            [-3, 4],
   6474          ],
   6475          expected: 2,
   6476        },
   6477        {
   6478          input: [
   6479            [11, 22],
   6480            [33, 44],
   6481          ],
   6482          expected: -242,
   6483        },
   6484        {
   6485          input: [
   6486            [5, 6],
   6487            [8, 9],
   6488          ],
   6489          expected: -3,
   6490        },
   6491        {
   6492          input: [
   6493            [4, 6],
   6494            [7, 9],
   6495          ],
   6496          expected: -6,
   6497        },
   6498        {
   6499          input: [
   6500            [4, 5],
   6501            [7, 8],
   6502          ],
   6503          expected: -3,
   6504        },
   6505        {
   6506          input: [
   6507            [1, 2, 3],
   6508            [4, 5, 6],
   6509            [7, 8, 9],
   6510          ],
   6511          expected: 0,
   6512        },
   6513        {
   6514          input: [
   6515            [-1, 2, 3],
   6516            [-4, 5, 6],
   6517            [-7, 8, 9],
   6518          ],
   6519          expected: 0,
   6520        },
   6521        {
   6522          input: [
   6523            [4, 1, -1],
   6524            [-3, 0, 5],
   6525            [5, 3, 2],
   6526          ],
   6527          expected: -20,
   6528        },
   6529        {
   6530          input: [
   6531            [1, 2, 3, 4],
   6532            [5, 6, 7, 8],
   6533            [9, 10, 11, 12],
   6534            [13, 14, 15, 16],
   6535          ],
   6536          expected: 0,
   6537        },
   6538        {
   6539          input: [
   6540            [4, 0, 0, 0],
   6541            [3, 1, -1, 3],
   6542            [2, -3, 3, 1],
   6543            [2, 3, 3, 1],
   6544          ],
   6545          expected: -240,
   6546        },
   6547      ])
   6548  )
   6549  .fn(t => {
   6550    const input = t.params.input;
   6551    const trait = FP[t.params.trait];
   6552    const expected = trait.toInterval(t.params.expected);
   6553    const got = trait.determinantInterval(input);
   6554    t.expect(
   6555      objectEquals(expected, got),
   6556      `${t.params.trait}.determinantInterval([${JSON.stringify(
   6557        input
   6558      )}]) returned '${got}. Expected '${expected}'`
   6559    );
   6560  });
   6561 
   6562 interface MatrixToMatrixCase {
   6563  input: number[][];
   6564  expected: (number | IntervalEndpoints)[][];
   6565 }
   6566 
   6567 g.test('transposeInterval')
   6568  .params(u =>
   6569    u
   6570      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   6571      .beginSubcases()
   6572      .expandWithParams<MatrixToMatrixCase>(p => {
   6573        const trait = FP[p.trait];
   6574        const constants = trait.constants();
   6575        return [
   6576          {
   6577            input: [
   6578              [1, 2],
   6579              [3, 4],
   6580            ],
   6581            expected: [
   6582              [1, 3],
   6583              [2, 4],
   6584            ],
   6585          },
   6586          {
   6587            input: [
   6588              [1, 2],
   6589              [3, 4],
   6590              [5, 6],
   6591            ],
   6592            expected: [
   6593              [1, 3, 5],
   6594              [2, 4, 6],
   6595            ],
   6596          },
   6597          {
   6598            input: [
   6599              [1, 2],
   6600              [3, 4],
   6601              [5, 6],
   6602              [7, 8],
   6603            ],
   6604            expected: [
   6605              [1, 3, 5, 7],
   6606              [2, 4, 6, 8],
   6607            ],
   6608          },
   6609          {
   6610            input: [
   6611              [1, 2, 3],
   6612              [4, 5, 6],
   6613            ],
   6614            expected: [
   6615              [1, 4],
   6616              [2, 5],
   6617              [3, 6],
   6618            ],
   6619          },
   6620          {
   6621            input: [
   6622              [1, 2, 3],
   6623              [4, 5, 6],
   6624              [7, 8, 9],
   6625            ],
   6626            expected: [
   6627              [1, 4, 7],
   6628              [2, 5, 8],
   6629              [3, 6, 9],
   6630            ],
   6631          },
   6632          {
   6633            input: [
   6634              [1, 2, 3],
   6635              [4, 5, 6],
   6636              [7, 8, 9],
   6637              [10, 11, 12],
   6638            ],
   6639            expected: [
   6640              [1, 4, 7, 10],
   6641              [2, 5, 8, 11],
   6642              [3, 6, 9, 12],
   6643            ],
   6644          },
   6645          {
   6646            input: [
   6647              [1, 2, 3, 4],
   6648              [5, 6, 7, 8],
   6649            ],
   6650            expected: [
   6651              [1, 5],
   6652              [2, 6],
   6653              [3, 7],
   6654              [4, 8],
   6655            ],
   6656          },
   6657          {
   6658            input: [
   6659              [1, 2, 3, 4],
   6660              [5, 6, 7, 8],
   6661              [9, 10, 11, 12],
   6662            ],
   6663            expected: [
   6664              [1, 5, 9],
   6665              [2, 6, 10],
   6666              [3, 7, 11],
   6667              [4, 8, 12],
   6668            ],
   6669          },
   6670          {
   6671            input: [
   6672              [1, 2, 3, 4],
   6673              [5, 6, 7, 8],
   6674              [9, 10, 11, 12],
   6675              [13, 14, 15, 16],
   6676            ],
   6677            expected: [
   6678              [1, 5, 9, 13],
   6679              [2, 6, 10, 14],
   6680              [3, 7, 11, 15],
   6681              [4, 8, 12, 16],
   6682            ],
   6683          },
   6684          {
   6685            input: [
   6686              [constants.positive.subnormal.max, constants.positive.subnormal.min],
   6687              [constants.negative.subnormal.min, constants.negative.subnormal.max],
   6688            ],
   6689            expected: [
   6690              [
   6691                [0, constants.positive.subnormal.max],
   6692                [constants.negative.subnormal.min, 0],
   6693              ],
   6694              [
   6695                [0, constants.positive.subnormal.min],
   6696                [constants.negative.subnormal.max, 0],
   6697              ],
   6698            ],
   6699          },
   6700        ];
   6701      })
   6702  )
   6703  .fn(t => {
   6704    const input = t.params.input;
   6705    const trait = FP[t.params.trait];
   6706    const expected = trait.toMatrix(t.params.expected);
   6707    const got = trait.transposeInterval(input);
   6708    t.expect(
   6709      objectEquals(expected, got),
   6710      `FP.${t.params.trait}.transposeInterval([${JSON.stringify(
   6711        input
   6712      )}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'`
   6713    );
   6714  });
   6715 
   6716 interface MatrixPairToMatrixCase {
   6717  input: [number[][], number[][]];
   6718  expected: (number | IntervalEndpoints)[][];
   6719 }
   6720 
   6721 g.test('additionMatrixMatrixInterval')
   6722  .params(u =>
   6723    u
   6724      .combine('trait', ['f32', 'f16'] as const)
   6725      .beginSubcases()
   6726      .expandWithParams<MatrixPairToMatrixCase>(p => {
   6727        const trait = FP[p.trait];
   6728        const constants = trait.constants();
   6729        return [
   6730          // Only testing that different shapes of matrices are handled correctly
   6731          // here, to reduce test duplication.
   6732          // additionMatrixMatrixInterval uses AdditionIntervalOp for calculating intervals,
   6733          // so the testing for additionInterval covers the actual interval
   6734          // calculations.
   6735          {
   6736            input: [
   6737              [
   6738                [1, 2],
   6739                [3, 4],
   6740              ],
   6741              [
   6742                [10, 20],
   6743                [30, 40],
   6744              ],
   6745            ],
   6746            expected: [
   6747              [11, 22],
   6748              [33, 44],
   6749            ],
   6750          },
   6751          {
   6752            input: [
   6753              [
   6754                [1, 2],
   6755                [3, 4],
   6756                [5, 6],
   6757              ],
   6758              [
   6759                [10, 20],
   6760                [30, 40],
   6761                [50, 60],
   6762              ],
   6763            ],
   6764            expected: [
   6765              [11, 22],
   6766              [33, 44],
   6767              [55, 66],
   6768            ],
   6769          },
   6770          {
   6771            input: [
   6772              [
   6773                [1, 2],
   6774                [3, 4],
   6775                [5, 6],
   6776                [7, 8],
   6777              ],
   6778              [
   6779                [10, 20],
   6780                [30, 40],
   6781                [50, 60],
   6782                [70, 80],
   6783              ],
   6784            ],
   6785            expected: [
   6786              [11, 22],
   6787              [33, 44],
   6788              [55, 66],
   6789              [77, 88],
   6790            ],
   6791          },
   6792          {
   6793            input: [
   6794              [
   6795                [1, 2, 3],
   6796                [4, 5, 6],
   6797              ],
   6798              [
   6799                [10, 20, 30],
   6800                [40, 50, 60],
   6801              ],
   6802            ],
   6803            expected: [
   6804              [11, 22, 33],
   6805              [44, 55, 66],
   6806            ],
   6807          },
   6808          {
   6809            input: [
   6810              [
   6811                [1, 2, 3],
   6812                [4, 5, 6],
   6813                [7, 8, 9],
   6814              ],
   6815              [
   6816                [10, 20, 30],
   6817                [40, 50, 60],
   6818                [70, 80, 90],
   6819              ],
   6820            ],
   6821            expected: [
   6822              [11, 22, 33],
   6823              [44, 55, 66],
   6824              [77, 88, 99],
   6825            ],
   6826          },
   6827          {
   6828            input: [
   6829              [
   6830                [1, 2, 3],
   6831                [4, 5, 6],
   6832                [7, 8, 9],
   6833                [10, 11, 12],
   6834              ],
   6835              [
   6836                [10, 20, 30],
   6837                [40, 50, 60],
   6838                [70, 80, 90],
   6839                [1000, 1100, 1200],
   6840              ],
   6841            ],
   6842            expected: [
   6843              [11, 22, 33],
   6844              [44, 55, 66],
   6845              [77, 88, 99],
   6846              [1010, 1111, 1212],
   6847            ],
   6848          },
   6849          {
   6850            input: [
   6851              [
   6852                [1, 2, 3, 4],
   6853                [5, 6, 7, 8],
   6854              ],
   6855              [
   6856                [10, 20, 30, 40],
   6857                [50, 60, 70, 80],
   6858              ],
   6859            ],
   6860            expected: [
   6861              [11, 22, 33, 44],
   6862              [55, 66, 77, 88],
   6863            ],
   6864          },
   6865          {
   6866            input: [
   6867              [
   6868                [1, 2, 3, 4],
   6869                [5, 6, 7, 8],
   6870                [9, 10, 11, 12],
   6871              ],
   6872              [
   6873                [10, 20, 30, 40],
   6874                [50, 60, 70, 80],
   6875                [90, 1000, 1100, 1200],
   6876              ],
   6877            ],
   6878            expected: [
   6879              [11, 22, 33, 44],
   6880              [55, 66, 77, 88],
   6881              [99, 1010, 1111, 1212],
   6882            ],
   6883          },
   6884          {
   6885            input: [
   6886              [
   6887                [1, 2, 3, 4],
   6888                [5, 6, 7, 8],
   6889                [9, 10, 11, 12],
   6890                [13, 14, 15, 16],
   6891              ],
   6892              [
   6893                [10, 20, 30, 40],
   6894                [50, 60, 70, 80],
   6895                [90, 1000, 1100, 1200],
   6896                [1300, 1400, 1500, 1600],
   6897              ],
   6898            ],
   6899            expected: [
   6900              [11, 22, 33, 44],
   6901              [55, 66, 77, 88],
   6902              [99, 1010, 1111, 1212],
   6903              [1313, 1414, 1515, 1616],
   6904            ],
   6905          },
   6906          // Test the OOB is handled component-wise
   6907          {
   6908            input: [
   6909              [
   6910                [constants.positive.max, 2],
   6911                [3, 4],
   6912              ],
   6913              [
   6914                [constants.positive.max, 20],
   6915                [30, 40],
   6916              ],
   6917            ],
   6918            expected: [
   6919              [kUnboundedEndpoints, 22],
   6920              [33, 44],
   6921            ],
   6922          },
   6923        ];
   6924      })
   6925  )
   6926  .fn(t => {
   6927    const [x, y] = t.params.input;
   6928    const trait = FP[t.params.trait];
   6929    const expected = trait.toMatrix(t.params.expected);
   6930    const got = trait.additionMatrixMatrixInterval(x, y);
   6931    t.expect(
   6932      objectEquals(expected, got),
   6933      `${t.params.trait}.additionMatrixMatrixInterval([${JSON.stringify(x)}], [${JSON.stringify(
   6934        y
   6935      )}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'`
   6936    );
   6937  });
   6938 
   6939 g.test('subtractionMatrixMatrixInterval')
   6940  .params(u =>
   6941    u
   6942      .combine('trait', ['f32', 'f16'] as const)
   6943      .beginSubcases()
   6944      .expandWithParams<MatrixPairToMatrixCase>(p => {
   6945        const trait = FP[p.trait];
   6946        const constants = trait.constants();
   6947        return [
   6948          // Only testing that different shapes of matrices are handled correctly
   6949          // here, to reduce test duplication.
   6950          // subtractionMatrixMatrixInterval uses SubtractionIntervalOp for calculating intervals,
   6951          // so the testing for subtractionInterval covers the actual interval
   6952          // calculations.
   6953          {
   6954            input: [
   6955              [
   6956                [1, 2],
   6957                [3, 4],
   6958              ],
   6959              [
   6960                [-10, -20],
   6961                [-30, -40],
   6962              ],
   6963            ],
   6964            expected: [
   6965              [11, 22],
   6966              [33, 44],
   6967            ],
   6968          },
   6969          {
   6970            input: [
   6971              [
   6972                [1, 2],
   6973                [3, 4],
   6974                [5, 6],
   6975              ],
   6976              [
   6977                [-10, -20],
   6978                [-30, -40],
   6979                [-50, -60],
   6980              ],
   6981            ],
   6982            expected: [
   6983              [11, 22],
   6984              [33, 44],
   6985              [55, 66],
   6986            ],
   6987          },
   6988          {
   6989            input: [
   6990              [
   6991                [1, 2],
   6992                [3, 4],
   6993                [5, 6],
   6994                [7, 8],
   6995              ],
   6996              [
   6997                [-10, -20],
   6998                [-30, -40],
   6999                [-50, -60],
   7000                [-70, -80],
   7001              ],
   7002            ],
   7003            expected: [
   7004              [11, 22],
   7005              [33, 44],
   7006              [55, 66],
   7007              [77, 88],
   7008            ],
   7009          },
   7010          {
   7011            input: [
   7012              [
   7013                [1, 2, 3],
   7014                [4, 5, 6],
   7015              ],
   7016              [
   7017                [-10, -20, -30],
   7018                [-40, -50, -60],
   7019              ],
   7020            ],
   7021            expected: [
   7022              [11, 22, 33],
   7023              [44, 55, 66],
   7024            ],
   7025          },
   7026          {
   7027            input: [
   7028              [
   7029                [1, 2, 3],
   7030                [4, 5, 6],
   7031                [7, 8, 9],
   7032              ],
   7033              [
   7034                [-10, -20, -30],
   7035                [-40, -50, -60],
   7036                [-70, -80, -90],
   7037              ],
   7038            ],
   7039            expected: [
   7040              [11, 22, 33],
   7041              [44, 55, 66],
   7042              [77, 88, 99],
   7043            ],
   7044          },
   7045          {
   7046            input: [
   7047              [
   7048                [1, 2, 3],
   7049                [4, 5, 6],
   7050                [7, 8, 9],
   7051                [10, 11, 12],
   7052              ],
   7053              [
   7054                [-10, -20, -30],
   7055                [-40, -50, -60],
   7056                [-70, -80, -90],
   7057                [-1000, -1100, -1200],
   7058              ],
   7059            ],
   7060            expected: [
   7061              [11, 22, 33],
   7062              [44, 55, 66],
   7063              [77, 88, 99],
   7064              [1010, 1111, 1212],
   7065            ],
   7066          },
   7067          {
   7068            input: [
   7069              [
   7070                [1, 2, 3, 4],
   7071                [5, 6, 7, 8],
   7072              ],
   7073              [
   7074                [-10, -20, -30, -40],
   7075                [-50, -60, -70, -80],
   7076              ],
   7077            ],
   7078            expected: [
   7079              [11, 22, 33, 44],
   7080              [55, 66, 77, 88],
   7081            ],
   7082          },
   7083          {
   7084            input: [
   7085              [
   7086                [1, 2, 3, 4],
   7087                [5, 6, 7, 8],
   7088                [9, 10, 11, 12],
   7089              ],
   7090              [
   7091                [-10, -20, -30, -40],
   7092                [-50, -60, -70, -80],
   7093                [-90, -1000, -1100, -1200],
   7094              ],
   7095            ],
   7096            expected: [
   7097              [11, 22, 33, 44],
   7098              [55, 66, 77, 88],
   7099              [99, 1010, 1111, 1212],
   7100            ],
   7101          },
   7102          {
   7103            input: [
   7104              [
   7105                [1, 2, 3, 4],
   7106                [5, 6, 7, 8],
   7107                [9, 10, 11, 12],
   7108                [13, 14, 15, 16],
   7109              ],
   7110              [
   7111                [-10, -20, -30, -40],
   7112                [-50, -60, -70, -80],
   7113                [-90, -1000, -1100, -1200],
   7114                [-1300, -1400, -1500, -1600],
   7115              ],
   7116            ],
   7117            expected: [
   7118              [11, 22, 33, 44],
   7119              [55, 66, 77, 88],
   7120              [99, 1010, 1111, 1212],
   7121              [1313, 1414, 1515, 1616],
   7122            ],
   7123          },
   7124          // Test the OOB is handled component-wise
   7125          {
   7126            input: [
   7127              [
   7128                [constants.positive.max, 2],
   7129                [3, 4],
   7130              ],
   7131              [
   7132                [constants.negative.min, -20],
   7133                [-30, -40],
   7134              ],
   7135            ],
   7136            expected: [
   7137              [kUnboundedEndpoints, 22],
   7138              [33, 44],
   7139            ],
   7140          },
   7141        ];
   7142      })
   7143  )
   7144  .fn(t => {
   7145    const [x, y] = t.params.input;
   7146    const trait = FP[t.params.trait];
   7147    const expected = trait.toMatrix(t.params.expected);
   7148    const got = trait.subtractionMatrixMatrixInterval(x, y);
   7149    t.expect(
   7150      objectEquals(expected, got),
   7151      `${t.params.trait}.subtractionMatrixMatrixInterval([${JSON.stringify(x)}], [${JSON.stringify(
   7152        y
   7153      )}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'`
   7154    );
   7155  });
   7156 
   7157 g.test('multiplicationMatrixMatrixInterval')
   7158  .params(u =>
   7159    u
   7160      .combine('trait', ['f32', 'f16'] as const)
   7161      .beginSubcases()
   7162      .combineWithParams<MatrixPairToMatrixCase>([
   7163        // Only testing that different shapes of matrices are handled correctly
   7164        // here, to reduce test duplication.
   7165        // multiplicationMatrixMatrixInterval uses and transposeInterval &
   7166        // dotInterval for calculating intervals, so the testing for those functions
   7167        // will cover the actual interval calculations.
   7168        // Keep all expected result integer no larger than 2047 to ensure that all result is exactly
   7169        // represeantable in both f32 and f16.
   7170        {
   7171          input: [
   7172            [
   7173              [1, 2],
   7174              [3, 4],
   7175            ],
   7176            [
   7177              [11, 22],
   7178              [33, 44],
   7179            ],
   7180          ],
   7181          expected: [
   7182            [77, 110],
   7183            [165, 242],
   7184          ],
   7185        },
   7186        {
   7187          input: [
   7188            [
   7189              [1, 2],
   7190              [3, 4],
   7191            ],
   7192            [
   7193              [11, 22],
   7194              [33, 44],
   7195              [55, 66],
   7196            ],
   7197          ],
   7198          expected: [
   7199            [77, 110],
   7200            [165, 242],
   7201            [253, 374],
   7202          ],
   7203        },
   7204        {
   7205          input: [
   7206            [
   7207              [1, 2],
   7208              [3, 4],
   7209            ],
   7210            [
   7211              [11, 22],
   7212              [33, 44],
   7213              [55, 66],
   7214              [77, 88],
   7215            ],
   7216          ],
   7217          expected: [
   7218            [77, 110],
   7219            [165, 242],
   7220            [253, 374],
   7221            [341, 506],
   7222          ],
   7223        },
   7224        {
   7225          input: [
   7226            [
   7227              [1, 2, 3],
   7228              [4, 5, 6],
   7229            ],
   7230            [
   7231              [11, 22],
   7232              [33, 44],
   7233            ],
   7234          ],
   7235          expected: [
   7236            [99, 132, 165],
   7237            [209, 286, 363],
   7238          ],
   7239        },
   7240        {
   7241          input: [
   7242            [
   7243              [1, 2, 3],
   7244              [4, 5, 6],
   7245            ],
   7246            [
   7247              [11, 22],
   7248              [33, 44],
   7249              [55, 66],
   7250            ],
   7251          ],
   7252          expected: [
   7253            [99, 132, 165],
   7254            [209, 286, 363],
   7255            [319, 440, 561],
   7256          ],
   7257        },
   7258        {
   7259          input: [
   7260            [
   7261              [1, 2, 3],
   7262              [4, 5, 6],
   7263            ],
   7264            [
   7265              [11, 22],
   7266              [33, 44],
   7267              [55, 66],
   7268              [77, 88],
   7269            ],
   7270          ],
   7271          expected: [
   7272            [99, 132, 165],
   7273            [209, 286, 363],
   7274            [319, 440, 561],
   7275            [429, 594, 759],
   7276          ],
   7277        },
   7278        {
   7279          input: [
   7280            [
   7281              [1, 2, 3, 4],
   7282              [5, 6, 7, 8],
   7283            ],
   7284            [
   7285              [11, 22],
   7286              [33, 44],
   7287            ],
   7288          ],
   7289          expected: [
   7290            [121, 154, 187, 220],
   7291            [253, 330, 407, 484],
   7292          ],
   7293        },
   7294        {
   7295          input: [
   7296            [
   7297              [1, 2, 3, 4],
   7298              [5, 6, 7, 8],
   7299            ],
   7300            [
   7301              [11, 22],
   7302              [33, 44],
   7303              [55, 66],
   7304            ],
   7305          ],
   7306          expected: [
   7307            [121, 154, 187, 220],
   7308            [253, 330, 407, 484],
   7309            [385, 506, 627, 748],
   7310          ],
   7311        },
   7312        {
   7313          input: [
   7314            [
   7315              [1, 2, 3, 4],
   7316              [5, 6, 7, 8],
   7317            ],
   7318            [
   7319              [11, 22],
   7320              [33, 44],
   7321              [55, 66],
   7322              [77, 88],
   7323            ],
   7324          ],
   7325          expected: [
   7326            [121, 154, 187, 220],
   7327            [253, 330, 407, 484],
   7328            [385, 506, 627, 748],
   7329            [517, 682, 847, 1012],
   7330          ],
   7331        },
   7332        {
   7333          input: [
   7334            [
   7335              [1, 2],
   7336              [3, 4],
   7337              [5, 6],
   7338            ],
   7339            [
   7340              [11, 22, 33],
   7341              [44, 55, 66],
   7342            ],
   7343          ],
   7344          expected: [
   7345            [242, 308],
   7346            [539, 704],
   7347          ],
   7348        },
   7349        {
   7350          input: [
   7351            [
   7352              [1, 2],
   7353              [3, 4],
   7354              [5, 6],
   7355            ],
   7356            [
   7357              [11, 22, 33],
   7358              [44, 55, 66],
   7359              [77, 88, 99],
   7360            ],
   7361          ],
   7362          expected: [
   7363            [242, 308],
   7364            [539, 704],
   7365            [836, 1100],
   7366          ],
   7367        },
   7368        {
   7369          input: [
   7370            [
   7371              [1, 2],
   7372              [3, 4],
   7373              [5, 6],
   7374            ],
   7375            [
   7376              [11, 22, 33],
   7377              [44, 55, 66],
   7378              [77, 88, 99],
   7379              [10, 11, 12],
   7380            ],
   7381          ],
   7382          expected: [
   7383            [242, 308],
   7384            [539, 704],
   7385            [836, 1100],
   7386            [103, 136],
   7387          ],
   7388        },
   7389        {
   7390          input: [
   7391            [
   7392              [1, 2, 3],
   7393              [4, 5, 6],
   7394              [7, 8, 9],
   7395            ],
   7396            [
   7397              [11, 22, 33],
   7398              [44, 55, 66],
   7399            ],
   7400          ],
   7401          expected: [
   7402            [330, 396, 462],
   7403            [726, 891, 1056],
   7404          ],
   7405        },
   7406        {
   7407          input: [
   7408            [
   7409              [1, 2, 3],
   7410              [4, 5, 6],
   7411              [7, 8, 9],
   7412            ],
   7413            [
   7414              [11, 22, 33],
   7415              [44, 55, 66],
   7416              [77, 88, 99],
   7417            ],
   7418          ],
   7419          expected: [
   7420            [330, 396, 462],
   7421            [726, 891, 1056],
   7422            [1122, 1386, 1650],
   7423          ],
   7424        },
   7425        {
   7426          input: [
   7427            [
   7428              [1, 2, 3],
   7429              [4, 5, 6],
   7430              [7, 8, 9],
   7431            ],
   7432            [
   7433              [11, 22, 33],
   7434              [44, 55, 66],
   7435              [77, 88, 99],
   7436              [10, 11, 12],
   7437            ],
   7438          ],
   7439          expected: [
   7440            [330, 396, 462],
   7441            [726, 891, 1056],
   7442            [1122, 1386, 1650],
   7443            [138, 171, 204],
   7444          ],
   7445        },
   7446        {
   7447          input: [
   7448            [
   7449              [1, 2, 3, 4],
   7450              [5, 6, 7, 8],
   7451              [9, 10, 11, 12],
   7452            ],
   7453            [
   7454              [11, 12, 13],
   7455              [21, 22, 23],
   7456            ],
   7457          ],
   7458          expected: [
   7459            [188, 224, 260, 296],
   7460            [338, 404, 470, 536],
   7461          ],
   7462        },
   7463        {
   7464          input: [
   7465            [
   7466              [1, 2, 3, 4],
   7467              [5, 6, 7, 8],
   7468              [9, 10, 11, 12],
   7469            ],
   7470            [
   7471              [11, 12, 13],
   7472              [21, 22, 23],
   7473              [31, 32, 33],
   7474            ],
   7475          ],
   7476          expected: [
   7477            [188, 224, 260, 296],
   7478            [338, 404, 470, 536],
   7479            [488, 584, 680, 776],
   7480          ],
   7481        },
   7482        {
   7483          input: [
   7484            [
   7485              [1, 2, 3, 4],
   7486              [5, 6, 7, 8],
   7487              [9, 10, 11, 12],
   7488            ],
   7489            [
   7490              [11, 12, 13],
   7491              [21, 22, 23],
   7492              [31, 32, 33],
   7493              [41, 42, 43],
   7494            ],
   7495          ],
   7496          expected: [
   7497            [188, 224, 260, 296],
   7498            [338, 404, 470, 536],
   7499            [488, 584, 680, 776],
   7500            [638, 764, 890, 1016],
   7501          ],
   7502        },
   7503        {
   7504          input: [
   7505            [
   7506              [1, 2],
   7507              [3, 4],
   7508              [5, 6],
   7509              [7, 8],
   7510            ],
   7511            [
   7512              [11, 22, 33, 44],
   7513              [55, 66, 77, 88],
   7514            ],
   7515          ],
   7516          expected: [
   7517            [550, 660],
   7518            [1254, 1540],
   7519          ],
   7520        },
   7521        {
   7522          input: [
   7523            [
   7524              [1, 2],
   7525              [3, 4],
   7526              [5, 6],
   7527              [7, 8],
   7528            ],
   7529            [
   7530              [11, 12, 13, 14],
   7531              [21, 22, 23, 24],
   7532              [31, 32, 33, 34],
   7533            ],
   7534          ],
   7535          expected: [
   7536            [210, 260],
   7537            [370, 460],
   7538            [530, 660],
   7539          ],
   7540        },
   7541        {
   7542          input: [
   7543            [
   7544              [1, 2],
   7545              [3, 4],
   7546              [5, 6],
   7547              [7, 8],
   7548            ],
   7549            [
   7550              [11, 12, 13, 14],
   7551              [21, 22, 23, 24],
   7552              [31, 32, 33, 34],
   7553              [41, 42, 43, 44],
   7554            ],
   7555          ],
   7556          expected: [
   7557            [210, 260],
   7558            [370, 460],
   7559            [530, 660],
   7560            [690, 860],
   7561          ],
   7562        },
   7563        {
   7564          input: [
   7565            [
   7566              [1, 2, 3],
   7567              [4, 5, 6],
   7568              [7, 8, 9],
   7569              [10, 11, 12],
   7570            ],
   7571            [
   7572              [11, 12, 13, 14],
   7573              [21, 22, 23, 24],
   7574            ],
   7575          ],
   7576          expected: [
   7577            [290, 340, 390],
   7578            [510, 600, 690],
   7579          ],
   7580        },
   7581        {
   7582          input: [
   7583            [
   7584              [1, 2, 3],
   7585              [4, 5, 6],
   7586              [7, 8, 9],
   7587              [10, 11, 12],
   7588            ],
   7589            [
   7590              [11, 12, 13, 14],
   7591              [21, 22, 23, 24],
   7592              [31, 32, 33, 34],
   7593            ],
   7594          ],
   7595          expected: [
   7596            [290, 340, 390],
   7597            [510, 600, 690],
   7598            [730, 860, 990],
   7599          ],
   7600        },
   7601        {
   7602          input: [
   7603            [
   7604              [1, 2, 3],
   7605              [4, 5, 6],
   7606              [7, 8, 9],
   7607              [10, 11, 12],
   7608            ],
   7609            [
   7610              [11, 12, 13, 14],
   7611              [21, 22, 23, 24],
   7612              [31, 32, 33, 34],
   7613              [41, 42, 43, 44],
   7614            ],
   7615          ],
   7616          expected: [
   7617            [290, 340, 390],
   7618            [510, 600, 690],
   7619            [730, 860, 990],
   7620            [950, 1120, 1290],
   7621          ],
   7622        },
   7623        {
   7624          input: [
   7625            [
   7626              [1, 2, 3, 4],
   7627              [5, 6, 7, 8],
   7628              [9, 10, 11, 12],
   7629              [13, 14, 15, 16],
   7630            ],
   7631            [
   7632              [11, 12, 13, 14],
   7633              [21, 22, 23, 24],
   7634            ],
   7635          ],
   7636          expected: [
   7637            [370, 420, 470, 520],
   7638            [650, 740, 830, 920],
   7639          ],
   7640        },
   7641        {
   7642          input: [
   7643            [
   7644              [1, 2, 3, 4],
   7645              [5, 6, 7, 8],
   7646              [9, 10, 11, 12],
   7647              [13, 14, 15, 16],
   7648            ],
   7649            [
   7650              [11, 12, 13, 14],
   7651              [21, 22, 23, 24],
   7652              [31, 32, 33, 34],
   7653            ],
   7654          ],
   7655          expected: [
   7656            [370, 420, 470, 520],
   7657            [650, 740, 830, 920],
   7658            [930, 1060, 1190, 1320],
   7659          ],
   7660        },
   7661        {
   7662          input: [
   7663            [
   7664              [1, 2, 3, 4],
   7665              [5, 6, 7, 8],
   7666              [9, 10, 11, 12],
   7667              [13, 14, 15, 16],
   7668            ],
   7669            [
   7670              [11, 12, 13, 14],
   7671              [21, 22, 23, 24],
   7672              [31, 32, 33, 34],
   7673              [41, 42, 43, 44],
   7674            ],
   7675          ],
   7676          expected: [
   7677            [370, 420, 470, 520],
   7678            [650, 740, 830, 920],
   7679            [930, 1060, 1190, 1320],
   7680            [1210, 1380, 1550, 1720],
   7681          ],
   7682        },
   7683      ])
   7684  )
   7685  .fn(t => {
   7686    const [x, y] = t.params.input;
   7687    const trait = FP[t.params.trait];
   7688    const expected = trait.toMatrix(t.params.expected);
   7689    const got = trait.multiplicationMatrixMatrixInterval(x, y);
   7690    t.expect(
   7691      objectEquals(expected, got),
   7692      `${t.params.trait}.multiplicationMatrixMatrixInterval([${JSON.stringify(
   7693        x
   7694      )}], [${JSON.stringify(y)}]) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(
   7695        expected
   7696      )}]'`
   7697    );
   7698  });
   7699 
   7700 interface MatrixScalarToMatrixCase {
   7701  matrix: number[][];
   7702  scalar: number;
   7703  expected: (number | IntervalEndpoints)[][];
   7704 }
   7705 
   7706 const kMultiplicationMatrixScalarIntervalCases = {
   7707  f32: [
   7708    // From https://github.com/gpuweb/cts/issues/3044
   7709    {
   7710      matrix: [
   7711        [kValue.f32.negative.min, 0],
   7712        [0, 0],
   7713      ],
   7714      scalar: kValue.f32.negative.subnormal.min,
   7715      expected: [
   7716        [[0, reinterpretU32AsF32(0x407ffffe)], 0], // [[0, 3.9999995...], 0],
   7717        [0, 0],
   7718      ],
   7719    },
   7720  ] as MatrixScalarToMatrixCase[],
   7721  f16: [
   7722    // From https://github.com/gpuweb/cts/issues/3044
   7723    {
   7724      matrix: [
   7725        [kValue.f16.negative.min, 0],
   7726        [0, 0],
   7727      ],
   7728      scalar: kValue.f16.negative.subnormal.min,
   7729      expected: [
   7730        [[0, reinterpretU16AsF16(0x43fe)], 0], // [[0, 3.99609375], 0]
   7731        [0, 0],
   7732      ],
   7733    },
   7734  ] as MatrixScalarToMatrixCase[],
   7735 } as const;
   7736 
   7737 g.test('multiplicationMatrixScalarInterval')
   7738  .params(u =>
   7739    u
   7740      .combine('trait', ['f32', 'f16'] as const)
   7741      .beginSubcases()
   7742      .expandWithParams<MatrixScalarToMatrixCase>(p => {
   7743        const trait = FP[p.trait];
   7744        const constants = trait.constants();
   7745        // Primarily testing that different shapes of matrices are handled correctly
   7746        // here, to reduce test duplication. Additional testing for edge case
   7747        // discovered in https://github.com/gpuweb/cts/issues/3044.
   7748        //
   7749        // multiplicationMatrixScalarInterval uses for calculating intervals,
   7750        // so the testing for multiplicationInterval covers the actual interval
   7751        // calculations.
   7752        return [
   7753          {
   7754            matrix: [
   7755              [1, 2],
   7756              [3, 4],
   7757            ],
   7758            scalar: 10,
   7759            expected: [
   7760              [10, 20],
   7761              [30, 40],
   7762            ],
   7763          },
   7764          {
   7765            matrix: [
   7766              [1, 2],
   7767              [3, 4],
   7768              [5, 6],
   7769            ],
   7770            scalar: 10,
   7771            expected: [
   7772              [10, 20],
   7773              [30, 40],
   7774              [50, 60],
   7775            ],
   7776          },
   7777          {
   7778            matrix: [
   7779              [1, 2],
   7780              [3, 4],
   7781              [5, 6],
   7782              [7, 8],
   7783            ],
   7784            scalar: 10,
   7785            expected: [
   7786              [10, 20],
   7787              [30, 40],
   7788              [50, 60],
   7789              [70, 80],
   7790            ],
   7791          },
   7792          {
   7793            matrix: [
   7794              [1, 2, 3],
   7795              [4, 5, 6],
   7796            ],
   7797            scalar: 10,
   7798            expected: [
   7799              [10, 20, 30],
   7800              [40, 50, 60],
   7801            ],
   7802          },
   7803          {
   7804            matrix: [
   7805              [1, 2, 3],
   7806              [4, 5, 6],
   7807              [7, 8, 9],
   7808            ],
   7809            scalar: 10,
   7810            expected: [
   7811              [10, 20, 30],
   7812              [40, 50, 60],
   7813              [70, 80, 90],
   7814            ],
   7815          },
   7816          {
   7817            matrix: [
   7818              [1, 2, 3],
   7819              [4, 5, 6],
   7820              [7, 8, 9],
   7821              [10, 11, 12],
   7822            ],
   7823            scalar: 10,
   7824            expected: [
   7825              [10, 20, 30],
   7826              [40, 50, 60],
   7827              [70, 80, 90],
   7828              [100, 110, 120],
   7829            ],
   7830          },
   7831          {
   7832            matrix: [
   7833              [1, 2, 3, 4],
   7834              [5, 6, 7, 8],
   7835            ],
   7836            scalar: 10,
   7837            expected: [
   7838              [10, 20, 30, 40],
   7839              [50, 60, 70, 80],
   7840            ],
   7841          },
   7842          {
   7843            matrix: [
   7844              [1, 2, 3, 4],
   7845              [5, 6, 7, 8],
   7846              [9, 10, 11, 12],
   7847            ],
   7848            scalar: 10,
   7849            expected: [
   7850              [10, 20, 30, 40],
   7851              [50, 60, 70, 80],
   7852              [90, 100, 110, 120],
   7853            ],
   7854          },
   7855          {
   7856            matrix: [
   7857              [1, 2, 3, 4],
   7858              [5, 6, 7, 8],
   7859              [9, 10, 11, 12],
   7860              [13, 14, 15, 16],
   7861            ],
   7862            scalar: 10,
   7863            expected: [
   7864              [10, 20, 30, 40],
   7865              [50, 60, 70, 80],
   7866              [90, 100, 110, 120],
   7867              [130, 140, 150, 160],
   7868            ],
   7869          },
   7870          ...kMultiplicationMatrixScalarIntervalCases[p.trait],
   7871          // Test that OOB is component-wise
   7872          {
   7873            matrix: [
   7874              [1, 2],
   7875              [constants.positive.max, 4],
   7876            ],
   7877            scalar: 10,
   7878            expected: [
   7879              [10, 20],
   7880              [kUnboundedEndpoints, 40],
   7881            ],
   7882          },
   7883        ];
   7884      })
   7885  )
   7886  .fn(t => {
   7887    const matrix = t.params.matrix;
   7888    const scalar = t.params.scalar;
   7889    const trait = FP[t.params.trait];
   7890    const expected = trait.toMatrix(t.params.expected);
   7891    const got = trait.multiplicationMatrixScalarInterval(matrix, scalar);
   7892    t.expect(
   7893      objectEquals(expected, got),
   7894      `${t.params.trait}.multiplicationMatrixScalarInterval([${JSON.stringify(
   7895        matrix
   7896      )}], ${scalar}) returned '[${JSON.stringify(got)}]'. Expected '[${JSON.stringify(expected)}]'`
   7897    );
   7898  });
   7899 
   7900 // There are no explicit tests for multiplicationScalarMatrixInterval, since it
   7901 // is just a pass-through to multiplicationMatrixScalarInterval
   7902 
   7903 interface MatrixVectorToVectorCase {
   7904  matrix: number[][];
   7905  vector: number[];
   7906  expected: (number | IntervalEndpoints)[];
   7907 }
   7908 
   7909 g.test('multiplicationMatrixVectorInterval')
   7910  .params(u =>
   7911    u
   7912      .combine('trait', ['f32', 'f16'] as const)
   7913      .beginSubcases()
   7914      .combineWithParams<MatrixVectorToVectorCase>([
   7915        // Only testing that different shapes of matrices are handled correctly
   7916        // here, to reduce test duplication.
   7917        // multiplicationMatrixVectorInterval uses DotIntervalOp &
   7918        // TransposeIntervalOp for calculating intervals, so the testing for
   7919        // dotInterval & transposeInterval covers the actual interval
   7920        // calculations.
   7921        {
   7922          matrix: [
   7923            [1, 2],
   7924            [3, 4],
   7925          ],
   7926          vector: [11, 22],
   7927          expected: [77, 110],
   7928        },
   7929        {
   7930          matrix: [
   7931            [1, 2, 3],
   7932            [4, 5, 6],
   7933          ],
   7934          vector: [11, 22],
   7935          expected: [99, 132, 165],
   7936        },
   7937        {
   7938          matrix: [
   7939            [1, 2, 3, 4],
   7940            [5, 6, 7, 8],
   7941          ],
   7942          vector: [11, 22],
   7943          expected: [121, 154, 187, 220],
   7944        },
   7945        {
   7946          matrix: [
   7947            [1, 2],
   7948            [3, 4],
   7949            [5, 6],
   7950          ],
   7951          vector: [11, 22, 33],
   7952          expected: [242, 308],
   7953        },
   7954        {
   7955          matrix: [
   7956            [1, 2, 3],
   7957            [4, 5, 6],
   7958            [7, 8, 9],
   7959          ],
   7960          vector: [11, 22, 33],
   7961          expected: [330, 396, 462],
   7962        },
   7963        {
   7964          matrix: [
   7965            [1, 2, 3, 4],
   7966            [5, 6, 7, 8],
   7967            [9, 10, 11, 12],
   7968          ],
   7969          vector: [11, 22, 33],
   7970          expected: [418, 484, 550, 616],
   7971        },
   7972        {
   7973          matrix: [
   7974            [1, 2],
   7975            [3, 4],
   7976            [5, 6],
   7977            [7, 8],
   7978          ],
   7979          vector: [11, 22, 33, 44],
   7980          expected: [550, 660],
   7981        },
   7982        {
   7983          matrix: [
   7984            [1, 2, 3],
   7985            [4, 5, 6],
   7986            [7, 8, 9],
   7987            [10, 11, 12],
   7988          ],
   7989          vector: [11, 22, 33, 44],
   7990          expected: [770, 880, 990],
   7991        },
   7992        {
   7993          matrix: [
   7994            [1, 2, 3, 4],
   7995            [5, 6, 7, 8],
   7996            [9, 10, 11, 12],
   7997            [13, 14, 15, 16],
   7998          ],
   7999          vector: [11, 22, 33, 44],
   8000          expected: [990, 1100, 1210, 1320],
   8001        },
   8002      ])
   8003  )
   8004  .fn(t => {
   8005    const matrix = t.params.matrix;
   8006    const vector = t.params.vector;
   8007    const trait = FP[t.params.trait];
   8008    const expected = trait.toVector(t.params.expected);
   8009    const got = trait.multiplicationMatrixVectorInterval(matrix, vector);
   8010    t.expect(
   8011      objectEquals(expected, got),
   8012      `${t.params.trait}.multiplicationMatrixVectorInterval([${JSON.stringify(
   8013        matrix
   8014      )}], [${JSON.stringify(vector)}]) returned '[${JSON.stringify(
   8015        got
   8016      )}]'. Expected '[${JSON.stringify(expected)}]'`
   8017    );
   8018  });
   8019 
   8020 interface VectorMatrixToVectorCase {
   8021  vector: number[];
   8022  matrix: number[][];
   8023  expected: (number | IntervalEndpoints)[];
   8024 }
   8025 
   8026 g.test('multiplicationVectorMatrixInterval')
   8027  .params(u =>
   8028    u
   8029      .combine('trait', ['f32', 'f16'] as const)
   8030      .beginSubcases()
   8031      .combineWithParams<VectorMatrixToVectorCase>([
   8032        // Only testing that different shapes of matrices are handled correctly
   8033        // here, to reduce test duplication.
   8034        // multiplicationVectorMatrixInterval uses DotIntervalOp for calculating
   8035        // intervals, so the testing for dotInterval covers the actual interval
   8036        // calculations.
   8037        // Keep all expected result integer no larger than 2047 to ensure that
   8038        // all result is exactly representable in both f32 and f16.
   8039        {
   8040          vector: [1, 2],
   8041          matrix: [
   8042            [11, 22],
   8043            [33, 44],
   8044          ],
   8045          expected: [55, 121],
   8046        },
   8047        {
   8048          vector: [1, 2],
   8049          matrix: [
   8050            [11, 22],
   8051            [33, 44],
   8052            [55, 66],
   8053          ],
   8054          expected: [55, 121, 187],
   8055        },
   8056        {
   8057          vector: [1, 2],
   8058          matrix: [
   8059            [11, 22],
   8060            [33, 44],
   8061            [55, 66],
   8062            [77, 88],
   8063          ],
   8064          expected: [55, 121, 187, 253],
   8065        },
   8066        {
   8067          vector: [1, 2, 3],
   8068          matrix: [
   8069            [11, 12, 13],
   8070            [21, 22, 23],
   8071          ],
   8072          expected: [74, 134],
   8073        },
   8074        {
   8075          vector: [1, 2, 3],
   8076          matrix: [
   8077            [11, 12, 13],
   8078            [21, 22, 23],
   8079            [31, 32, 33],
   8080          ],
   8081          expected: [74, 134, 194],
   8082        },
   8083        {
   8084          vector: [1, 2, 3],
   8085          matrix: [
   8086            [11, 12, 13],
   8087            [21, 22, 23],
   8088            [31, 32, 33],
   8089            [41, 42, 43],
   8090          ],
   8091          expected: [74, 134, 194, 254],
   8092        },
   8093        {
   8094          vector: [1, 2, 3, 4],
   8095          matrix: [
   8096            [11, 12, 13, 14],
   8097            [21, 22, 23, 24],
   8098          ],
   8099          expected: [130, 230],
   8100        },
   8101        {
   8102          vector: [1, 2, 3, 4],
   8103          matrix: [
   8104            [11, 12, 13, 14],
   8105            [21, 22, 23, 24],
   8106            [31, 32, 33, 34],
   8107          ],
   8108          expected: [130, 230, 330],
   8109        },
   8110        {
   8111          vector: [1, 2, 3, 4],
   8112          matrix: [
   8113            [11, 12, 13, 14],
   8114            [21, 22, 23, 24],
   8115            [31, 32, 33, 34],
   8116            [41, 42, 43, 44],
   8117          ],
   8118          expected: [130, 230, 330, 430],
   8119        },
   8120      ])
   8121  )
   8122  .fn(t => {
   8123    const vector = t.params.vector;
   8124    const matrix = t.params.matrix;
   8125    const trait = FP[t.params.trait];
   8126    const expected = trait.toVector(t.params.expected);
   8127    const got = trait.multiplicationVectorMatrixInterval(vector, matrix);
   8128    t.expect(
   8129      objectEquals(expected, got),
   8130      `${t.params.trait}.multiplicationVectorMatrixInterval([${JSON.stringify(
   8131        vector
   8132      )}], [${JSON.stringify(matrix)}]) returned '[${JSON.stringify(
   8133        got
   8134      )}]'. Expected '[${JSON.stringify(expected)}]'`
   8135    );
   8136  });
   8137 
   8138 // API - Acceptance Intervals w/ bespoke implementations
   8139 
   8140 interface FaceForwardCase {
   8141  input: [number[], number[], number[]];
   8142  expected: ((number | IntervalEndpoints)[] | undefined)[];
   8143 }
   8144 
   8145 g.test('faceForwardIntervals')
   8146  .params(u =>
   8147    u
   8148      .combine('trait', ['f32', 'f16'] as const)
   8149      .beginSubcases()
   8150      .expandWithParams<FaceForwardCase>(p => {
   8151        const trait = FP[p.trait];
   8152        const constants = trait.constants();
   8153        // prettier-ignore
   8154        return [
   8155          // vec2
   8156          { input: [[1.0, 0.0], [1.0, 0.0], [1.0, 0.0]], expected: [[-1.0, 0.0]] },
   8157          { input: [[-1.0, 0.0], [1.0, 0.0], [1.0, 0.0]], expected: [[1.0, 0.0]] },
   8158          { input: [[1.0, 0.0], [-1.0, 1.0], [1.0, -1.0]], expected: [[1.0, 0.0]] },
   8159          { input: [[-1.0, 0.0], [-1.0, 1.0], [1.0, -1.0]], expected: [[-1.0, 0.0]] },
   8160          { input: [[10.0, 0.0], [10.0, 0.0], [10.0, 0.0]], expected: [[-10.0, 0.0]] },
   8161          { input: [[-10.0, 0.0], [10.0, 0.0], [10.0, 0.0]], expected: [[10.0, 0.0]] },
   8162          { input: [[10.0, 0.0], [-10.0, 10.0], [10.0, -10.0]], expected: [[10.0, 0.0]] },
   8163          { input: [[-10.0, 0.0], [-10.0, 10.0], [10.0, -10.0]], expected: [[-10.0, 0.0]] },
   8164          { input: [[0.1, 0.0], [0.1, 0.0], [0.1, 0.0]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'], 0.0]] },
   8165          { input: [[-0.1, 0.0], [0.1, 0.0], [0.1, 0.0]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['0.1'], 0.0]] },
   8166          { input: [[0.1, 0.0], [-0.1, 0.1], [0.1, -0.1]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['0.1'], 0.0]] },
   8167          { input: [[-0.1, 0.0], [-0.1, 0.1], [0.1, -0.1]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'], 0.0]] },
   8168 
   8169          // vec3
   8170          { 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]] },
   8171          { 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]] },
   8172          { 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]] },
   8173          { 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]] },
   8174          { 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]] },
   8175          { 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]] },
   8176          { 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]] },
   8177          { 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]] },
   8178          { input: [[0.1, 0.0, 0.0], [0.1, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'], 0.0, 0.0]] },
   8179          { input: [[-0.1, 0.0, 0.0], [0.1, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['0.1'], 0.0, 0.0]] },
   8180          { input: [[0.1, 0.0, 0.0], [-0.1, 0.0, 0.0], [0.1, -0.0, 0.0]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['0.1'], 0.0, 0.0]] },
   8181          { input: [[-0.1, 0.0, 0.0], [-0.1, 0.0, 0.0], [0.1, -0.0, 0.0]], expected: [[kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'], 0.0, 0.0]] },
   8182 
   8183          // vec4
   8184          { 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]] },
   8185          { 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]] },
   8186          { 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]] },
   8187          { 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]] },
   8188          { 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]] },
   8189          { 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]] },
   8190          { 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]] },
   8191          { 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]] },
   8192          { 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: [[kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'], 0.0, 0.0, 0.0]] },
   8193          { 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: [[kConstantCorrectlyRoundedExpectation[p.trait]['0.1'], 0.0, 0.0, 0.0]] },
   8194          { 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: [[kConstantCorrectlyRoundedExpectation[p.trait]['0.1'], 0.0, 0.0, 0.0]] },
   8195          { 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: [[kConstantCorrectlyRoundedExpectation[p.trait]['-0.1'], 0.0, 0.0, 0.0]] },
   8196 
   8197          // dot(y, z) === 0
   8198          { input: [[1.0, 1.0], [1.0, 0.0], [0.0, 1.0]], expected:  [[-1.0, -1.0]] },
   8199 
   8200          // subnormals, also dot(y, z) spans 0
   8201          { input: [[constants.positive.subnormal.max, 0.0], [constants.positive.subnormal.min, 0.0], [constants.negative.subnormal.min, 0.0]], expected:  [[[0.0, constants.positive.subnormal.max], 0.0], [[constants.negative.subnormal.min, 0], 0.0]] },
   8202 
   8203          // dot going OOB returns [undefined, x, -x]
   8204          { input: [[1.0, 1.0], [constants.positive.max, constants.positive.max], [constants.positive.max, constants.positive.max]], expected: [undefined, [1, 1], [-1, -1]] },
   8205        ];
   8206      })
   8207  )
   8208  .fn(t => {
   8209    const [x, y, z] = t.params.input;
   8210    const trait = FP[t.params.trait];
   8211    const expected = t.params.expected.map(e => (e !== undefined ? trait.toVector(e) : undefined));
   8212    const got = trait.faceForwardIntervals(x, y, z);
   8213    t.expect(
   8214      objectEquals(expected, got),
   8215      `${t.params.trait}.faceForwardInterval([${x}], [${y}], [${z}]) returned [${got}]. Expected [${expected}]`
   8216    );
   8217  });
   8218 
   8219 interface ModfCase {
   8220  input: number;
   8221  fract: number | IntervalEndpoints;
   8222  whole: number | IntervalEndpoints;
   8223 }
   8224 
   8225 g.test('modfInterval')
   8226  .params(u =>
   8227    u
   8228      .combine('trait', ['f32', 'f16', 'abstract'] as const)
   8229      .beginSubcases()
   8230      .expandWithParams<ModfCase>(p => {
   8231        const constants = FP[p.trait].constants();
   8232        // prettier-ignore
   8233        return [
   8234          // Normals
   8235          { input: 0, fract: 0, whole: 0 },
   8236          { input: 1, fract: 0, whole: 1 },
   8237          { input: -1, fract: 0, whole: -1 },
   8238          { input: 0.5, fract: 0.5, whole: 0 },
   8239          { input: -0.5, fract: -0.5, whole: 0 },
   8240          { input: 2.5, fract: 0.5, whole: 2 },
   8241          { input: -2.5, fract: -0.5, whole: -2 },
   8242          { input: 10.0, fract: 0, whole: 10 },
   8243          { input: -10.0, fract: 0, whole: -10 },
   8244 
   8245          // Subnormals
   8246          { input: constants.positive.subnormal.min, fract: [0, constants.positive.subnormal.min], whole: 0 },
   8247          { input: constants.positive.subnormal.max, fract: [0, constants.positive.subnormal.max], whole: 0 },
   8248          { input: constants.negative.subnormal.min, fract: [constants.negative.subnormal.min, 0], whole: 0 },
   8249          { input: constants.negative.subnormal.max, fract: [constants.negative.subnormal.max, 0], whole: 0 },
   8250 
   8251          // Boundaries
   8252          { input: constants.negative.min, fract: 0, whole: constants.negative.min },
   8253          { input: constants.negative.max, fract: constants.negative.max, whole: 0 },
   8254          { input: constants.positive.min, fract: constants.positive.min, whole: 0 },
   8255          { input: constants.positive.max, fract: 0, whole: constants.positive.max },
   8256        ];
   8257      })
   8258  )
   8259  .fn(t => {
   8260    const trait = FP[t.params.trait];
   8261    const expected = {
   8262      fract: trait.toInterval(t.params.fract),
   8263      whole: trait.toInterval(t.params.whole),
   8264    };
   8265 
   8266    const got = trait.modfInterval(t.params.input);
   8267    t.expect(
   8268      objectEquals(expected, got),
   8269      `${trait}.modfInterval([${t.params.input}) returned { fract: [${got.fract}], whole: [${got.whole}] }. Expected { fract: [${expected.fract}], whole: [${expected.whole}] }`
   8270    );
   8271  });
   8272 
   8273 interface RefractCase {
   8274  input: [number[], number[], number];
   8275  expected: (number | IntervalEndpoints)[];
   8276 }
   8277 
   8278 // Scope for refractInterval tests so that they can have constants for magic
   8279 // numbers that don't pollute the global namespace or have unwieldy long names.
   8280 {
   8281  const kNegativeOneEndpoints = {
   8282    f32: [
   8283      reinterpretU64AsF64(0xbff0_0000_c000_0000n),
   8284      reinterpretU64AsF64(0xbfef_ffff_4000_0000n),
   8285    ] as IntervalEndpoints,
   8286    f16: [reinterpretU16AsF16(0xbc06), reinterpretU16AsF16(0xbbfa)] as IntervalEndpoints,
   8287  } as const;
   8288 
   8289  // prettier-ignore
   8290  const kRefractIntervalCases = {
   8291    f32: [
   8292      // k > 0
   8293      // vec2
   8294      { input: [[1, -2], [3, 4], 5], expected: [[reinterpretU32AsF32(0x40ce87a4), reinterpretU32AsF32(0x40ce8840)],  // ~6.454...
   8295          [reinterpretU32AsF32(0xc100fae8), reinterpretU32AsF32(0xc100fa80)]] },  // ~-8.061...
   8296      // vec3
   8297      { input: [[1, -2, 3], [-4, 5, -6], 7], expected: [[reinterpretU32AsF32(0x40d24480), reinterpretU32AsF32(0x40d24c00)],  // ~6.571...
   8298          [reinterpretU32AsF32(0xc1576f80), reinterpretU32AsF32(0xc1576ad0)],  // ~-13.464...
   8299          [reinterpretU32AsF32(0x41a2d9b0), reinterpretU32AsF32(0x41a2dc80)]] },  // ~20.356...
   8300      // vec4
   8301      { input: [[1, -2, 3, -4], [-5, 6, -7, 8], 9], expected: [[reinterpretU32AsF32(0x410ae480), reinterpretU32AsF32(0x410af240)],  // ~8.680...
   8302          [reinterpretU32AsF32(0xc18cf7c0), reinterpretU32AsF32(0xc18cef80)],  // ~-17.620...
   8303          [reinterpretU32AsF32(0x41d46cc0), reinterpretU32AsF32(0x41d47660)],  // ~26.553...
   8304          [reinterpretU32AsF32(0xc20dfa80), reinterpretU32AsF32(0xc20df500)]] },  // ~-35.494...
   8305    ] as RefractCase[],
   8306    f16: [
   8307      // k > 0
   8308      // vec2
   8309      { input: [[1, -2], [3, 4], 5], expected: [[reinterpretU16AsF16(0x4620), reinterpretU16AsF16(0x46bc)],  // ~6.454...
   8310          [reinterpretU16AsF16(0xc840), reinterpretU16AsF16(0xc7b0)]] },  // ~-8.061...
   8311      // vec3
   8312      { input: [[1, -2, 3], [-4, 5, -6], 7], expected: [[reinterpretU16AsF16(0x4100), reinterpretU16AsF16(0x4940)],  // ~6.571...
   8313      [reinterpretU16AsF16(0xcc98), reinterpretU16AsF16(0xc830)],  // ~-13.464...
   8314      [reinterpretU16AsF16(0x4b20), reinterpretU16AsF16(0x4e90)]] },  // ~20.356...
   8315      // vec4
   8316      // x = [1, -2, 3, -4], y = [-5, 6, -7, 8], z = 9,
   8317      // dot(y, x) = -71, k = 1.0 - 9 * 9 * (1.0 - 71 * 71) = 408241 overflow f16.
   8318      { input: [[1, -2, 3, -4], [-5, 6, -7, 8], 9], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8319      // x = [1, -2, 3, -4], y = [-5, 4, -3, 2], z = 2.5,
   8320      // dot(y, x) = -30, k = 1.0 - 2.5 * 2.5 * (1.0 - 30 * 30) = 5619.75.
   8321      // a = z * dot(y, x) + sqrt(k) = ~-0.035, result is about z * x - a * y = [~2.325, ~-4.86, ~7.4025, ~-9.93]
   8322      { input: [[1, -2, 3, -4], [-5, 4, -3, 2], 2.5], expected: [[reinterpretU16AsF16(0x3900), reinterpretU16AsF16(0x4410)],  // ~2.325
   8323          [reinterpretU16AsF16(0xc640), reinterpretU16AsF16(0xc300)],  // ~-4.86
   8324          [reinterpretU16AsF16(0x4660), reinterpretU16AsF16(0x4838)],  // ~7.4025
   8325          [reinterpretU16AsF16(0xc950), reinterpretU16AsF16(0xc8a0)]] },  // ~-9.93
   8326    ] as RefractCase[],
   8327  } as const;
   8328 
   8329  g.test('refractInterval')
   8330    .params(u =>
   8331      u
   8332        .combine('trait', ['f32', 'f16'] as const)
   8333        .beginSubcases()
   8334        .expandWithParams<RefractCase>(p => {
   8335          const trait = FP[p.trait];
   8336          const constants = trait.constants();
   8337          // prettier-ignore
   8338          return [
   8339            ...kRefractIntervalCases[p.trait],
   8340 
   8341            // k < 0
   8342            { input: [[1, 1], [0.1, 0], 10], expected: [0, 0] },
   8343 
   8344            // k contains 0
   8345            { input: [[1, 1], [0.1, 0], 1.005038], expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
   8346 
   8347            // k > 0
   8348            // vec2
   8349            { input: [[1, 1], [1, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1] },
   8350            // vec3
   8351            { input: [[1, 1, 1], [1, 0, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1, 1] },
   8352            // vec4
   8353            { input: [[1, 1, 1, 1], [1, 0, 0, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1, 1, 1] },
   8354 
   8355            // Test that dot going OOB in the intermediate calculations propagates
   8356            { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8357            { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8358            { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8359            { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8360            { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8361            { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
   8362          ];
   8363        })
   8364    )
   8365    .fn(t => {
   8366      const [i, s, r] = t.params.input;
   8367      const trait = FP[t.params.trait];
   8368      const expected = trait.toVector(t.params.expected);
   8369      const got = trait.refractInterval(i, s, r);
   8370      t.expect(
   8371        objectEquals(expected, got),
   8372        `${t.params.trait}.refractIntervals([${i}], [${s}], ${r}) returned [${got}]. Expected [${expected}]`
   8373      );
   8374    });
   8375 }