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 }