| import { ROArrayArray, ROArrayArrayArray } from '../../common/util/types.js'; |
| import { assert, unreachable } from '../../common/util/util.js'; |
| import { Float16Array } from '../../external/petamoriken/float16/float16.js'; |
| import { Case } from '../shader/execution/expression/case.js'; |
| import { IntervalFilter } from '../shader/execution/expression/interval_filter.js'; |
| |
| import BinaryStream from './binary_stream.js'; |
| import { anyOf } from './compare.js'; |
| import { kValue } from './constants.js'; |
| import { |
| abstractFloat, |
| f16, |
| f32, |
| isFloatType, |
| ScalarValue, |
| ScalarType, |
| toMatrix, |
| toVector, |
| u32, |
| } from './conversion.js'; |
| import { |
| calculatePermutations, |
| cartesianProduct, |
| correctlyRoundedF16, |
| correctlyRoundedF32, |
| correctlyRoundedF64, |
| every2DArray, |
| flatten2DArray, |
| FlushMode, |
| flushSubnormalNumberF16, |
| flushSubnormalNumberF32, |
| flushSubnormalNumberF64, |
| isFiniteF16, |
| isFiniteF32, |
| isSubnormalNumberF16, |
| isSubnormalNumberF32, |
| isSubnormalNumberF64, |
| map2DArray, |
| oneULPF16, |
| oneULPF32, |
| nextAfterF64, |
| quantizeToF16, |
| quantizeToF32, |
| scalarF16Range, |
| scalarF32Range, |
| scalarF64Range, |
| sparseMatrixF16Range, |
| sparseMatrixF32Range, |
| sparseMatrixF64Range, |
| sparseScalarF16Range, |
| sparseScalarF32Range, |
| sparseScalarF64Range, |
| sparseVectorF16Range, |
| sparseVectorF32Range, |
| sparseVectorF64Range, |
| unflatten2DArray, |
| vectorF16Range, |
| vectorF32Range, |
| vectorF64Range, |
| } from './math.js'; |
| |
| /** Indicate the kind of WGSL floating point numbers being operated on */ |
| export type FPKind = 'f32' | 'f16' | 'abstract'; |
| |
| enum SerializedFPIntervalKind { |
| Abstract, |
| F32, |
| F16, |
| } |
| |
| /** serializeFPKind() serializes a FPKind to a BinaryStream */ |
| export function serializeFPKind(s: BinaryStream, value: FPKind) { |
| switch (value) { |
| case 'abstract': |
| s.writeU8(SerializedFPIntervalKind.Abstract); |
| break; |
| case 'f16': |
| s.writeU8(SerializedFPIntervalKind.F16); |
| break; |
| case 'f32': |
| s.writeU8(SerializedFPIntervalKind.F32); |
| break; |
| } |
| } |
| |
| /** deserializeFPKind() deserializes a FPKind from a BinaryStream */ |
| export function deserializeFPKind(s: BinaryStream): FPKind { |
| const kind = s.readU8(); |
| switch (kind) { |
| case SerializedFPIntervalKind.Abstract: |
| return 'abstract'; |
| case SerializedFPIntervalKind.F16: |
| return 'f16'; |
| case SerializedFPIntervalKind.F32: |
| return 'f32'; |
| default: |
| unreachable(`invalid deserialized FPKind: ${kind}`); |
| } |
| } |
| // Containers |
| |
| /** |
| * Representation of endpoints for an interval as an array with either one or |
| * two elements. Single element indicates that the interval is a single point. |
| * For two elements, the first is the lower edges of the interval and the |
| * second is the upper edge, i.e. e[0] <= e[1], where e is an IntervalEndpoints |
| */ |
| export type IntervalEndpoints = readonly [number] | readonly [number, number]; |
| |
| /** Represents a closed interval of floating point numbers */ |
| export class FPInterval { |
| public readonly kind: FPKind; |
| public readonly begin: number; |
| public readonly end: number; |
| |
| /** |
| * Constructor |
| * |
| * `FPTraits.toInterval` is the preferred way to create FPIntervals |
| * |
| * @param kind the floating point number type this is an interval for |
| * @param endpoints beginning and end of the interval |
| */ |
| public constructor(kind: FPKind, ...endpoints: IntervalEndpoints) { |
| this.kind = kind; |
| |
| const begin = endpoints[0]; |
| const end = endpoints.length === 2 ? endpoints[1] : endpoints[0]; |
| assert(!Number.isNaN(begin) && !Number.isNaN(end), `endpoints need to be non-NaN`); |
| assert( |
| begin <= end, |
| `endpoints[0] (${begin}) must be less than or equal to endpoints[1] (${end})` |
| ); |
| |
| this.begin = begin; |
| this.end = end; |
| } |
| |
| /** @returns the floating point traits for this interval */ |
| public traits(): FPTraits { |
| return FP[this.kind]; |
| } |
| |
| /** @returns begin and end if non-point interval, otherwise just begin */ |
| public endpoints(): IntervalEndpoints { |
| return this.isPoint() ? [this.begin] : [this.begin, this.end]; |
| } |
| |
| /** @returns if a point or interval is completely contained by this interval */ |
| public contains(n: number | FPInterval): boolean { |
| if (Number.isNaN(n)) { |
| // Being the 'any' interval indicates that accuracy is not defined for this |
| // test, so the test is just checking that this input doesn't cause the |
| // implementation to misbehave, so NaN is accepted. |
| return this.begin === Number.NEGATIVE_INFINITY && this.end === Number.POSITIVE_INFINITY; |
| } |
| |
| if (n instanceof FPInterval) { |
| return this.begin <= n.begin && this.end >= n.end; |
| } |
| return this.begin <= n && this.end >= n; |
| } |
| |
| /** @returns if any values in the interval may be flushed to zero, this |
| * includes any subnormals and zero itself. |
| */ |
| public containsZeroOrSubnormals(): boolean { |
| return !( |
| this.end < this.traits().constants().negative.subnormal.min || |
| this.begin > this.traits().constants().positive.subnormal.max |
| ); |
| } |
| |
| /** @returns if this interval contains a single point */ |
| public isPoint(): boolean { |
| return this.begin === this.end; |
| } |
| |
| /** @returns if this interval only contains finite values */ |
| public isFinite(): boolean { |
| return this.traits().isFinite(this.begin) && this.traits().isFinite(this.end); |
| } |
| |
| /** @returns a string representation for logging purposes */ |
| public toString(): string { |
| return `{ '${this.kind}', [${this.endpoints().map(this.traits().scalarBuilder)}] }`; |
| } |
| } |
| |
| /** serializeFPInterval() serializes a FPInterval to a BinaryStream */ |
| export function serializeFPInterval(s: BinaryStream, i: FPInterval) { |
| serializeFPKind(s, i.kind); |
| const traits = FP[i.kind]; |
| s.writeCond(i !== traits.constants().unboundedInterval, { |
| if_true: () => { |
| // Bounded |
| switch (i.kind) { |
| case 'abstract': |
| s.writeF64(i.begin); |
| s.writeF64(i.end); |
| break; |
| case 'f32': |
| s.writeF32(i.begin); |
| s.writeF32(i.end); |
| break; |
| case 'f16': |
| s.writeF16(i.begin); |
| s.writeF16(i.end); |
| break; |
| default: |
| unreachable(`Unable to serialize FPInterval ${i}`); |
| break; |
| } |
| }, |
| if_false: () => { |
| // Unbounded |
| }, |
| }); |
| } |
| |
| /** deserializeFPInterval() deserializes a FPInterval from a BinaryStream */ |
| export function deserializeFPInterval(s: BinaryStream): FPInterval { |
| const kind = deserializeFPKind(s); |
| const traits = FP[kind]; |
| return s.readCond({ |
| if_true: () => { |
| // Bounded |
| switch (kind) { |
| case 'abstract': |
| return new FPInterval(traits.kind, s.readF64(), s.readF64()); |
| case 'f32': |
| return new FPInterval(traits.kind, s.readF32(), s.readF32()); |
| case 'f16': |
| return new FPInterval(traits.kind, s.readF16(), s.readF16()); |
| } |
| unreachable(`Unable to deserialize FPInterval with kind ${kind}`); |
| }, |
| if_false: () => { |
| // Unbounded |
| return traits.constants().unboundedInterval; |
| }, |
| }); |
| } |
| |
| /** |
| * Representation of a vec2/3/4 of floating point intervals as an array of |
| * FPIntervals. |
| */ |
| export type FPVector = |
| | [FPInterval, FPInterval] |
| | [FPInterval, FPInterval, FPInterval] |
| | [FPInterval, FPInterval, FPInterval, FPInterval]; |
| |
| /** Shorthand for an Array of Arrays that contains a column-major matrix */ |
| type Array2D<T> = ROArrayArray<T>; |
| |
| /** |
| * Representation of a matCxR of floating point intervals as an array of arrays |
| * of FPIntervals. This maps onto the WGSL concept of matrix. Internally |
| */ |
| export type FPMatrix = |
| | readonly [readonly [FPInterval, FPInterval], readonly [FPInterval, FPInterval]] |
| | readonly [ |
| readonly [FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| ] |
| | readonly [ |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| readonly [FPInterval, FPInterval, FPInterval, FPInterval], |
| ]; |
| |
| // Utilities |
| |
| /** @returns input with an appended 0, if inputs contains non-zero subnormals */ |
| // When f16 traits is defined, this can be replaced with something like |
| // `FP.f16..addFlushIfNeeded` |
| function addFlushedIfNeededF16(values: readonly number[]): readonly number[] { |
| return values.some(v => v !== 0 && isSubnormalNumberF16(v)) ? values.concat(0) : values; |
| } |
| |
| // Operations |
| |
| /** |
| * A function that converts a point to an acceptance interval. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface ScalarToInterval { |
| (x: number): FPInterval; |
| } |
| |
| /** Operation used to implement a ScalarToInterval */ |
| interface ScalarToIntervalOp { |
| /** @returns acceptance interval for a function at point x */ |
| impl: ScalarToInterval; |
| |
| /** |
| * Calculates where in the domain defined by x the min/max extrema of impl |
| * occur and returns a span of those points to be used as the domain instead. |
| * |
| * Used by this.runScalarToIntervalOp before invoking impl. |
| * If not defined, the endpoints of the existing domain are assumed to be the |
| * extrema. |
| * |
| * This is only implemented for operations that meet all the following |
| * criteria: |
| * a) non-monotonic |
| * b) used in inherited accuracy calculations |
| * c) need to take in an interval for b) |
| * i.e. fooInterval takes in x: number | FPInterval, not x: number |
| */ |
| extrema?: (x: FPInterval) => FPInterval; |
| |
| /** |
| * Restricts the inputs to operation to the given domain. |
| * |
| * Only defined for operations that have tighter domain requirements than 'must |
| * be finite'. |
| */ |
| domain?: () => FPInterval; |
| } |
| |
| /** |
| * A function that converts a pair of points to an acceptance interval. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface ScalarPairToInterval { |
| (x: number, y: number): FPInterval; |
| } |
| |
| /** Domain for a ScalarPairToInterval implementation */ |
| interface ScalarPairToIntervalDomain { |
| // Arrays to support discrete valid domain intervals |
| x: readonly FPInterval[]; |
| y: readonly FPInterval[]; |
| } |
| |
| /** Operation used to implement a ScalarPairToInterval */ |
| interface ScalarPairToIntervalOp { |
| /** @returns acceptance interval for a function at point (x, y) */ |
| impl: ScalarPairToInterval; |
| /** |
| * Calculates where in domain defined by x & y the min/max extrema of impl |
| * occur and returns spans of those points to be used as the domain instead. |
| * |
| * Used by runScalarPairToIntervalOp before invoking impl. |
| * If not defined, the endpoints of the existing domain are assumed to be the |
| * extrema. |
| * |
| * This is only implemented for functions that meet all the following |
| * criteria: |
| * a) non-monotonic |
| * b) used in inherited accuracy calculations |
| * c) need to take in an interval for b) |
| */ |
| extrema?: (x: FPInterval, y: FPInterval) => [FPInterval, FPInterval]; |
| |
| /** |
| * Restricts the inputs to operation to the given domain. |
| * |
| * Only defined for operations that have tighter domain requirements than 'must |
| * be finite'. |
| */ |
| domain?: () => ScalarPairToIntervalDomain; |
| } |
| |
| /** |
| * A function that converts a triplet of points to an acceptance interval. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface ScalarTripleToInterval { |
| (x: number, y: number, z: number): FPInterval; |
| } |
| |
| /** Operation used to implement a ScalarTripleToInterval */ |
| interface ScalarTripleToIntervalOp { |
| // Re-using the *Op interface pattern for symmetry with the other operations. |
| /** @returns acceptance interval for a function at point (x, y, z) */ |
| impl: ScalarTripleToInterval; |
| } |
| |
| // Currently ScalarToVector is not integrated with the rest of the floating point |
| // framework, because the only builtins that use it are actually |
| // u32 -> [f32, f32, f32, f32] functions, so the whole rounding and interval |
| // process doesn't get applied to the inputs. |
| // They do use the framework internally by invoking divisionInterval on segments |
| // of the input. |
| /** |
| * A function that converts a point to a vector of acceptance intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface ScalarToVector { |
| (n: number): FPVector; |
| } |
| |
| /** |
| * A function that converts a vector to an acceptance interval. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface VectorToInterval { |
| (x: readonly number[]): FPInterval; |
| } |
| |
| /** Operation used to implement a VectorToInterval */ |
| interface VectorToIntervalOp { |
| // Re-using the *Op interface pattern for symmetry with the other operations. |
| /** @returns acceptance interval for a function on vector x */ |
| impl: VectorToInterval; |
| } |
| |
| /** |
| * A function that converts a pair of vectors to an acceptance interval. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface VectorPairToInterval { |
| (x: readonly number[], y: readonly number[]): FPInterval; |
| } |
| |
| /** Operation used to implement a VectorPairToInterval */ |
| interface VectorPairToIntervalOp { |
| // Re-using the *Op interface pattern for symmetry with the other operations. |
| /** @returns acceptance interval for a function on vectors (x, y) */ |
| impl: VectorPairToInterval; |
| } |
| |
| /** |
| * A function that converts a vector to a vector of acceptance intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface VectorToVector { |
| (x: readonly number[]): FPVector; |
| } |
| |
| /** Operation used to implement a VectorToVector */ |
| interface VectorToVectorOp { |
| // Re-using the *Op interface pattern for symmetry with the other operations. |
| /** @returns a vector of acceptance intervals for a function on vector x */ |
| impl: VectorToVector; |
| } |
| |
| /** |
| * A function that converts a pair of vectors to a vector of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface VectorPairToVector { |
| (x: readonly number[], y: readonly number[]): FPVector; |
| } |
| |
| /** Operation used to implement a VectorPairToVector */ |
| interface VectorPairToVectorOp { |
| // Re-using the *Op interface pattern for symmetry with the other operations. |
| /** @returns a vector of acceptance intervals for a function on vectors (x, y) */ |
| impl: VectorPairToVector; |
| } |
| |
| /** |
| * A function that converts a vector and a scalar to a vector of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface VectorScalarToVector { |
| (x: readonly number[], y: number): FPVector; |
| } |
| |
| /** |
| * A function that converts a scalar and a vector to a vector of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface ScalarVectorToVector { |
| (x: number, y: readonly number[]): FPVector; |
| } |
| |
| /** |
| * A function that converts a matrix to an acceptance interval. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface MatrixToScalar { |
| (m: Array2D<number>): FPInterval; |
| } |
| |
| /** Operation used to implement a MatrixToMatrix */ |
| interface MatrixToMatrixOp { |
| // Re-using the *Op interface pattern for symmetry with the other operations. |
| /** @returns a matrix of acceptance intervals for a function on matrix x */ |
| impl: MatrixToMatrix; |
| } |
| |
| /** |
| * A function that converts a matrix to a matrix of acceptance intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface MatrixToMatrix { |
| (m: Array2D<number>): FPMatrix; |
| } |
| |
| /** |
| * A function that converts a pair of matrices to a matrix of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface MatrixPairToMatrix { |
| (x: Array2D<number>, y: Array2D<number>): FPMatrix; |
| } |
| |
| /** |
| * A function that converts a matrix and a scalar to a matrix of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface MatrixScalarToMatrix { |
| (x: Array2D<number>, y: number): FPMatrix; |
| } |
| |
| /** |
| * A function that converts a scalar and a matrix to a matrix of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface ScalarMatrixToMatrix { |
| (x: number, y: Array2D<number>): FPMatrix; |
| } |
| |
| /** |
| * A function that converts a matrix and a vector to a vector of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface MatrixVectorToVector { |
| (x: Array2D<number>, y: readonly number[]): FPVector; |
| } |
| |
| /** |
| * A function that converts a vector and a matrix to a vector of acceptance |
| * intervals. |
| * This is the public facing API for builtin implementations that is called |
| * from tests. |
| */ |
| export interface VectorMatrixToVector { |
| (x: readonly number[], y: Array2D<number>): FPVector; |
| } |
| |
| // Traits |
| |
| /** |
| * Typed structure containing all the constants defined for each |
| * WGSL floating point kind |
| */ |
| interface FPConstants { |
| positive: { |
| min: number; |
| max: number; |
| infinity: number; |
| nearest_max: number; |
| less_than_one: number; |
| subnormal: { |
| min: number; |
| max: number; |
| }; |
| pi: { |
| whole: number; |
| three_quarters: number; |
| half: number; |
| third: number; |
| quarter: number; |
| sixth: number; |
| }; |
| e: number; |
| }; |
| negative: { |
| min: number; |
| max: number; |
| infinity: number; |
| nearest_min: number; |
| less_than_one: number; |
| subnormal: { |
| min: number; |
| max: number; |
| }; |
| pi: { |
| whole: number; |
| three_quarters: number; |
| half: number; |
| third: number; |
| quarter: number; |
| sixth: number; |
| }; |
| }; |
| bias: number; |
| unboundedInterval: FPInterval; |
| zeroInterval: FPInterval; |
| negPiToPiInterval: FPInterval; |
| greaterThanZeroInterval: FPInterval; |
| negOneToOneInterval: FPInterval; |
| zeroVector: { |
| 2: FPVector; |
| 3: FPVector; |
| 4: FPVector; |
| }; |
| unboundedVector: { |
| 2: FPVector; |
| 3: FPVector; |
| 4: FPVector; |
| }; |
| unboundedMatrix: { |
| 2: { |
| 2: FPMatrix; |
| 3: FPMatrix; |
| 4: FPMatrix; |
| }; |
| 3: { |
| 2: FPMatrix; |
| 3: FPMatrix; |
| 4: FPMatrix; |
| }; |
| 4: { |
| 2: FPMatrix; |
| 3: FPMatrix; |
| 4: FPMatrix; |
| }; |
| }; |
| } |
| |
| /** A representation of an FPInterval for a case param */ |
| export type FPIntervalParam = { |
| kind: FPKind; |
| interval: number | IntervalEndpoints; |
| }; |
| |
| /** Abstract base class for all floating-point traits */ |
| export abstract class FPTraits { |
| public readonly kind: FPKind; |
| protected constructor(k: FPKind) { |
| this.kind = k; |
| } |
| |
| public abstract constants(): FPConstants; |
| |
| // Utilities - Implemented |
| |
| /** @returns an interval containing the point or the original interval */ |
| public toInterval(n: number | IntervalEndpoints | FPInterval): FPInterval { |
| if (n instanceof FPInterval) { |
| if (n.kind === this.kind) { |
| return n; |
| } |
| |
| // Preserve if the original interval was unbounded or bounded |
| if (!n.isFinite()) { |
| return this.constants().unboundedInterval; |
| } |
| |
| return new FPInterval(this.kind, ...n.endpoints()); |
| } |
| |
| if (n instanceof Array) { |
| return new FPInterval(this.kind, ...n); |
| } |
| |
| return new FPInterval(this.kind, n, n); |
| } |
| |
| /** |
| * WGSL specifies unbounded precision: |
| * https://www.w3.org/TR/WGSL/#floating-point-accuracy |
| * In most computations doubles (number in js) are equivalent |
| * to correctly rounded intervals. However in some cases |
| * (addition/subtraction) doubles do not provide enough numeric precision. |
| * |
| * These cases are whenever a small magnitude number, y |
| * (e.g. smallest positive normal) is added (or subtracted) from a much |
| * larger magnitude number x (1.0), such that the difference between them |
| * is smaller then ULP(x). When working in JS numbers the result will, |
| * incorrectly, simply be x, since it will get rounded. JS rounds to even |
| * but WGSL allows for rounding up and down (which in our context will be an interval). |
| * We must detect these cases where double precision does not represent |
| * infinitely accurate computations accurately then we must manually create the interval. |
| * |
| * @param sum the result of adding large_val + small_val, using Number |
| * @param large_val: the summand with larger magnitude |
| * @param small_val: the summand with smaller magnitude |
| * @returns an interval containing the correctly rounded val with respect to unbounded precision |
| */ |
| public correctlyRoundedIntervalWithUnboundedPrecisionForAddition( |
| val: number, |
| large_val: number, |
| small_val: number |
| ): FPInterval { |
| if (val === large_val && !(small_val === 0.0)) { |
| if (Math.sign(small_val) >= 0) { |
| return this.correctlyRoundedInterval( |
| this.toInterval([large_val, nextAfterF64(large_val, 'positive', 'flush')]) |
| ); |
| } else { |
| return this.correctlyRoundedInterval( |
| this.toInterval([nextAfterF64(large_val, 'negative', 'flush'), large_val]) |
| ); |
| } |
| } |
| return this.correctlyRoundedInterval(val); |
| } |
| |
| /** |
| * Makes a param that can be turned into an interval |
| */ |
| public toParam(n: number | IntervalEndpoints): FPIntervalParam { |
| return { |
| kind: this.kind, |
| interval: n, |
| }; |
| } |
| |
| /** |
| * Converts p into an FPInterval if it is an FPIntervalPAram |
| */ |
| public fromParam( |
| p: number | IntervalEndpoints | FPIntervalParam |
| ): number | IntervalEndpoints | FPInterval { |
| const param = p as FPIntervalParam; |
| if (param.interval && param.kind) { |
| assert(param.kind === this.kind); |
| return this.toInterval(param.interval); |
| } |
| return p as number | IntervalEndpoints; |
| } |
| |
| /** |
| * @returns an interval with the tightest endpoints that includes all provided |
| * intervals |
| */ |
| public spanIntervals(...intervals: readonly FPInterval[]): FPInterval { |
| assert(intervals.length > 0, `span of an empty list of FPIntervals is not allowed`); |
| assert( |
| intervals.every(i => i.kind === this.kind), |
| `span is only defined for intervals with the same kind` |
| ); |
| let begin = Number.POSITIVE_INFINITY; |
| let end = Number.NEGATIVE_INFINITY; |
| intervals.forEach(i => { |
| begin = Math.min(i.begin, begin); |
| end = Math.max(i.end, end); |
| }); |
| return this.toInterval([begin, end]); |
| } |
| |
| /** Narrow an array of values to FPVector if possible */ |
| public isVector(v: ReadonlyArray<number | IntervalEndpoints | FPInterval>): v is FPVector { |
| if (v.every(e => e instanceof FPInterval && e.kind === this.kind)) { |
| return v.length === 2 || v.length === 3 || v.length === 4; |
| } |
| return false; |
| } |
| |
| /** @returns an FPVector representation of an array of values if possible */ |
| public toVector(v: ReadonlyArray<number | IntervalEndpoints | FPInterval>): FPVector { |
| if (this.isVector(v) && v.every(e => e.kind === this.kind)) { |
| return v; |
| } |
| |
| const f = v.map(e => this.toInterval(e)); |
| // The return of the map above is a readonly FPInterval[], which needs to be narrowed |
| // to FPVector, since FPVector is defined as fixed length tuples. |
| if (this.isVector(f)) { |
| return f; |
| } |
| unreachable(`Cannot convert [${v}] to FPVector`); |
| } |
| |
| /** |
| * @returns a FPVector where each element is the span for corresponding |
| * elements at the same index in the input vectors |
| */ |
| public spanVectors(...vectors: FPVector[]): FPVector { |
| assert( |
| vectors.every(e => this.isVector(e)), |
| 'Vector span is not defined for vectors of differing floating point kinds' |
| ); |
| |
| const vector_length = vectors[0].length; |
| assert( |
| vectors.every(e => e.length === vector_length), |
| `Vector span is not defined for vectors of differing lengths` |
| ); |
| |
| const result: FPInterval[] = new Array<FPInterval>(vector_length); |
| |
| for (let i = 0; i < vector_length; i++) { |
| result[i] = this.spanIntervals(...vectors.map(v => v[i])); |
| } |
| return this.toVector(result); |
| } |
| |
| /** Narrow an array of an array of values to FPMatrix if possible */ |
| public isMatrix(m: Array2D<number | IntervalEndpoints | FPInterval> | FPVector[]): m is FPMatrix { |
| if (!m.every(c => c.every(e => e instanceof FPInterval && e.kind === this.kind))) { |
| return false; |
| } |
| // At this point m guaranteed to be a ROArrayArray<FPInterval>, but maybe typed as a |
| // FPVector[]. |
| // Coercing the type since FPVector[] is functionally equivalent to |
| // ROArrayArray<FPInterval> for .length and .every, but they are type compatible, |
| // since tuples are not equivalent to arrays, so TS considers c in .every to |
| // be unresolvable below, even though our usage is safe. |
| m = m as ROArrayArray<FPInterval>; |
| |
| if (m.length > 4 || m.length < 2) { |
| return false; |
| } |
| |
| const num_rows = m[0].length; |
| if (num_rows > 4 || num_rows < 2) { |
| return false; |
| } |
| |
| return m.every(c => c.length === num_rows); |
| } |
| |
| /** @returns an FPMatrix representation of an array of an array of values if possible */ |
| public toMatrix(m: Array2D<number | IntervalEndpoints | FPInterval> | FPVector[]): FPMatrix { |
| if ( |
| this.isMatrix(m) && |
| every2DArray(m, (e: FPInterval) => { |
| return e.kind === this.kind; |
| }) |
| ) { |
| return m; |
| } |
| |
| const result = map2DArray(m, this.toInterval.bind(this)); |
| |
| // The return of the map above is a ROArrayArray<FPInterval>, which needs to be |
| // narrowed to FPMatrix, since FPMatrix is defined as fixed length tuples. |
| if (this.isMatrix(result)) { |
| return result; |
| } |
| unreachable(`Cannot convert ${m} to FPMatrix`); |
| } |
| |
| /** |
| * @returns a FPMatrix where each element is the span for corresponding |
| * elements at the same index in the input matrices |
| */ |
| public spanMatrices(...matrices: FPMatrix[]): FPMatrix { |
| // Coercing the type of matrices, since tuples are not generally compatible |
| // with Arrays, but they are functionally equivalent for the usages in this |
| // function. |
| const ms = matrices as Array2D<FPInterval>[]; |
| const num_cols = ms[0].length; |
| const num_rows = ms[0][0].length; |
| assert( |
| ms.every(m => m.length === num_cols && m.every(r => r.length === num_rows)), |
| `Matrix span is not defined for Matrices of differing dimensions` |
| ); |
| |
| const result: FPInterval[][] = [...Array(num_cols)].map(_ => [...Array(num_rows)]); |
| for (let i = 0; i < num_cols; i++) { |
| for (let j = 0; j < num_rows; j++) { |
| result[i][j] = this.spanIntervals(...ms.map(m => m[i][j])); |
| } |
| } |
| |
| return this.toMatrix(result); |
| } |
| |
| /** @returns input with an appended 0, if inputs contains non-zero subnormals */ |
| public addFlushedIfNeeded(values: readonly number[]): readonly number[] { |
| const subnormals = values.filter(this.isSubnormal); |
| const needs_zero = subnormals.length > 0 && subnormals.every(s => s !== 0); |
| return needs_zero ? values.concat(0) : values; |
| } |
| |
| /** Stub for scalar to interval generator */ |
| protected unimplementedScalarToInterval(name: string, _x: number | FPInterval): FPInterval { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for scalar pair to interval generator */ |
| protected unimplementedScalarPairToInterval( |
| name: string, |
| _x: number | FPInterval, |
| _y: number | FPInterval |
| ): FPInterval { |
| unreachable(`'${name}' is yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for scalar triple to interval generator */ |
| protected unimplementedScalarTripleToInterval( |
| name: string, |
| _x: number | FPInterval, |
| _y: number | FPInterval, |
| _z: number | FPInterval |
| ): FPInterval { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for scalar to vector generator */ |
| protected unimplementedScalarToVector(name: string, _x: number | FPInterval): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for vector to interval generator */ |
| protected unimplementedVectorToInterval(name: string, _x: (number | FPInterval)[]): FPInterval { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for vector pair to interval generator */ |
| protected unimplementedVectorPairToInterval( |
| name: string, |
| _x: readonly (number | FPInterval)[], |
| _y: readonly (number | FPInterval)[] |
| ): FPInterval { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for vector to vector generator */ |
| protected unimplementedVectorToVector( |
| name: string, |
| _x: readonly (number | FPInterval)[] |
| ): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for vector pair to vector generator */ |
| protected unimplementedVectorPairToVector( |
| name: string, |
| _x: readonly (number | FPInterval)[], |
| _y: readonly (number | FPInterval)[] |
| ): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for vector-scalar to vector generator */ |
| protected unimplementedVectorScalarToVector( |
| name: string, |
| _x: readonly (number | FPInterval)[], |
| _y: number | FPInterval |
| ): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for scalar-vector to vector generator */ |
| protected unimplementedScalarVectorToVector( |
| name: string, |
| _x: number | FPInterval, |
| _y: (number | FPInterval)[] |
| ): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for matrix to interval generator */ |
| protected unimplementedMatrixToInterval(name: string, _x: Array2D<number>): FPInterval { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for matrix to matirx generator */ |
| protected unimplementedMatrixToMatrix(name: string, _x: Array2D<number>): FPMatrix { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for matrix pair to matrix generator */ |
| protected unimplementedMatrixPairToMatrix( |
| name: string, |
| _x: Array2D<number>, |
| _y: Array2D<number> |
| ): FPMatrix { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for matrix-scalar to matrix generator */ |
| protected unimplementedMatrixScalarToMatrix( |
| name: string, |
| _x: Array2D<number>, |
| _y: number | FPInterval |
| ): FPMatrix { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for scalar-matrix to matrix generator */ |
| protected unimplementedScalarMatrixToMatrix( |
| name: string, |
| _x: number | FPInterval, |
| _y: Array2D<number> |
| ): FPMatrix { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for matrix-vector to vector generator */ |
| protected unimplementedMatrixVectorToVector( |
| name: string, |
| _x: Array2D<number>, |
| _y: readonly (number | FPInterval)[] |
| ): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for vector-matrix to vector generator */ |
| protected unimplementedVectorMatrixToVector( |
| name: string, |
| _x: readonly (number | FPInterval)[], |
| _y: Array2D<number> |
| ): FPVector { |
| unreachable(`'${name}' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for distance generator */ |
| protected unimplementedDistance( |
| _x: number | readonly number[], |
| _y: number | readonly number[] |
| ): FPInterval { |
| unreachable(`'distance' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for faceForward */ |
| protected unimplementedFaceForward( |
| _x: readonly number[], |
| _y: readonly number[], |
| _z: readonly number[] |
| ): (FPVector | undefined)[] { |
| unreachable(`'faceForward' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for length generator */ |
| protected unimplementedLength( |
| _x: number | FPInterval | readonly number[] | FPVector |
| ): FPInterval { |
| unreachable(`'length' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for modf generator */ |
| protected unimplementedModf(_x: number): { fract: FPInterval; whole: FPInterval } { |
| unreachable(`'modf' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for refract generator */ |
| protected unimplementedRefract( |
| _i: readonly number[], |
| _s: readonly number[], |
| _r: number |
| ): FPVector { |
| unreachable(`'refract' is not yet implemented for '${this.kind}'`); |
| } |
| |
| /** Stub for absolute errors */ |
| protected unimplementedAbsoluteErrorInterval(_n: number, _error_range: number): FPInterval { |
| unreachable(`Absolute Error is not implement for '${this.kind}'`); |
| } |
| |
| /** Stub for ULP errors */ |
| protected unimplementedUlpInterval(_n: number, _numULP: number): FPInterval { |
| unreachable(`ULP Error is not implement for '${this.kind}'`); |
| } |
| |
| // Utilities - Defined by subclass |
| /** |
| * @returns the nearest precise value to the input. Rounding should be IEEE |
| * 'roundTiesToEven'. |
| */ |
| public abstract readonly quantize: (n: number) => number; |
| /** @returns all valid roundings of input */ |
| public abstract readonly correctlyRounded: (n: number) => readonly number[]; |
| /** @returns true if input is considered finite, otherwise false */ |
| public abstract readonly isFinite: (n: number) => boolean; |
| /** @returns true if input is considered subnormal, otherwise false */ |
| public abstract readonly isSubnormal: (n: number) => boolean; |
| /** @returns 0 if the provided number is subnormal, otherwise returns the proved number */ |
| public abstract readonly flushSubnormal: (n: number) => number; |
| /** @returns 1 * ULP: (number) */ |
| public abstract readonly oneULP: (target: number, mode?: FlushMode) => number; |
| /** @returns a builder for converting numbers to ScalarsValues */ |
| public abstract readonly scalarBuilder: (n: number) => ScalarValue; |
| /** @returns a range of scalars for testing */ |
| public abstract scalarRange(): readonly number[]; |
| /** @returns a reduced range of scalars for testing */ |
| public abstract sparseScalarRange(): readonly number[]; |
| /** @returns a range of dim element vectors for testing */ |
| public abstract vectorRange(dim: number): ROArrayArray<number>; |
| /** @returns a reduced range of dim element vectors for testing */ |
| public abstract sparseVectorRange(dim: number): ROArrayArray<number>; |
| /** @returns a reduced range of cols x rows matrices for testing |
| * |
| * A non-sparse version of this generator is intentionally not provided due to |
| * runtime issues with more dense ranges. |
| */ |
| public abstract sparseMatrixRange(cols: number, rows: number): ROArrayArrayArray<number>; |
| |
| // Framework - Cases |
| |
| /** |
| * @returns a Case for the param and the interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param the param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| private makeScalarToIntervalCase( |
| param: number, |
| filter: IntervalFilter, |
| ...ops: ScalarToInterval[] |
| ): Case | undefined { |
| param = this.quantize(param); |
| |
| const intervals = ops.map(o => o(param)); |
| if (filter === 'finite' && intervals.some(i => !i.isFinite())) { |
| return undefined; |
| } |
| return { input: [this.scalarBuilder(param)], expected: anyOf(...intervals) }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param params array of inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateScalarToIntervalCases( |
| params: readonly number[], |
| filter: IntervalFilter, |
| ...ops: ScalarToInterval[] |
| ): Case[] { |
| return params.reduce((cases, e) => { |
| const c = this.makeScalarToIntervalCase(e, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and the interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param0 the first param to pass in |
| * @param param1 the second param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| private makeScalarPairToIntervalCase( |
| param0: number, |
| param1: number, |
| filter: IntervalFilter, |
| ...ops: ScalarPairToInterval[] |
| ): Case | undefined { |
| param0 = this.quantize(param0); |
| param1 = this.quantize(param1); |
| |
| const intervals = ops.map(o => o(param0, param1)); |
| if (filter === 'finite' && intervals.some(i => !i.isFinite())) { |
| return undefined; |
| } |
| return { |
| input: [this.scalarBuilder(param0), this.scalarBuilder(param1)], |
| expected: anyOf(...intervals), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param param0s array of inputs to try for the first input |
| * @param param1s array of inputs to try for the second input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateScalarPairToIntervalCases( |
| param0s: readonly number[], |
| param1s: readonly number[], |
| filter: IntervalFilter, |
| ...ops: ScalarPairToInterval[] |
| ): Case[] { |
| return cartesianProduct(param0s, param1s).reduce((cases, e) => { |
| const c = this.makeScalarPairToIntervalCase(e[0], e[1], filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and the interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param0 the first param to pass in |
| * @param param1 the second param to pass in |
| * @param param2 the third param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public makeScalarTripleToIntervalCase( |
| param0: number, |
| param1: number, |
| param2: number, |
| filter: IntervalFilter, |
| ...ops: ScalarTripleToInterval[] |
| ): Case | undefined { |
| param0 = this.quantize(param0); |
| param1 = this.quantize(param1); |
| param2 = this.quantize(param2); |
| |
| const intervals = ops.map(o => o(param0, param1, param2)); |
| if (filter === 'finite' && intervals.some(i => !i.isFinite())) { |
| return undefined; |
| } |
| return { |
| input: [this.scalarBuilder(param0), this.scalarBuilder(param1), this.scalarBuilder(param2)], |
| expected: anyOf(...intervals), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param param0s array of inputs to try for the first input |
| * @param param1s array of inputs to try for the second input |
| * @param param2s array of inputs to try for the third input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateScalarTripleToIntervalCases( |
| param0s: readonly number[], |
| param1s: readonly number[], |
| param2s: readonly number[], |
| filter: IntervalFilter, |
| ...ops: ScalarTripleToInterval[] |
| ): Case[] { |
| return cartesianProduct(param0s, param1s, param2s).reduce((cases, e) => { |
| const c = this.makeScalarTripleToIntervalCase(e[0], e[1], e[2], filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and the interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param the param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| private makeVectorToIntervalCase( |
| param: readonly number[], |
| filter: IntervalFilter, |
| ...ops: VectorToInterval[] |
| ): Case | undefined { |
| param = param.map(this.quantize); |
| |
| const intervals = ops.map(o => o(param)); |
| if (filter === 'finite' && intervals.some(i => !i.isFinite())) { |
| return undefined; |
| } |
| return { |
| input: [toVector(param, this.scalarBuilder)], |
| expected: anyOf(...intervals), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param params array of inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateVectorToIntervalCases( |
| params: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: VectorToInterval[] |
| ): Case[] { |
| return params.reduce((cases, e) => { |
| const c = this.makeVectorToIntervalCase(e, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and the interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param0 the first param to pass in |
| * @param param1 the second param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| private makeVectorPairToIntervalCase( |
| param0: readonly number[], |
| param1: readonly number[], |
| filter: IntervalFilter, |
| ...ops: VectorPairToInterval[] |
| ): Case | undefined { |
| param0 = param0.map(this.quantize); |
| param1 = param1.map(this.quantize); |
| |
| const intervals = ops.map(o => o(param0, param1)); |
| if (filter === 'finite' && intervals.some(i => !i.isFinite())) { |
| return undefined; |
| } |
| return { |
| input: [toVector(param0, this.scalarBuilder), toVector(param1, this.scalarBuilder)], |
| expected: anyOf(...intervals), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param param0s array of inputs to try for the first input |
| * @param param1s array of inputs to try for the second input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateVectorPairToIntervalCases( |
| param0s: ROArrayArray<number>, |
| param1s: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: VectorPairToInterval[] |
| ): Case[] { |
| return cartesianProduct(param0s, param1s).reduce((cases, e) => { |
| const c = this.makeVectorPairToIntervalCase(e[0], e[1], filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the param and vector of intervals generator provided |
| * @param param the param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals. |
| */ |
| private makeVectorToVectorCase( |
| param: readonly number[], |
| filter: IntervalFilter, |
| ...ops: VectorToVector[] |
| ): Case | undefined { |
| param = param.map(this.quantize); |
| |
| const vectors = ops.map(o => o(param)); |
| if (filter === 'finite' && vectors.some(v => v.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [toVector(param, this.scalarBuilder)], |
| expected: anyOf(...vectors), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param params array of inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals. |
| */ |
| public generateVectorToVectorCases( |
| params: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: VectorToVector[] |
| ): Case[] { |
| return params.reduce((cases, e) => { |
| const c = this.makeVectorToVectorCase(e, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and the interval vector generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param scalar the scalar param to pass in |
| * @param vector the vector param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance intervals |
| */ |
| private makeScalarVectorToVectorCase( |
| scalar: number, |
| vector: readonly number[], |
| filter: IntervalFilter, |
| ...ops: ScalarVectorToVector[] |
| ): Case | undefined { |
| scalar = this.quantize(scalar); |
| vector = vector.map(this.quantize); |
| |
| const results = ops.map(o => o(scalar, vector)); |
| if (filter === 'finite' && results.some(r => r.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [this.scalarBuilder(scalar), toVector(vector, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param scalars array of scalar inputs to try |
| * @param vectors array of vector inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance intervals |
| */ |
| public generateScalarVectorToVectorCases( |
| scalars: readonly number[], |
| vectors: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: ScalarVectorToVector[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| scalars.forEach(scalar => { |
| vectors.forEach(vector => { |
| const c = this.makeScalarVectorToVectorCase(scalar, vector, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| return cases; |
| } |
| |
| /** |
| * @returns a Case for the params and the interval vector generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param vector the vector param to pass in |
| * @param scalar the scalar param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance intervals |
| */ |
| private makeVectorScalarToVectorCase( |
| vector: readonly number[], |
| scalar: number, |
| filter: IntervalFilter, |
| ...ops: VectorScalarToVector[] |
| ): Case | undefined { |
| vector = vector.map(this.quantize); |
| scalar = this.quantize(scalar); |
| |
| const results = ops.map(o => o(vector, scalar)); |
| if (filter === 'finite' && results.some(r => r.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [toVector(vector, this.scalarBuilder), this.scalarBuilder(scalar)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param vectors array of vector inputs to try |
| * @param scalars array of scalar inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance intervals |
| */ |
| public generateVectorScalarToVectorCases( |
| vectors: ROArrayArray<number>, |
| scalars: readonly number[], |
| filter: IntervalFilter, |
| ...ops: VectorScalarToVector[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| vectors.forEach(vector => { |
| scalars.forEach(scalar => { |
| const c = this.makeVectorScalarToVectorCase(vector, scalar, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| return cases; |
| } |
| |
| /** |
| * @returns a Case for the param and vector of intervals generator provided |
| * @param param0 the first param to pass in |
| * @param param1 the second param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals. |
| */ |
| private makeVectorPairToVectorCase( |
| param0: readonly number[], |
| param1: readonly number[], |
| filter: IntervalFilter, |
| ...ops: VectorPairToVector[] |
| ): Case | undefined { |
| param0 = param0.map(this.quantize); |
| param1 = param1.map(this.quantize); |
| const vectors = ops.map(o => o(param0, param1)); |
| if (filter === 'finite' && vectors.some(v => v.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [toVector(param0, this.scalarBuilder), toVector(param1, this.scalarBuilder)], |
| expected: anyOf(...vectors), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param param0s array of inputs to try for the first input |
| * @param param1s array of inputs to try for the second input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals. |
| */ |
| public generateVectorPairToVectorCases( |
| param0s: ROArrayArray<number>, |
| param1s: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: VectorPairToVector[] |
| ): Case[] { |
| return cartesianProduct(param0s, param1s).reduce((cases, e) => { |
| const c = this.makeVectorPairToVectorCase(e[0], e[1], filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and the component-wise interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param0 the first vector param to pass in |
| * @param param1 the second vector param to pass in |
| * @param param2 the scalar param to pass in |
| * @param filter what interval filtering to apply |
| * @param componentWiseOps callbacks that implement generating a component-wise acceptance interval, |
| * one component result at a time. |
| */ |
| private makeVectorPairScalarToVectorComponentWiseCase( |
| param0: readonly number[], |
| param1: readonly number[], |
| param2: number, |
| filter: IntervalFilter, |
| ...componentWiseOps: ScalarTripleToInterval[] |
| ): Case | undefined { |
| // Width of input vector |
| const width = param0.length; |
| assert(2 <= width && width <= 4, 'input vector width must between 2 and 4'); |
| assert(param1.length === width, 'two input vectors must have the same width'); |
| param0 = param0.map(this.quantize); |
| param1 = param1.map(this.quantize); |
| param2 = this.quantize(param2); |
| |
| // Call the component-wise interval generator and build the expectation FPVector |
| const results = componentWiseOps.map(o => { |
| return param0.map((el0, index) => o(el0, param1[index], param2)) as FPVector; |
| }); |
| if (filter === 'finite' && results.some(r => r.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [ |
| toVector(param0, this.scalarBuilder), |
| toVector(param1, this.scalarBuilder), |
| this.scalarBuilder(param2), |
| ], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param param0s array of first vector inputs to try |
| * @param param1s array of second vector inputs to try |
| * @param param2s array of scalar inputs to try |
| * @param filter what interval filtering to apply |
| * @param componentWiseOpscallbacks that implement generating a component-wise acceptance interval |
| */ |
| public generateVectorPairScalarToVectorComponentWiseCase( |
| param0s: ROArrayArray<number>, |
| param1s: ROArrayArray<number>, |
| param2s: readonly number[], |
| filter: IntervalFilter, |
| ...componentWiseOps: ScalarTripleToInterval[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| param0s.forEach(param0 => { |
| param1s.forEach(param1 => { |
| param2s.forEach(param2 => { |
| const c = this.makeVectorPairScalarToVectorComponentWiseCase( |
| param0, |
| param1, |
| param2, |
| filter, |
| ...componentWiseOps |
| ); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| }); |
| return cases; |
| } |
| |
| /** |
| * @returns a Case for the param and an array of interval generators provided |
| * @param param the param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| private makeMatrixToScalarCase( |
| param: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixToScalar[] |
| ): Case | undefined { |
| param = map2DArray(param, this.quantize); |
| |
| const results = ops.map(o => o(param)); |
| if (filter === 'finite' && results.some(e => !e.isFinite())) { |
| return undefined; |
| } |
| |
| return { |
| input: [toMatrix(param, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param params array of inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateMatrixToScalarCases( |
| params: ROArrayArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixToScalar[] |
| ): Case[] { |
| return params.reduce((cases, e) => { |
| const c = this.makeMatrixToScalarCase(e, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the param and an array of interval generators provided |
| * @param param the param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| private makeMatrixToMatrixCase( |
| param: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixToMatrix[] |
| ): Case | undefined { |
| param = map2DArray(param, this.quantize); |
| |
| const results = ops.map(o => o(param)); |
| if (filter === 'finite' && results.some(m => m.some(c => c.some(r => !r.isFinite())))) { |
| return undefined; |
| } |
| |
| return { |
| input: [toMatrix(param, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param params array of inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| public generateMatrixToMatrixCases( |
| params: ROArrayArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixToMatrix[] |
| ): Case[] { |
| return params.reduce((cases, e) => { |
| const c = this.makeMatrixToMatrixCase(e, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and matrix of intervals generator provided |
| * @param param0 the first param to pass in |
| * @param param1 the second param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| private makeMatrixPairToMatrixCase( |
| param0: ROArrayArray<number>, |
| param1: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixPairToMatrix[] |
| ): Case | undefined { |
| param0 = map2DArray(param0, this.quantize); |
| param1 = map2DArray(param1, this.quantize); |
| const results = ops.map(o => o(param0, param1)); |
| if (filter === 'finite' && results.some(m => m.some(c => c.some(r => !r.isFinite())))) { |
| return undefined; |
| } |
| return { |
| input: [toMatrix(param0, this.scalarBuilder), toMatrix(param1, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param param0s array of inputs to try for the first input |
| * @param param1s array of inputs to try for the second input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| public generateMatrixPairToMatrixCases( |
| param0s: ROArrayArrayArray<number>, |
| param1s: ROArrayArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixPairToMatrix[] |
| ): Case[] { |
| return cartesianProduct(param0s, param1s).reduce((cases, e) => { |
| const c = this.makeMatrixPairToMatrixCase(e[0], e[1], filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| /** |
| * @returns a Case for the params and matrix of intervals generator provided |
| * @param mat the matrix param to pass in |
| * @param scalar the scalar to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| private makeMatrixScalarToMatrixCase( |
| mat: ROArrayArray<number>, |
| scalar: number, |
| filter: IntervalFilter, |
| ...ops: MatrixScalarToMatrix[] |
| ): Case | undefined { |
| mat = map2DArray(mat, this.quantize); |
| scalar = this.quantize(scalar); |
| |
| const results = ops.map(o => o(mat, scalar)); |
| if (filter === 'finite' && results.some(m => m.some(c => c.some(r => !r.isFinite())))) { |
| return undefined; |
| } |
| return { |
| input: [toMatrix(mat, this.scalarBuilder), this.scalarBuilder(scalar)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param mats array of inputs to try for the matrix input |
| * @param scalars array of inputs to try for the scalar input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| public generateMatrixScalarToMatrixCases( |
| mats: ROArrayArrayArray<number>, |
| scalars: readonly number[], |
| filter: IntervalFilter, |
| ...ops: MatrixScalarToMatrix[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| mats.forEach(mat => { |
| scalars.forEach(scalar => { |
| const c = this.makeMatrixScalarToMatrixCase(mat, scalar, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| return cases; |
| } |
| |
| /** |
| * @returns a Case for the params and matrix of intervals generator provided |
| * @param scalar the scalar to pass in |
| * @param mat the matrix param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| private makeScalarMatrixToMatrixCase( |
| scalar: number, |
| mat: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: ScalarMatrixToMatrix[] |
| ): Case | undefined { |
| scalar = this.quantize(scalar); |
| mat = map2DArray(mat, this.quantize); |
| |
| const results = ops.map(o => o(scalar, mat)); |
| if (filter === 'finite' && results.some(m => m.some(c => c.some(r => !r.isFinite())))) { |
| return undefined; |
| } |
| return { |
| input: [this.scalarBuilder(scalar), toMatrix(mat, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param scalars array of inputs to try for the scalar input |
| * @param mats array of inputs to try for the matrix input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a matrix of acceptance |
| * intervals |
| */ |
| public generateScalarMatrixToMatrixCases( |
| scalars: readonly number[], |
| mats: ROArrayArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: ScalarMatrixToMatrix[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| mats.forEach(mat => { |
| scalars.forEach(scalar => { |
| const c = this.makeScalarMatrixToMatrixCase(scalar, mat, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| return cases; |
| } |
| |
| /** |
| * @returns a Case for the params and the vector of intervals generator provided |
| * @param mat the matrix param to pass in |
| * @param vec the vector to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals |
| */ |
| private makeMatrixVectorToVectorCase( |
| mat: ROArrayArray<number>, |
| vec: readonly number[], |
| filter: IntervalFilter, |
| ...ops: MatrixVectorToVector[] |
| ): Case | undefined { |
| mat = map2DArray(mat, this.quantize); |
| vec = vec.map(this.quantize); |
| |
| const results = ops.map(o => o(mat, vec)); |
| if (filter === 'finite' && results.some(v => v.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [toMatrix(mat, this.scalarBuilder), toVector(vec, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param mats array of inputs to try for the matrix input |
| * @param vecs array of inputs to try for the vector input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals |
| */ |
| public generateMatrixVectorToVectorCases( |
| mats: ROArrayArrayArray<number>, |
| vecs: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: MatrixVectorToVector[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| mats.forEach(mat => { |
| vecs.forEach(vec => { |
| const c = this.makeMatrixVectorToVectorCase(mat, vec, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| return cases; |
| } |
| |
| /** |
| * @returns a Case for the params and the vector of intervals generator provided |
| * @param vec the vector to pass in |
| * @param mat the matrix param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals |
| */ |
| private makeVectorMatrixToVectorCase( |
| vec: readonly number[], |
| mat: ROArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: VectorMatrixToVector[] |
| ): Case | undefined { |
| vec = vec.map(this.quantize); |
| mat = map2DArray(mat, this.quantize); |
| |
| const results = ops.map(o => o(vec, mat)); |
| if (filter === 'finite' && results.some(v => v.some(e => !e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: [toVector(vec, this.scalarBuilder), toMatrix(mat, this.scalarBuilder)], |
| expected: anyOf(...results), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param vecs array of inputs to try for the vector input |
| * @param mats array of inputs to try for the matrix input |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating a vector of acceptance |
| * intervals |
| */ |
| public generateVectorMatrixToVectorCases( |
| vecs: ROArrayArray<number>, |
| mats: ROArrayArrayArray<number>, |
| filter: IntervalFilter, |
| ...ops: VectorMatrixToVector[] |
| ): Case[] { |
| // Cannot use cartesianProduct here, due to heterogeneous types |
| const cases: Case[] = []; |
| vecs.forEach(vec => { |
| mats.forEach(mat => { |
| const c = this.makeVectorMatrixToVectorCase(vec, mat, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| }); |
| }); |
| return cases; |
| } |
| |
| // Framework - Intervals |
| |
| /** |
| * Converts a point to an acceptance interval, using a specific function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * op.extrema is invoked before this point in the call stack. |
| * op.domain is tested before this point in the call stack. |
| * |
| * @param n value to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| private roundAndFlushScalarToInterval(n: number, op: ScalarToIntervalOp) { |
| assert(!Number.isNaN(n), `flush not defined for NaN`); |
| const values = this.correctlyRounded(n); |
| const inputs = this.addFlushedIfNeeded(values); |
| |
| if (op.domain !== undefined) { |
| // Cannot invoke op.domain() directly in the .some, because the narrowing doesn't propegate. |
| const domain = op.domain(); |
| if (inputs.some(i => !domain.contains(i))) { |
| return this.constants().unboundedInterval; |
| } |
| } |
| |
| const results = new Set<FPInterval>(inputs.map(op.impl)); |
| return this.spanIntervals(...results); |
| } |
| |
| /** |
| * Converts a pair to an acceptance interval, using a specific function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * All unique combinations of x & y are run. |
| * op.extrema is invoked before this point in the call stack. |
| * op.domain is tested before this point in the call stack. |
| * |
| * @param x first param to flush & round then invoke op.impl on |
| * @param y second param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| private roundAndFlushScalarPairToInterval( |
| x: number, |
| y: number, |
| op: ScalarPairToIntervalOp |
| ): FPInterval { |
| assert(!Number.isNaN(x), `flush not defined for NaN`); |
| assert(!Number.isNaN(y), `flush not defined for NaN`); |
| |
| const x_values = this.correctlyRounded(x); |
| const y_values = this.correctlyRounded(y); |
| const x_inputs = this.addFlushedIfNeeded(x_values); |
| const y_inputs = this.addFlushedIfNeeded(y_values); |
| |
| if (op.domain !== undefined) { |
| // Cannot invoke op.domain() directly in the .some, because the narrowing doesn't propegate. |
| const domain = op.domain(); |
| |
| if (x_inputs.some(i => !domain.x.some(e => e.contains(i)))) { |
| return this.constants().unboundedInterval; |
| } |
| |
| if (y_inputs.some(j => !domain.y.some(e => e.contains(j)))) { |
| return this.constants().unboundedInterval; |
| } |
| } |
| |
| const intervals = new Set<FPInterval>(); |
| x_inputs.forEach(inner_x => { |
| y_inputs.forEach(inner_y => { |
| intervals.add(op.impl(inner_x, inner_y)); |
| }); |
| }); |
| return this.spanIntervals(...intervals); |
| } |
| |
| /** |
| * Converts a triplet to an acceptance interval, using a specific function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * All unique combinations of x, y & z are run. |
| * |
| * @param x first param to flush & round then invoke op.impl on |
| * @param y second param to flush & round then invoke op.impl on |
| * @param z third param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| private roundAndFlushScalarTripleToInterval( |
| x: number, |
| y: number, |
| z: number, |
| op: ScalarTripleToIntervalOp |
| ): FPInterval { |
| assert(!Number.isNaN(x), `flush not defined for NaN`); |
| assert(!Number.isNaN(y), `flush not defined for NaN`); |
| assert(!Number.isNaN(z), `flush not defined for NaN`); |
| const x_values = this.correctlyRounded(x); |
| const y_values = this.correctlyRounded(y); |
| const z_values = this.correctlyRounded(z); |
| const x_inputs = this.addFlushedIfNeeded(x_values); |
| const y_inputs = this.addFlushedIfNeeded(y_values); |
| const z_inputs = this.addFlushedIfNeeded(z_values); |
| const intervals = new Set<FPInterval>(); |
| // prettier-ignore |
| x_inputs.forEach(inner_x => { |
| y_inputs.forEach(inner_y => { |
| z_inputs.forEach(inner_z => { |
| intervals.add(op.impl(inner_x, inner_y, inner_z)); |
| }); |
| }); |
| }); |
| |
| return this.spanIntervals(...intervals); |
| } |
| |
| /** |
| * Converts a vector to an acceptance interval using a specific function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * |
| * @param x param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| private roundAndFlushVectorToInterval(x: readonly number[], op: VectorToIntervalOp): FPInterval { |
| assert( |
| x.every(e => !Number.isNaN(e)), |
| `flush not defined for NaN` |
| ); |
| |
| const x_rounded: ROArrayArray<number> = x.map(this.correctlyRounded); |
| const x_flushed: ROArrayArray<number> = x_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const x_inputs = cartesianProduct<number>(...x_flushed); |
| |
| const intervals = new Set<FPInterval>(); |
| x_inputs.forEach(inner_x => { |
| intervals.add(op.impl(inner_x)); |
| }); |
| return this.spanIntervals(...intervals); |
| } |
| |
| /** |
| * Converts a pair of vectors to an acceptance interval using a specific |
| * function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * All unique combinations of x & y are run. |
| * |
| * @param x first param to flush & round then invoke op.impl on |
| * @param y second param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| private roundAndFlushVectorPairToInterval( |
| x: readonly number[], |
| y: readonly number[], |
| op: VectorPairToIntervalOp |
| ): FPInterval { |
| assert( |
| x.every(e => !Number.isNaN(e)), |
| `flush not defined for NaN` |
| ); |
| assert( |
| y.every(e => !Number.isNaN(e)), |
| `flush not defined for NaN` |
| ); |
| |
| const x_rounded: ROArrayArray<number> = x.map(this.correctlyRounded); |
| const y_rounded: ROArrayArray<number> = y.map(this.correctlyRounded); |
| const x_flushed: ROArrayArray<number> = x_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const y_flushed: ROArrayArray<number> = y_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const x_inputs = cartesianProduct<number>(...x_flushed); |
| const y_inputs = cartesianProduct<number>(...y_flushed); |
| |
| const intervals = new Set<FPInterval>(); |
| x_inputs.forEach(inner_x => { |
| y_inputs.forEach(inner_y => { |
| intervals.add(op.impl(inner_x, inner_y)); |
| }); |
| }); |
| return this.spanIntervals(...intervals); |
| } |
| |
| /** |
| * Converts a vector to a vector of acceptance intervals using a specific |
| * function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * |
| * @param x param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a vector of spans for each outputs of op.impl |
| */ |
| private roundAndFlushVectorToVector(x: readonly number[], op: VectorToVectorOp): FPVector { |
| assert( |
| x.every(e => !Number.isNaN(e)), |
| `flush not defined for NaN` |
| ); |
| |
| const x_rounded: ROArrayArray<number> = x.map(this.correctlyRounded); |
| const x_flushed: ROArrayArray<number> = x_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const x_inputs = cartesianProduct<number>(...x_flushed); |
| |
| const interval_vectors = new Set<FPVector>(); |
| x_inputs.forEach(inner_x => { |
| interval_vectors.add(op.impl(inner_x)); |
| }); |
| |
| return this.spanVectors(...interval_vectors); |
| } |
| |
| /** |
| * Converts a pair of vectors to a vector of acceptance intervals using a |
| * specific function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * |
| * @param x first param to flush & round then invoke op.impl on |
| * @param y second param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a vector of spans for each output of op.impl |
| */ |
| private roundAndFlushVectorPairToVector( |
| x: readonly number[], |
| y: readonly number[], |
| op: VectorPairToVectorOp |
| ): FPVector { |
| assert( |
| x.every(e => !Number.isNaN(e)), |
| `flush not defined for NaN` |
| ); |
| assert( |
| y.every(e => !Number.isNaN(e)), |
| `flush not defined for NaN` |
| ); |
| |
| const x_rounded: ROArrayArray<number> = x.map(this.correctlyRounded); |
| const y_rounded: ROArrayArray<number> = y.map(this.correctlyRounded); |
| const x_flushed: ROArrayArray<number> = x_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const y_flushed: ROArrayArray<number> = y_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const x_inputs = cartesianProduct<number>(...x_flushed); |
| const y_inputs = cartesianProduct<number>(...y_flushed); |
| |
| const interval_vectors = new Set<FPVector>(); |
| x_inputs.forEach(inner_x => { |
| y_inputs.forEach(inner_y => { |
| interval_vectors.add(op.impl(inner_x, inner_y)); |
| }); |
| }); |
| |
| return this.spanVectors(...interval_vectors); |
| } |
| |
| /** |
| * Converts a matrix to a matrix of acceptance intervals using a specific |
| * function |
| * |
| * This handles correctly rounding and flushing inputs as needed. |
| * Duplicate inputs are pruned before invoking op.impl. |
| * |
| * @param m param to flush & round then invoke op.impl on |
| * @param op operation defining the function being run |
| * @returns a matrix of spans for each outputs of op.impl |
| */ |
| private roundAndFlushMatrixToMatrix(m: Array2D<number>, op: MatrixToMatrixOp): FPMatrix { |
| const num_cols = m.length; |
| const num_rows = m[0].length; |
| assert( |
| m.every(c => c.every(r => !Number.isNaN(r))), |
| `flush not defined for NaN` |
| ); |
| |
| const m_flat = flatten2DArray(m); |
| const m_rounded: ROArrayArray<number> = m_flat.map(this.correctlyRounded); |
| const m_flushed: ROArrayArray<number> = m_rounded.map(this.addFlushedIfNeeded.bind(this)); |
| const m_options: ROArrayArray<number> = cartesianProduct<number>(...m_flushed); |
| const m_inputs: ROArrayArrayArray<number> = m_options.map(e => |
| unflatten2DArray(e, num_cols, num_rows) |
| ); |
| |
| const interval_matrices = new Set<FPMatrix>(); |
| m_inputs.forEach(inner_m => { |
| interval_matrices.add(op.impl(inner_m)); |
| }); |
| |
| return this.spanMatrices(...interval_matrices); |
| } |
| |
| /** |
| * Calculate the acceptance interval for a unary function over an interval |
| * |
| * If the interval is actually a point, this just decays to |
| * roundAndFlushScalarToInterval. |
| * |
| * The provided domain interval may be adjusted if the operation defines an |
| * extrema function. |
| * |
| * @param x input domain interval |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| protected runScalarToIntervalOp(x: FPInterval, op: ScalarToIntervalOp): FPInterval { |
| if (!x.isFinite()) { |
| return this.constants().unboundedInterval; |
| } |
| |
| if (op.extrema !== undefined) { |
| x = op.extrema(x); |
| } |
| |
| const result = this.spanIntervals( |
| ...x.endpoints().map(b => this.roundAndFlushScalarToInterval(b, op)) |
| ); |
| return result.isFinite() ? result : this.constants().unboundedInterval; |
| } |
| |
| /** |
| * Calculate the acceptance interval for a binary function over an interval |
| * |
| * The provided domain intervals may be adjusted if the operation defines an |
| * extrema function. |
| * |
| * @param x first input domain interval |
| * @param y second input domain interval |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| protected runScalarPairToIntervalOp( |
| x: FPInterval, |
| y: FPInterval, |
| op: ScalarPairToIntervalOp |
| ): FPInterval { |
| if (!x.isFinite() || !y.isFinite()) { |
| return this.constants().unboundedInterval; |
| } |
| |
| if (op.extrema !== undefined) { |
| [x, y] = op.extrema(x, y); |
| } |
| |
| const outputs = new Set<FPInterval>(); |
| x.endpoints().forEach(inner_x => { |
| y.endpoints().forEach(inner_y => { |
| outputs.add(this.roundAndFlushScalarPairToInterval(inner_x, inner_y, op)); |
| }); |
| }); |
| |
| const result = this.spanIntervals(...outputs); |
| return result.isFinite() ? result : this.constants().unboundedInterval; |
| } |
| |
| /** |
| * Calculate the acceptance interval for a ternary function over an interval |
| * |
| * @param x first input domain interval |
| * @param y second input domain interval |
| * @param z third input domain interval |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| protected runScalarTripleToIntervalOp( |
| x: FPInterval, |
| y: FPInterval, |
| z: FPInterval, |
| op: ScalarTripleToIntervalOp |
| ): FPInterval { |
| if (!x.isFinite() || !y.isFinite() || !z.isFinite()) { |
| return this.constants().unboundedInterval; |
| } |
| |
| const outputs = new Set<FPInterval>(); |
| x.endpoints().forEach(inner_x => { |
| y.endpoints().forEach(inner_y => { |
| z.endpoints().forEach(inner_z => { |
| outputs.add(this.roundAndFlushScalarTripleToInterval(inner_x, inner_y, inner_z, op)); |
| }); |
| }); |
| }); |
| |
| const result = this.spanIntervals(...outputs); |
| return result.isFinite() ? result : this.constants().unboundedInterval; |
| } |
| |
| /** |
| * Calculate the acceptance interval for a vector function over given |
| * intervals |
| * |
| * @param x input domain intervals vector |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| protected runVectorToIntervalOp(x: FPVector, op: VectorToIntervalOp): FPInterval { |
| if (x.some(e => !e.isFinite())) { |
| return this.constants().unboundedInterval; |
| } |
| |
| const x_values = cartesianProduct<number>(...x.map(e => e.endpoints())); |
| |
| const outputs = new Set<FPInterval>(); |
| x_values.forEach(inner_x => { |
| outputs.add(this.roundAndFlushVectorToInterval(inner_x, op)); |
| }); |
| |
| const result = this.spanIntervals(...outputs); |
| return result.isFinite() ? result : this.constants().unboundedInterval; |
| } |
| |
| /** |
| * Calculate the acceptance interval for a vector pair function over given |
| * intervals |
| * |
| * @param x first input domain intervals vector |
| * @param y second input domain intervals vector |
| * @param op operation defining the function being run |
| * @returns a span over all the outputs of op.impl |
| */ |
| protected runVectorPairToIntervalOp( |
| x: FPVector, |
| y: FPVector, |
| op: VectorPairToIntervalOp |
| ): FPInterval { |
| if (x.some(e => !e.isFinite()) || y.some(e => !e.isFinite())) { |
| return this.constants().unboundedInterval; |
| } |
| |
| const x_values = cartesianProduct<number>(...x.map(e => e.endpoints())); |
| const y_values = cartesianProduct<number>(...y.map(e => e.endpoints())); |
| |
| const outputs = new Set<FPInterval>(); |
| x_values.forEach(inner_x => { |
| y_values.forEach(inner_y => { |
| outputs.add(this.roundAndFlushVectorPairToInterval(inner_x, inner_y, op)); |
| }); |
| }); |
| |
| const result = this.spanIntervals(...outputs); |
| return result.isFinite() ? result : this.constants().unboundedInterval; |
| } |
| |
| /** |
| * Calculate the vector of acceptance intervals for a pair of vector function |
| * over given intervals |
| * |
| * @param x input domain intervals vector |
| * @param op operation defining the function being run |
| * @returns a vector of spans over all the outputs of op.impl |
| */ |
| protected runVectorToVectorOp(x: FPVector, op: VectorToVectorOp): FPVector { |
| if (x.some(e => !e.isFinite())) { |
| return this.constants().unboundedVector[x.length]; |
| } |
| |
| const x_values = cartesianProduct<number>(...x.map(e => e.endpoints())); |
| |
| const outputs = new Set<FPVector>(); |
| x_values.forEach(inner_x => { |
| outputs.add(this.roundAndFlushVectorToVector(inner_x, op)); |
| }); |
| |
| const result = this.spanVectors(...outputs); |
| return result.every(e => e.isFinite()) |
| ? result |
| : this.constants().unboundedVector[result.length]; |
| } |
| |
| /** |
| * Calculate the vector of acceptance intervals by running a scalar operation |
| * component-wise over a vector. |
| * |
| * This is used for situations where a component-wise operation, like vector |
| * negation, is needed as part of an inherited accuracy, but the top-level |
| * operation test don't require an explicit vector definition of the function, |
| * due to the generated 'vectorize' tests being sufficient. |
| * |
| * @param x input domain intervals vector |
| * @param op scalar operation to be run component-wise |
| * @returns a vector of intervals with the outputs of op.impl |
| */ |
| protected runScalarToIntervalOpComponentWise(x: FPVector, op: ScalarToIntervalOp): FPVector { |
| return this.toVector(x.map(e => this.runScalarToIntervalOp(e, op))); |
| } |
| |
| /** |
| * Calculate the vector of acceptance intervals for a vector function over |
| * given intervals |
| * |
| * @param x first input domain intervals vector |
| * @param y second input domain intervals vector |
| * @param op operation defining the function being run |
| * @returns a vector of spans over all the outputs of op.impl |
| */ |
| protected runVectorPairToVectorOp(x: FPVector, y: FPVector, op: VectorPairToVectorOp): FPVector { |
| if (x.some(e => !e.isFinite()) || y.some(e => !e.isFinite())) { |
| return this.constants().unboundedVector[x.length]; |
| } |
| |
| const x_values = cartesianProduct<number>(...x.map(e => e.endpoints())); |
| const y_values = cartesianProduct<number>(...y.map(e => e.endpoints())); |
| |
| const outputs = new Set<FPVector>(); |
| x_values.forEach(inner_x => { |
| y_values.forEach(inner_y => { |
| outputs.add(this.roundAndFlushVectorPairToVector(inner_x, inner_y, op)); |
| }); |
| }); |
| |
| const result = this.spanVectors(...outputs); |
| return result.every(e => e.isFinite()) |
| ? result |
| : this.constants().unboundedVector[result.length]; |
| } |
| |
| /** |
| * Calculate the vector of acceptance intervals by running a scalar operation |
| * component-wise over a pair of vectors. |
| * |
| * This is used for situations where a component-wise operation, like vector |
| * subtraction, is needed as part of an inherited accuracy, but the top-level |
| * operation test don't require an explicit vector definition of the function, |
| * due to the generated 'vectorize' tests being sufficient. |
| * |
| * @param x first input domain intervals vector |
| * @param y second input domain intervals vector |
| * @param op scalar operation to be run component-wise |
| * @returns a vector of intervals with the outputs of op.impl |
| */ |
| protected runScalarPairToIntervalOpVectorComponentWise( |
| x: FPVector, |
| y: FPVector, |
| op: ScalarPairToIntervalOp |
| ): FPVector { |
| assert( |
| x.length === y.length, |
| `runScalarPairToIntervalOpVectorComponentWise requires vectors of the same dimensions` |
| ); |
| |
| return this.toVector( |
| x.map((i, idx) => { |
| return this.runScalarPairToIntervalOp(i, y[idx], op); |
| }) |
| ); |
| } |
| |
| /** |
| * Calculate the matrix of acceptance intervals for a pair of matrix function over |
| * given intervals |
| * |
| * @param m input domain intervals matrix |
| * @param op operation defining the function being run |
| * @returns a matrix of spans over all the outputs of op.impl |
| */ |
| protected runMatrixToMatrixOp(m: FPMatrix, op: MatrixToMatrixOp): FPMatrix { |
| const num_cols = m.length; |
| const num_rows = m[0].length; |
| |
| // Do not check for OOB inputs and exit early here, because the shape of |
| // the output matrix may be determined by the operation being run, |
| // i.e. transpose. |
| |
| const m_flat: readonly FPInterval[] = flatten2DArray(m); |
| const m_values: ROArrayArray<number> = cartesianProduct<number>( |
| ...m_flat.map(e => e.endpoints()) |
| ); |
| |
| const outputs = new Set<FPMatrix>(); |
| m_values.forEach(inner_m => { |
| const unflat_m = unflatten2DArray(inner_m, num_cols, num_rows); |
| outputs.add(this.roundAndFlushMatrixToMatrix(unflat_m, op)); |
| }); |
| |
| const result = this.spanMatrices(...outputs); |
| const result_cols = result.length; |
| const result_rows = result[0].length; |
| |
| // FPMatrix has to be coerced to ROArrayArray<FPInterval> to use .every. This should |
| // always be safe, since FPMatrix are defined as fixed length array of |
| // arrays. |
| return (result as ROArrayArray<FPInterval>).every(c => c.every(r => r.isFinite())) |
| ? result |
| : this.constants().unboundedMatrix[result_cols][result_rows]; |
| } |
| |
| /** |
| * Calculate the Matrix of acceptance intervals by running a scalar operation |
| * component-wise over a scalar and a matrix. |
| * |
| * An example of this is performing constant scaling. |
| * |
| * @param i scalar input |
| * @param m matrix input |
| * @param op scalar operation to be run component-wise |
| * @returns a matrix of intervals with the outputs of op.impl |
| */ |
| protected runScalarPairToIntervalOpScalarMatrixComponentWise( |
| i: FPInterval, |
| m: FPMatrix, |
| op: ScalarPairToIntervalOp |
| ): FPMatrix { |
| const cols = m.length; |
| const rows = m[0].length; |
| return this.toMatrix( |
| unflatten2DArray( |
| flatten2DArray(m).map(e => this.runScalarPairToIntervalOp(i, e, op)), |
| cols, |
| rows |
| ) |
| ); |
| } |
| |
| /** |
| * Calculate the Matrix of acceptance intervals by running a scalar operation |
| * component-wise over a pair of matrices. |
| * |
| * An example of this is performing matrix addition. |
| * |
| * @param x first input domain intervals matrix |
| * @param y second input domain intervals matrix |
| * @param op scalar operation to be run component-wise |
| * @returns a matrix of intervals with the outputs of op.impl |
| */ |
| protected runScalarPairToIntervalOpMatrixMatrixComponentWise( |
| x: FPMatrix, |
| y: FPMatrix, |
| op: ScalarPairToIntervalOp |
| ): FPMatrix { |
| assert( |
| x.length === y.length && x[0].length === y[0].length, |
| `runScalarPairToIntervalOpMatrixMatrixComponentWise requires matrices of the same dimensions` |
| ); |
| |
| const cols = x.length; |
| const rows = x[0].length; |
| const flat_x = flatten2DArray(x); |
| const flat_y = flatten2DArray(y); |
| |
| return this.toMatrix( |
| unflatten2DArray( |
| flat_x.map((i, idx) => { |
| return this.runScalarPairToIntervalOp(i, flat_y[idx], op); |
| }), |
| cols, |
| rows |
| ) |
| ); |
| } |
| |
| // API - Fundamental Error Intervals |
| |
| /** @returns a ScalarToIntervalOp for [n - error_range, n + error_range] */ |
| private AbsoluteErrorIntervalOp(error_range: number): ScalarToIntervalOp { |
| const op: ScalarToIntervalOp = { |
| impl: (_: number) => { |
| return this.constants().unboundedInterval; |
| }, |
| }; |
| |
| assert( |
| error_range >= 0, |
| `absoluteErrorInterval must have non-negative error range, get ${error_range}` |
| ); |
| |
| if (this.isFinite(error_range)) { |
| op.impl = (n: number) => { |
| assert(!Number.isNaN(n), `absolute error not defined for NaN`); |
| // Return anyInterval if given center n is infinity. |
| if (!this.isFinite(n)) { |
| return this.constants().unboundedInterval; |
| } |
| return this.toInterval([n - error_range, n + error_range]); |
| }; |
| } |
| |
| return op; |
| } |
| |
| protected absoluteErrorIntervalImpl(n: number, error_range: number): FPInterval { |
| error_range = Math.abs(error_range); |
| return this.runScalarToIntervalOp( |
| this.toInterval(n), |
| this.AbsoluteErrorIntervalOp(error_range) |
| ); |
| } |
| |
| /** @returns an interval of the absolute error around the point */ |
| public abstract readonly absoluteErrorInterval: (n: number, error_range: number) => FPInterval; |
| |
| /** |
| * Defines a ScalarToIntervalOp for an interval of the correctly rounded values |
| * around the point |
| */ |
| private readonly CorrectlyRoundedIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number) => { |
| assert(!Number.isNaN(n), `absolute not defined for NaN`); |
| return this.toInterval(n); |
| }, |
| }; |
| |
| protected correctlyRoundedIntervalImpl(n: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.CorrectlyRoundedIntervalOp); |
| } |
| |
| /** @returns an interval of the correctly rounded values around the point */ |
| public abstract readonly correctlyRoundedInterval: (n: number | FPInterval) => FPInterval; |
| |
| protected correctlyRoundedMatrixImpl(m: Array2D<number>): FPMatrix { |
| return this.toMatrix(map2DArray(m, this.correctlyRoundedInterval)); |
| } |
| |
| /** @returns a matrix of correctly rounded intervals for the provided matrix */ |
| public abstract readonly correctlyRoundedMatrix: (m: Array2D<number>) => FPMatrix; |
| |
| /** @returns a ScalarToIntervalOp for [n - numULP * ULP(n), n + numULP * ULP(n)] */ |
| private ULPIntervalOp(numULP: number): ScalarToIntervalOp { |
| const op: ScalarToIntervalOp = { |
| impl: (_: number) => { |
| return this.constants().unboundedInterval; |
| }, |
| }; |
| |
| if (this.isFinite(numULP)) { |
| op.impl = (n: number) => { |
| assert(!Number.isNaN(n), `ULP error not defined for NaN`); |
| |
| const ulp = this.oneULP(n); |
| const begin = n - numULP * ulp; |
| const end = n + numULP * ulp; |
| |
| return this.toInterval([ |
| Math.min(begin, this.flushSubnormal(begin)), |
| Math.max(end, this.flushSubnormal(end)), |
| ]); |
| }; |
| } |
| |
| return op; |
| } |
| |
| protected ulpIntervalImpl(n: number, numULP: number): FPInterval { |
| numULP = Math.abs(numULP); |
| return this.runScalarToIntervalOp(this.toInterval(n), this.ULPIntervalOp(numULP)); |
| } |
| |
| /** @returns an interval of N * ULP around the point */ |
| public abstract readonly ulpInterval: (n: number, numULP: number) => FPInterval; |
| |
| // API - Acceptance Intervals |
| |
| private readonly AbsIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number) => { |
| return this.correctlyRoundedInterval(Math.abs(n)); |
| }, |
| }; |
| |
| protected absIntervalImpl(n: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.AbsIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval for abs(n) */ |
| public abstract readonly absInterval: (n: number | FPInterval) => FPInterval; |
| |
| // This op is implemented differently for f32 and f16. |
| private readonly AcosIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number) => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| // acos(n) = atan2(sqrt(1.0 - n * n), n) or a polynomial approximation with absolute error |
| const y = this.sqrtInterval(this.subtractionInterval(1, this.multiplicationInterval(n, n))); |
| const approx_abs_error = this.kind === 'f32' ? 6.77e-5 : 3.91e-3; |
| return this.spanIntervals( |
| this.atan2Interval(y, n), |
| this.absoluteErrorInterval(Math.acos(n), approx_abs_error) |
| ); |
| }, |
| domain: () => { |
| return this.constants().negOneToOneInterval; |
| }, |
| }; |
| |
| protected acosIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.AcosIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval for acos(n) */ |
| public abstract readonly acosInterval: (n: number) => FPInterval; |
| |
| private readonly AcoshAlternativeIntervalOp: ScalarToIntervalOp = { |
| impl: (x: number): FPInterval => { |
| // acosh(x) = log(x + sqrt((x + 1.0f) * (x - 1.0))) |
| const inner_value = this.multiplicationInterval( |
| this.additionInterval(x, 1.0), |
| this.subtractionInterval(x, 1.0) |
| ); |
| const sqrt_value = this.sqrtInterval(inner_value); |
| return this.logInterval(this.additionInterval(x, sqrt_value)); |
| }, |
| }; |
| |
| protected acoshAlternativeIntervalImpl(x: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(x), this.AcoshAlternativeIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of acosh(x) using log(x + sqrt((x + 1.0f) * (x - 1.0))) */ |
| public abstract readonly acoshAlternativeInterval: (x: number | FPInterval) => FPInterval; |
| |
| private readonly AcoshPrimaryIntervalOp: ScalarToIntervalOp = { |
| impl: (x: number): FPInterval => { |
| // acosh(x) = log(x + sqrt(x * x - 1.0)) |
| const inner_value = this.subtractionInterval(this.multiplicationInterval(x, x), 1.0); |
| const sqrt_value = this.sqrtInterval(inner_value); |
| return this.logInterval(this.additionInterval(x, sqrt_value)); |
| }, |
| }; |
| |
| protected acoshPrimaryIntervalImpl(x: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(x), this.AcoshPrimaryIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of acosh(x) using log(x + sqrt(x * x - 1.0)) */ |
| protected abstract acoshPrimaryInterval: (x: number | FPInterval) => FPInterval; |
| |
| /** All acceptance interval functions for acosh(x) */ |
| public abstract readonly acoshIntervals: ScalarToInterval[]; |
| |
| private readonly AdditionIntervalOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| const sum = x + y; |
| const large_val = Math.abs(x) > Math.abs(y) ? x : y; |
| const small_val = Math.abs(x) > Math.abs(y) ? y : x; |
| return this.correctlyRoundedIntervalWithUnboundedPrecisionForAddition( |
| sum, |
| large_val, |
| small_val |
| ); |
| }, |
| }; |
| |
| protected additionIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.AdditionIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x + y, when x and y are both scalars */ |
| public abstract readonly additionInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| protected additionMatrixMatrixIntervalImpl(x: Array2D<number>, y: Array2D<number>): FPMatrix { |
| return this.runScalarPairToIntervalOpMatrixMatrixComponentWise( |
| this.toMatrix(x), |
| this.toMatrix(y), |
| this.AdditionIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x + y, when x and y are matrices */ |
| public abstract readonly additionMatrixMatrixInterval: ( |
| x: Array2D<number>, |
| y: Array2D<number> |
| ) => FPMatrix; |
| |
| // This op is implemented differently for f32 and f16. |
| private readonly AsinIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number) => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| // asin(n) = atan2(n, sqrt(1.0 - n * n)) or a polynomial approximation with absolute error |
| const x = this.sqrtInterval(this.subtractionInterval(1, this.multiplicationInterval(n, n))); |
| const approx_abs_error = this.kind === 'f32' ? 6.81e-5 : 3.91e-3; |
| return this.spanIntervals( |
| this.atan2Interval(n, x), |
| this.absoluteErrorInterval(Math.asin(n), approx_abs_error) |
| ); |
| }, |
| domain: () => { |
| return this.constants().negOneToOneInterval; |
| }, |
| }; |
| |
| /** Calculate an acceptance interval for asin(n) */ |
| protected asinIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.AsinIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval for asin(n) */ |
| public abstract readonly asinInterval: (n: number) => FPInterval; |
| |
| private readonly AsinhIntervalOp: ScalarToIntervalOp = { |
| impl: (x: number): FPInterval => { |
| // asinh(x) = log(x + sqrt(x * x + 1.0)) |
| const inner_value = this.additionInterval(this.multiplicationInterval(x, x), 1.0); |
| const sqrt_value = this.sqrtInterval(inner_value); |
| return this.logInterval(this.additionInterval(x, sqrt_value)); |
| }, |
| }; |
| |
| protected asinhIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.AsinhIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of asinh(x) */ |
| public abstract readonly asinhInterval: (n: number) => FPInterval; |
| |
| private readonly AtanIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const ulp_error = this.kind === 'f32' ? 4096 : 5; |
| return this.ulpInterval(Math.atan(n), ulp_error); |
| }, |
| }; |
| |
| /** Calculate an acceptance interval of atan(x) */ |
| protected atanIntervalImpl(n: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.AtanIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of atan(x) */ |
| public abstract readonly atanInterval: (n: number | FPInterval) => FPInterval; |
| |
| // This op is implemented differently for f32 and f16. |
| private Atan2IntervalOpBuilder(): ScalarPairToIntervalOp { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const constants = this.constants(); |
| // For atan2, the params are labelled (y, x), not (x, y), so domain.x is first parameter (y), |
| // and domain.y is the second parameter (x). |
| // The first param must be finite and normal. |
| const domain_x = [ |
| this.toInterval([constants.negative.min, constants.negative.max]), |
| this.toInterval([constants.positive.min, constants.positive.max]), |
| ]; |
| // inherited from division |
| const domain_y = |
| this.kind === 'f32' |
| ? [this.toInterval([-(2 ** 126), -(2 ** -126)]), this.toInterval([2 ** -126, 2 ** 126])] |
| : [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])]; |
| const ulp_error = this.kind === 'f32' ? 4096 : 5; |
| return { |
| impl: (y: number, x: number): FPInterval => { |
| // Accurate result in f64 |
| let atan_yx = Math.atan(y / x); |
| // Offset by +/-pi according to the definition. Use pi value in f64 because we are |
| // handling accurate result. |
| if (x < 0) { |
| // x < 0, y > 0, result is atan(y/x) + π |
| if (y > 0) { |
| atan_yx = atan_yx + kValue.f64.positive.pi.whole; |
| } else { |
| // x < 0, y < 0, result is atan(y/x) - π |
| atan_yx = atan_yx - kValue.f64.positive.pi.whole; |
| } |
| } |
| |
| return this.ulpInterval(atan_yx, ulp_error); |
| }, |
| extrema: (y: FPInterval, x: FPInterval): [FPInterval, FPInterval] => { |
| // There is discontinuity, which generates an unbounded result, at y/x = 0 that will dominate the accuracy |
| if (y.contains(0)) { |
| if (x.contains(0)) { |
| return [this.toInterval(0), this.toInterval(0)]; |
| } |
| return [this.toInterval(0), x]; |
| } |
| return [y, x]; |
| }, |
| domain: () => { |
| return { x: domain_x, y: domain_y }; |
| }, |
| }; |
| } |
| |
| protected atan2IntervalImpl(y: number | FPInterval, x: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(y), |
| this.toInterval(x), |
| this.Atan2IntervalOpBuilder() |
| ); |
| } |
| |
| /** Calculate an acceptance interval of atan2(y, x) */ |
| public abstract readonly atan2Interval: ( |
| y: number | FPInterval, |
| x: number | FPInterval |
| ) => FPInterval; |
| |
| private readonly AtanhIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number) => { |
| // atanh(x) = log((1.0 + x) / (1.0 - x)) * 0.5 |
| const numerator = this.additionInterval(1.0, n); |
| const denominator = this.subtractionInterval(1.0, n); |
| const log_interval = this.logInterval(this.divisionInterval(numerator, denominator)); |
| return this.multiplicationInterval(log_interval, 0.5); |
| }, |
| }; |
| |
| protected atanhIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.AtanhIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of atanh(x) */ |
| public abstract readonly atanhInterval: (n: number) => FPInterval; |
| |
| private readonly CeilIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.correctlyRoundedInterval(Math.ceil(n)); |
| }, |
| }; |
| |
| protected ceilIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.CeilIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of ceil(x) */ |
| public abstract readonly ceilInterval: (n: number) => FPInterval; |
| |
| private readonly ClampMedianIntervalOp: ScalarTripleToIntervalOp = { |
| impl: (x: number, y: number, z: number): FPInterval => { |
| return this.correctlyRoundedInterval( |
| // Default sort is string sort, so have to implement numeric comparison. |
| // Cannot use the b-a one-liner, because that assumes no infinities. |
| [x, y, z].sort((a, b) => { |
| if (a < b) { |
| return -1; |
| } |
| if (a > b) { |
| return 1; |
| } |
| return 0; |
| })[1] |
| ); |
| }, |
| }; |
| |
| protected clampMedianIntervalImpl( |
| x: number | FPInterval, |
| y: number | FPInterval, |
| z: number | FPInterval |
| ): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.toInterval(z), |
| this.ClampMedianIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of clamp(x, y, z) via median(x, y, z) */ |
| public abstract readonly clampMedianInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval, |
| z: number | FPInterval |
| ) => FPInterval; |
| |
| private readonly ClampMinMaxIntervalOp: ScalarTripleToIntervalOp = { |
| impl: (x: number, low: number, high: number): FPInterval => { |
| return this.minInterval(this.maxInterval(x, low), high); |
| }, |
| }; |
| |
| protected clampMinMaxIntervalImpl( |
| x: number | FPInterval, |
| low: number | FPInterval, |
| high: number | FPInterval |
| ): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(low), |
| this.toInterval(high), |
| this.ClampMinMaxIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of clamp(x, high, low) via min(max(x, low), high) */ |
| public abstract readonly clampMinMaxInterval: ( |
| x: number | FPInterval, |
| low: number | FPInterval, |
| high: number | FPInterval |
| ) => FPInterval; |
| |
| /** All acceptance interval functions for clamp(x, y, z) */ |
| public abstract readonly clampIntervals: ScalarTripleToInterval[]; |
| |
| private readonly CosIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7; |
| return this.absoluteErrorInterval(Math.cos(n), abs_error); |
| }, |
| domain: () => { |
| return this.constants().negPiToPiInterval; |
| }, |
| }; |
| |
| protected cosIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.CosIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of cos(x) */ |
| public abstract readonly cosInterval: (n: number) => FPInterval; |
| |
| private readonly CoshIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| // cosh(x) = (exp(x) + exp(-x)) * 0.5 |
| const minus_n = this.negationInterval(n); |
| return this.multiplicationInterval( |
| this.additionInterval(this.expInterval(n), this.expInterval(minus_n)), |
| 0.5 |
| ); |
| }, |
| }; |
| |
| protected coshIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.CoshIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of cosh(x) */ |
| public abstract readonly coshInterval: (n: number) => FPInterval; |
| |
| private readonly CrossIntervalOp: VectorPairToVectorOp = { |
| impl: (x: readonly number[], y: readonly number[]): FPVector => { |
| assert(x.length === 3, `CrossIntervalOp received x with ${x.length} instead of 3`); |
| assert(y.length === 3, `CrossIntervalOp received y with ${y.length} instead of 3`); |
| |
| // cross(x, y) = r, where |
| // r[0] = x[1] * y[2] - x[2] * y[1] |
| // r[1] = x[2] * y[0] - x[0] * y[2] |
| // r[2] = x[0] * y[1] - x[1] * y[0] |
| |
| const r0 = this.subtractionInterval( |
| this.multiplicationInterval(x[1], y[2]), |
| this.multiplicationInterval(x[2], y[1]) |
| ); |
| const r1 = this.subtractionInterval( |
| this.multiplicationInterval(x[2], y[0]), |
| this.multiplicationInterval(x[0], y[2]) |
| ); |
| const r2 = this.subtractionInterval( |
| this.multiplicationInterval(x[0], y[1]), |
| this.multiplicationInterval(x[1], y[0]) |
| ); |
| |
| if (r0.isFinite() && r1.isFinite() && r2.isFinite()) { |
| return [r0, r1, r2]; |
| } |
| return this.constants().unboundedVector[3]; |
| }, |
| }; |
| |
| protected crossIntervalImpl(x: readonly number[], y: readonly number[]): FPVector { |
| assert(x.length === 3, `Cross is only defined for vec3`); |
| assert(y.length === 3, `Cross is only defined for vec3`); |
| return this.runVectorPairToVectorOp(this.toVector(x), this.toVector(y), this.CrossIntervalOp); |
| } |
| |
| /** Calculate a vector of acceptance intervals for cross(x, y) */ |
| public abstract readonly crossInterval: (x: readonly number[], y: readonly number[]) => FPVector; |
| |
| private readonly DegreesIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.multiplicationInterval(n, 57.295779513082322865); |
| }, |
| }; |
| |
| protected degreesIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.DegreesIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of degrees(x) */ |
| public abstract readonly degreesInterval: (n: number) => FPInterval; |
| |
| /** |
| * Calculate the minor of a NxN matrix. |
| * |
| * The ijth minor of a square matrix, is the N-1xN-1 matrix created by removing |
| * the ith column and jth row from the original matrix. |
| */ |
| private minorNxN(m: Array2D<number>, col: number, row: number): Array2D<number> { |
| const dim = m.length; |
| assert(m.length === m[0].length, `minorMatrix is only defined for square matrices`); |
| assert(col >= 0 && col < dim, `col ${col} needs be in [0, # of columns '${dim}')`); |
| assert(row >= 0 && row < dim, `row ${row} needs be in [0, # of rows '${dim}')`); |
| |
| const result: number[][] = [...Array(dim - 1)].map(_ => [...Array(dim - 1)]); |
| |
| const col_indices: readonly number[] = [...Array(dim).keys()].filter(e => e !== col); |
| const row_indices: readonly number[] = [...Array(dim).keys()].filter(e => e !== row); |
| |
| col_indices.forEach((c, i) => { |
| row_indices.forEach((r, j) => { |
| result[i][j] = m[c][r]; |
| }); |
| }); |
| return result; |
| } |
| |
| /** Calculate an acceptance interval for determinant(m), where m is a 2x2 matrix */ |
| private determinant2x2Interval(m: Array2D<number>): FPInterval { |
| assert( |
| m.length === m[0].length && m.length === 2, |
| `determinant2x2Interval called on non-2x2 matrix` |
| ); |
| return this.subtractionInterval( |
| this.multiplicationInterval(m[0][0], m[1][1]), |
| this.multiplicationInterval(m[0][1], m[1][0]) |
| ); |
| } |
| |
| /** Calculate an acceptance interval for determinant(m), where m is a 3x3 matrix */ |
| private determinant3x3Interval(m: Array2D<number>): FPInterval { |
| assert( |
| m.length === m[0].length && m.length === 3, |
| `determinant3x3Interval called on non-3x3 matrix` |
| ); |
| |
| // M is a 3x3 matrix |
| // det(M) is A + B + C, where A, B, C are three elements in a row/column times |
| // their own co-factor. |
| // (The co-factor is the determinant of the minor of that position with the |
| // appropriate +/-) |
| // For simplicity sake A, B, C are calculated as the elements of the first |
| // column |
| const A = this.multiplicationInterval( |
| m[0][0], |
| this.determinant2x2Interval(this.minorNxN(m, 0, 0)) |
| ); |
| const B = this.multiplicationInterval( |
| -m[0][1], |
| this.determinant2x2Interval(this.minorNxN(m, 0, 1)) |
| ); |
| const C = this.multiplicationInterval( |
| m[0][2], |
| this.determinant2x2Interval(this.minorNxN(m, 0, 2)) |
| ); |
| |
| // Need to calculate permutations, since for fp addition is not associative, |
| // so A + B + C is not guaranteed to equal B + C + A, etc. |
| const permutations: ROArrayArray<FPInterval> = calculatePermutations([A, B, C]); |
| return this.spanIntervals( |
| ...permutations.map(p => |
| p.reduce((prev: FPInterval, cur: FPInterval) => this.additionInterval(prev, cur)) |
| ) |
| ); |
| } |
| |
| /** Calculate an acceptance interval for determinant(m), where m is a 4x4 matrix */ |
| private determinant4x4Interval(m: Array2D<number>): FPInterval { |
| assert( |
| m.length === m[0].length && m.length === 4, |
| `determinant3x3Interval called on non-4x4 matrix` |
| ); |
| |
| // M is a 4x4 matrix |
| // det(M) is A + B + C + D, where A, B, C, D are four elements in a row/column |
| // times their own co-factor. |
| // (The co-factor is the determinant of the minor of that position with the |
| // appropriate +/-) |
| // For simplicity sake A, B, C, D are calculated as the elements of the |
| // first column |
| const A = this.multiplicationInterval( |
| m[0][0], |
| this.determinant3x3Interval(this.minorNxN(m, 0, 0)) |
| ); |
| const B = this.multiplicationInterval( |
| -m[0][1], |
| this.determinant3x3Interval(this.minorNxN(m, 0, 1)) |
| ); |
| const C = this.multiplicationInterval( |
| m[0][2], |
| this.determinant3x3Interval(this.minorNxN(m, 0, 2)) |
| ); |
| const D = this.multiplicationInterval( |
| -m[0][3], |
| this.determinant3x3Interval(this.minorNxN(m, 0, 3)) |
| ); |
| |
| // Need to calculate permutations, since for fp addition is not associative |
| // so A + B + C + D is not guaranteed to equal B + C + A + D, etc. |
| const permutations: ROArrayArray<FPInterval> = calculatePermutations([A, B, C, D]); |
| return this.spanIntervals( |
| ...permutations.map(p => |
| p.reduce((prev: FPInterval, cur: FPInterval) => this.additionInterval(prev, cur)) |
| ) |
| ); |
| } |
| |
| /** |
| * This code calculates 3x3 and 4x4 determinants using the textbook co-factor |
| * method, using the first column for the co-factor selection. |
| * |
| * For matrices composed of integer elements, e, with |e|^4 < 2**21, this |
| * should be fine. |
| * |
| * For e, where e is subnormal or 4*(e^4) might not be precisely expressible as |
| * a f32 values, this approach breaks down, because the rule of all co-factor |
| * definitions of determinant being equal doesn't hold in these cases. |
| * |
| * The general solution for this is to calculate all the permutations of the |
| * operations in the worked out formula for determinant. |
| * For 3x3 this is tractable, but for 4x4 this works out to ~23! permutations |
| * that need to be calculated. |
| * Thus, CTS testing and the spec definition of accuracy is restricted to the |
| * space that the simple implementation is valid. |
| */ |
| protected determinantIntervalImpl(x: Array2D<number>): FPInterval { |
| const dim = x.length; |
| assert( |
| x[0].length === dim && (dim === 2 || dim === 3 || dim === 4), |
| `determinantInterval only defined for 2x2, 3x3 and 4x4 matrices` |
| ); |
| switch (dim) { |
| case 2: |
| return this.determinant2x2Interval(x); |
| case 3: |
| return this.determinant3x3Interval(x); |
| case 4: |
| return this.determinant4x4Interval(x); |
| } |
| unreachable( |
| "determinantInterval called on x, where which has an unexpected dimension of '${dim}'" |
| ); |
| } |
| |
| /** Calculate an acceptance interval for determinant(x) */ |
| public abstract readonly determinantInterval: (x: Array2D<number>) => FPInterval; |
| |
| private readonly DistanceIntervalScalarOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| return this.lengthInterval(this.subtractionInterval(x, y)); |
| }, |
| }; |
| |
| private readonly DistanceIntervalVectorOp: VectorPairToIntervalOp = { |
| impl: (x: readonly number[], y: readonly number[]): FPInterval => { |
| return this.lengthInterval( |
| this.runScalarPairToIntervalOpVectorComponentWise( |
| this.toVector(x), |
| this.toVector(y), |
| this.SubtractionIntervalOp |
| ) |
| ); |
| }, |
| }; |
| |
| protected distanceIntervalImpl( |
| x: number | readonly number[], |
| y: number | readonly number[] |
| ): FPInterval { |
| if (x instanceof Array && y instanceof Array) { |
| assert( |
| x.length === y.length, |
| `distanceInterval requires both params to have the same number of elements` |
| ); |
| return this.runVectorPairToIntervalOp( |
| this.toVector(x), |
| this.toVector(y), |
| this.DistanceIntervalVectorOp |
| ); |
| } else if (!(x instanceof Array) && !(y instanceof Array)) { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.DistanceIntervalScalarOp |
| ); |
| } |
| unreachable( |
| `distanceInterval requires both params to both the same type, either scalars or vectors` |
| ); |
| } |
| |
| /** Calculate an acceptance interval of distance(x, y) */ |
| public abstract readonly distanceInterval: ( |
| x: number | readonly number[], |
| y: number | readonly number[] |
| ) => FPInterval; |
| |
| // This op is implemented differently for f32 and f16. |
| private DivisionIntervalOpBuilder(): ScalarPairToIntervalOp { |
| const constants = this.constants(); |
| const domain_x = [this.toInterval([constants.negative.min, constants.positive.max])]; |
| const domain_y = |
| this.kind === 'f32' || this.kind === 'abstract' |
| ? [this.toInterval([-(2 ** 126), -(2 ** -126)]), this.toInterval([2 ** -126, 2 ** 126])] |
| : [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])]; |
| return { |
| impl: (x: number, y: number): FPInterval => { |
| if (y === 0) { |
| return constants.unboundedInterval; |
| } |
| return this.ulpInterval(x / y, 2.5); |
| }, |
| extrema: (x: FPInterval, y: FPInterval): [FPInterval, FPInterval] => { |
| // division has a discontinuity at y = 0. |
| if (y.contains(0)) { |
| y = this.toInterval(0); |
| } |
| return [x, y]; |
| }, |
| domain: () => { |
| return { x: domain_x, y: domain_y }; |
| }, |
| }; |
| } |
| |
| protected divisionIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.DivisionIntervalOpBuilder() |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x / y */ |
| public abstract readonly divisionInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| private readonly DotIntervalOp: VectorPairToIntervalOp = { |
| impl: (x: readonly number[], y: readonly number[]): FPInterval => { |
| // dot(x, y) = sum of x[i] * y[i] |
| const multiplications = this.runScalarPairToIntervalOpVectorComponentWise( |
| this.toVector(x), |
| this.toVector(y), |
| this.MultiplicationIntervalOp |
| ); |
| |
| // vec2 doesn't require permutations, since a + b = b + a for floats |
| if (multiplications.length === 2) { |
| return this.additionInterval(multiplications[0], multiplications[1]); |
| } |
| |
| // The spec does not state the ordering of summation, so all the |
| // permutations are calculated and their results spanned, since addition |
| // of more than two floats is not transitive, i.e. a + b + c is not |
| // guaranteed to equal b + a + c |
| const permutations: ROArrayArray<FPInterval> = calculatePermutations(multiplications); |
| return this.spanIntervals( |
| ...permutations.map(p => p.reduce((prev, cur) => this.additionInterval(prev, cur))) |
| ); |
| }, |
| }; |
| |
| protected dotIntervalImpl( |
| x: readonly number[] | readonly FPInterval[], |
| y: readonly number[] | readonly FPInterval[] |
| ): FPInterval { |
| assert( |
| x.length === y.length, |
| `dot not defined for vectors with different lengths, x = ${x}, y = ${y}` |
| ); |
| return this.runVectorPairToIntervalOp(this.toVector(x), this.toVector(y), this.DotIntervalOp); |
| } |
| |
| /** Calculated the acceptance interval for dot(x, y) */ |
| public abstract readonly dotInterval: ( |
| x: readonly number[] | readonly FPInterval[], |
| y: readonly number[] | readonly FPInterval[] |
| ) => FPInterval; |
| |
| private readonly ExpIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const ulp_error = this.kind === 'f32' ? 3 + 2 * Math.abs(n) : 1 + 2 * Math.abs(n); |
| return this.ulpInterval(Math.exp(n), ulp_error); |
| }, |
| }; |
| |
| protected expIntervalImpl(x: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(x), this.ExpIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval for exp(x) */ |
| public abstract readonly expInterval: (x: number | FPInterval) => FPInterval; |
| |
| private readonly Exp2IntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const ulp_error = this.kind === 'f32' ? 3 + 2 * Math.abs(n) : 1 + 2 * Math.abs(n); |
| return this.ulpInterval(Math.pow(2, n), ulp_error); |
| }, |
| }; |
| |
| protected exp2IntervalImpl(x: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(x), this.Exp2IntervalOp); |
| } |
| |
| /** Calculate an acceptance interval for exp2(x) */ |
| public abstract readonly exp2Interval: (x: number | FPInterval) => FPInterval; |
| |
| /** |
| * faceForward(x, y, z) = select(-x, x, dot(z, y) < 0.0) |
| * |
| * This builtin selects from two discrete results (delta rounding/flushing), |
| * so the majority of the framework code is not appropriate, since the |
| * framework attempts to span results. |
| * |
| * Thus, a bespoke implementation is used instead of |
| * defining an Op and running that through the framework. |
| */ |
| protected faceForwardIntervalsImpl( |
| x: readonly number[], |
| y: readonly number[], |
| z: readonly number[] |
| ): (FPVector | undefined)[] { |
| const x_vec = this.toVector(x); |
| // Running vector through this.runScalarToIntervalOpComponentWise to make |
| // sure that flushing/rounding is handled, since toVector does not perform |
| // those operations. |
| const positive_x = this.runScalarToIntervalOpComponentWise(x_vec, { |
| impl: (i: number): FPInterval => { |
| return this.toInterval(i); |
| }, |
| }); |
| const negative_x = this.runScalarToIntervalOpComponentWise(x_vec, this.NegationIntervalOp); |
| |
| const dot_interval = this.dotInterval(z, y); |
| |
| const results: (FPVector | undefined)[] = []; |
| |
| if (!dot_interval.isFinite()) { |
| // dot calculation went out of bounds |
| // Inserting undefined in the result, so that the test running framework |
| // is aware of this potential OOB. |
| // For const-eval tests, it means that the test case should be skipped, |
| // since the shader will fail to compile. |
| // For non-const-eval the undefined should be stripped out of the possible |
| // results. |
| |
| results.push(undefined); |
| } |
| |
| // Because the result of dot can be an interval, it might span across 0, thus |
| // it is possible that both -x and x are valid responses. |
| if (dot_interval.begin < 0 || dot_interval.end < 0) { |
| results.push(positive_x); |
| } |
| |
| if (dot_interval.begin >= 0 || dot_interval.end >= 0) { |
| results.push(negative_x); |
| } |
| |
| assert( |
| results.length > 0 || results.every(r => r === undefined), |
| `faceForwardInterval selected neither positive x or negative x for the result, this shouldn't be possible` |
| ); |
| return results; |
| } |
| |
| /** Calculate the acceptance intervals for faceForward(x, y, z) */ |
| public abstract readonly faceForwardIntervals: ( |
| x: readonly number[], |
| y: readonly number[], |
| z: readonly number[] |
| ) => (FPVector | undefined)[]; |
| |
| private readonly FloorIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.correctlyRoundedInterval(Math.floor(n)); |
| }, |
| }; |
| |
| protected floorIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.FloorIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of floor(x) */ |
| public abstract readonly floorInterval: (n: number) => FPInterval; |
| |
| private readonly FmaIntervalOp: ScalarTripleToIntervalOp = { |
| impl: (x: number, y: number, z: number): FPInterval => { |
| return this.additionInterval(this.multiplicationInterval(x, y), z); |
| }, |
| }; |
| |
| protected fmaIntervalImpl(x: number, y: number, z: number): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.toInterval(z), |
| this.FmaIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval for fma(x, y, z) */ |
| public abstract readonly fmaInterval: (x: number, y: number, z: number) => FPInterval; |
| |
| private readonly FractIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| // fract(x) = x - floor(x) is defined in the spec. |
| // For people coming from a non-graphics background this will cause some |
| // unintuitive results. For example, |
| // fract(-1.1) is not 0.1 or -0.1, but instead 0.9. |
| // This is how other shading languages operate and allows for a desirable |
| // wrap around in graphics programming. |
| const result = this.subtractionInterval(n, this.floorInterval(n)); |
| assert( |
| // negative.subnormal.min instead of 0, because FTZ can occur |
| // selectively during the calculation |
| this.toInterval([this.constants().negative.subnormal.min, 1.0]).contains(result), |
| `fract(${n}) interval [${result}] unexpectedly extends beyond [~0.0, 1.0]` |
| ); |
| if (result.contains(1)) { |
| // Very small negative numbers can lead to catastrophic cancellation, |
| // thus calculating a fract of 1.0, which is technically not a |
| // fractional part, so some implementations clamp the result to next |
| // nearest number. |
| return this.spanIntervals(result, this.toInterval(this.constants().positive.less_than_one)); |
| } |
| return result; |
| }, |
| }; |
| |
| protected fractIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.FractIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of fract(x) */ |
| public abstract readonly fractInterval: (n: number) => FPInterval; |
| |
| private readonly InverseSqrtIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.ulpInterval(1 / Math.sqrt(n), 2); |
| }, |
| domain: () => { |
| return this.constants().greaterThanZeroInterval; |
| }, |
| }; |
| |
| protected inverseSqrtIntervalImpl(n: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.InverseSqrtIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of inverseSqrt(x) */ |
| public abstract readonly inverseSqrtInterval: (n: number | FPInterval) => FPInterval; |
| |
| private readonly LdexpIntervalOp: ScalarPairToIntervalOp = { |
| impl: (e1: number, e2: number) => { |
| assert(Number.isInteger(e2), 'the second param of ldexp must be an integer'); |
| // Spec explicitly calls indeterminate value if e2 > bias + 1 |
| if (e2 > this.constants().bias + 1) { |
| return this.constants().unboundedInterval; |
| } |
| // The spec says the result of ldexp(e1, e2) = e1 * 2 ^ e2, and the |
| // accuracy is correctly rounded to the true value, so the inheritance |
| // framework does not need to be invoked to determine endpoints. |
| // Instead, the value at a higher precision is calculated and passed to |
| // correctlyRoundedInterval. |
| const result = e1 * 2 ** e2; |
| if (!Number.isFinite(result)) { |
| // Overflowed TS's number type, so definitely out of bounds |
| return this.constants().unboundedInterval; |
| } |
| // The result may be zero if e2 + bias <= 0, but we can't simply span the interval to 0.0. |
| // For example, for f32 input e1 = 2**120 and e2 = -130, e2 + bias = -3 <= 0, but |
| // e1 * 2 ** e2 = 2**-10, so the valid result is 2**-10 or 0.0, instead of [0.0, 2**-10]. |
| // Always return the correctly-rounded interval, and special examination should be taken when |
| // using the result. |
| return this.correctlyRoundedInterval(result); |
| }, |
| }; |
| |
| protected ldexpIntervalImpl(e1: number, e2: number): FPInterval { |
| // Only round and flush e1, as e2 is of integer type (i32 or abstract integer) and should be |
| // precise. |
| return this.roundAndFlushScalarToInterval(e1, { |
| impl: (e1: number) => this.LdexpIntervalOp.impl(e1, e2), |
| }); |
| } |
| |
| /** |
| * Calculate an acceptance interval of ldexp(e1, e2), where e2 is integer |
| * |
| * Spec indicate that the result may be zero if e2 + bias <= 0, no matter how large |
| * was e1 * 2 ** e2, i.e. the actual valid result is correctlyRounded(e1 * 2 ** e2) or 0.0, if |
| * e2 + bias <= 0. Such discontinious flush-to-zero behavior is hard to be expressed using |
| * FPInterval, therefore in the situation of e2 + bias <= 0 the returned interval would be just |
| * correctlyRounded(e1 * 2 ** e2), and special examination should be taken when using the result. |
| * |
| */ |
| public abstract readonly ldexpInterval: (e1: number, e2: number) => FPInterval; |
| |
| private readonly LengthIntervalScalarOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.sqrtInterval(this.multiplicationInterval(n, n)); |
| }, |
| }; |
| |
| private readonly LengthIntervalVectorOp: VectorToIntervalOp = { |
| impl: (n: readonly number[]): FPInterval => { |
| return this.sqrtInterval(this.dotInterval(n, n)); |
| }, |
| }; |
| |
| protected lengthIntervalImpl(n: number | FPInterval | readonly number[] | FPVector): FPInterval { |
| if (n instanceof Array) { |
| return this.runVectorToIntervalOp(this.toVector(n), this.LengthIntervalVectorOp); |
| } else { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.LengthIntervalScalarOp); |
| } |
| } |
| |
| /** Calculate an acceptance interval of length(x) */ |
| public abstract readonly lengthInterval: ( |
| n: number | FPInterval | readonly number[] | FPVector |
| ) => FPInterval; |
| |
| private readonly LogIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7; |
| if (n >= 0.5 && n <= 2.0) { |
| return this.absoluteErrorInterval(Math.log(n), abs_error); |
| } |
| return this.ulpInterval(Math.log(n), 3); |
| }, |
| domain: () => { |
| return this.constants().greaterThanZeroInterval; |
| }, |
| }; |
| |
| protected logIntervalImpl(x: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(x), this.LogIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of log(x) */ |
| public abstract readonly logInterval: (x: number | FPInterval) => FPInterval; |
| |
| private readonly Log2IntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7; |
| if (n >= 0.5 && n <= 2.0) { |
| return this.absoluteErrorInterval(Math.log2(n), abs_error); |
| } |
| return this.ulpInterval(Math.log2(n), 3); |
| }, |
| domain: () => { |
| return this.constants().greaterThanZeroInterval; |
| }, |
| }; |
| |
| protected log2IntervalImpl(x: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(x), this.Log2IntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of log2(x) */ |
| public abstract readonly log2Interval: (x: number | FPInterval) => FPInterval; |
| |
| private readonly MaxIntervalOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| // If both of the inputs are subnormal, then either of the inputs can be returned |
| if (this.isSubnormal(x) && this.isSubnormal(y)) { |
| return this.correctlyRoundedInterval( |
| this.spanIntervals(this.toInterval(x), this.toInterval(y)) |
| ); |
| } |
| |
| return this.correctlyRoundedInterval(Math.max(x, y)); |
| }, |
| }; |
| |
| protected maxIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.MaxIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of max(x, y) */ |
| public abstract readonly maxInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| private readonly MinIntervalOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| // If both of the inputs are subnormal, then either of the inputs can be returned |
| if (this.isSubnormal(x) && this.isSubnormal(y)) { |
| return this.correctlyRoundedInterval( |
| this.spanIntervals(this.toInterval(x), this.toInterval(y)) |
| ); |
| } |
| |
| return this.correctlyRoundedInterval(Math.min(x, y)); |
| }, |
| }; |
| |
| protected minIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.MinIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of min(x, y) */ |
| public abstract readonly minInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| private readonly MixImpreciseIntervalOp: ScalarTripleToIntervalOp = { |
| impl: (x: number, y: number, z: number): FPInterval => { |
| // x + (y - x) * z = |
| // x + t, where t = (y - x) * z |
| const t = this.multiplicationInterval(this.subtractionInterval(y, x), z); |
| return this.additionInterval(x, t); |
| }, |
| }; |
| |
| protected mixImpreciseIntervalImpl(x: number, y: number, z: number): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.toInterval(z), |
| this.MixImpreciseIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of mix(x, y, z) using x + (y - x) * z */ |
| public abstract readonly mixImpreciseInterval: (x: number, y: number, z: number) => FPInterval; |
| |
| private readonly MixPreciseIntervalOp: ScalarTripleToIntervalOp = { |
| impl: (x: number, y: number, z: number): FPInterval => { |
| // x * (1.0 - z) + y * z = |
| // t + s, where t = x * (1.0 - z), s = y * z |
| const t = this.multiplicationInterval(x, this.subtractionInterval(1.0, z)); |
| const s = this.multiplicationInterval(y, z); |
| return this.additionInterval(t, s); |
| }, |
| }; |
| |
| protected mixPreciseIntervalImpl(x: number, y: number, z: number): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.toInterval(z), |
| this.MixPreciseIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of mix(x, y, z) using x * (1.0 - z) + y * z */ |
| public abstract readonly mixPreciseInterval: (x: number, y: number, z: number) => FPInterval; |
| |
| /** All acceptance interval functions for mix(x, y, z) */ |
| public abstract readonly mixIntervals: ScalarTripleToInterval[]; |
| |
| protected modfIntervalImpl(n: number): { fract: FPInterval; whole: FPInterval } { |
| const fract = this.correctlyRoundedInterval(n % 1.0); |
| const whole = this.correctlyRoundedInterval(n - (n % 1.0)); |
| return { fract, whole }; |
| } |
| |
| /** Calculate an acceptance interval of modf(x) */ |
| public abstract readonly modfInterval: (n: number) => { fract: FPInterval; whole: FPInterval }; |
| |
| private readonly MultiplicationInnerOp = { |
| impl: (x: number, y: number): FPInterval => { |
| return this.correctlyRoundedInterval(x * y); |
| }, |
| }; |
| |
| private readonly MultiplicationIntervalOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| return this.roundAndFlushScalarPairToInterval(x, y, this.MultiplicationInnerOp); |
| }, |
| }; |
| |
| protected multiplicationIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.MultiplicationIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x * y */ |
| public abstract readonly multiplicationInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| /** |
| * @returns the vector result of multiplying the given vector by the given |
| * scalar |
| */ |
| private multiplyVectorByScalar(v: readonly number[], c: number | FPInterval): FPVector { |
| return this.toVector(v.map(x => this.multiplicationInterval(x, c))); |
| } |
| |
| protected multiplicationMatrixScalarIntervalImpl(mat: Array2D<number>, scalar: number): FPMatrix { |
| return this.runScalarPairToIntervalOpScalarMatrixComponentWise( |
| this.toInterval(scalar), |
| this.toMatrix(mat), |
| this.MultiplicationIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x * y, when x is a matrix and y is a scalar */ |
| public abstract readonly multiplicationMatrixScalarInterval: ( |
| mat: Array2D<number>, |
| scalar: number |
| ) => FPMatrix; |
| |
| protected multiplicationScalarMatrixIntervalImpl(scalar: number, mat: Array2D<number>): FPMatrix { |
| return this.multiplicationMatrixScalarInterval(mat, scalar); |
| } |
| |
| /** Calculate an acceptance interval of x * y, when x is a scalar and y is a matrix */ |
| public abstract readonly multiplicationScalarMatrixInterval: ( |
| scalar: number, |
| mat: Array2D<number> |
| ) => FPMatrix; |
| |
| protected multiplicationMatrixMatrixIntervalImpl( |
| mat_x: Array2D<number>, |
| mat_y: Array2D<number> |
| ): FPMatrix { |
| const x_cols = mat_x.length; |
| const x_rows = mat_x[0].length; |
| const y_cols = mat_y.length; |
| const y_rows = mat_y[0].length; |
| assert(x_cols === y_rows, `'mat${x_cols}x${x_rows} * mat${y_cols}x${y_rows}' is not defined`); |
| |
| const x_transposed = this.transposeInterval(mat_x); |
| |
| let oob_result: boolean = false; |
| const result: FPInterval[][] = [...Array(y_cols)].map(_ => [...Array(x_rows)]); |
| mat_y.forEach((y, i) => { |
| x_transposed.forEach((x, j) => { |
| result[i][j] = this.dotInterval(x, y); |
| if (!oob_result && !result[i][j].isFinite()) { |
| oob_result = true; |
| } |
| }); |
| }); |
| |
| if (oob_result) { |
| return this.constants().unboundedMatrix[result.length as 2 | 3 | 4][ |
| result[0].length as 2 | 3 | 4 |
| ]; |
| } |
| return result as ROArrayArray<FPInterval> as FPMatrix; |
| } |
| |
| /** Calculate an acceptance interval of x * y, when x is a matrix and y is a matrix */ |
| public abstract readonly multiplicationMatrixMatrixInterval: ( |
| mat_x: Array2D<number>, |
| mat_y: Array2D<number> |
| ) => FPMatrix; |
| |
| protected multiplicationMatrixVectorIntervalImpl( |
| x: Array2D<number>, |
| y: readonly number[] |
| ): FPVector { |
| const cols = x.length; |
| const rows = x[0].length; |
| assert(y.length === cols, `'mat${cols}x${rows} * vec${y.length}' is not defined`); |
| |
| return this.transposeInterval(x).map(e => this.dotInterval(e, y)) as FPVector; |
| } |
| |
| /** Calculate an acceptance interval of x * y, when x is a matrix and y is a vector */ |
| public abstract readonly multiplicationMatrixVectorInterval: ( |
| x: Array2D<number>, |
| y: readonly number[] |
| ) => FPVector; |
| |
| protected multiplicationVectorMatrixIntervalImpl( |
| x: readonly number[], |
| y: Array2D<number> |
| ): FPVector { |
| const cols = y.length; |
| const rows = y[0].length; |
| assert(x.length === rows, `'vec${x.length} * mat${cols}x${rows}' is not defined`); |
| |
| return y.map(e => this.dotInterval(x, e)) as FPVector; |
| } |
| |
| /** Calculate an acceptance interval of x * y, when x is a vector and y is a matrix */ |
| public abstract readonly multiplicationVectorMatrixInterval: ( |
| x: readonly number[], |
| y: Array2D<number> |
| ) => FPVector; |
| |
| private readonly NegationIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.correctlyRoundedInterval(-n); |
| }, |
| }; |
| |
| protected negationIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.NegationIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of -x */ |
| public abstract readonly negationInterval: (n: number) => FPInterval; |
| |
| private readonly NormalizeIntervalOp: VectorToVectorOp = { |
| impl: (n: readonly number[]): FPVector => { |
| const length = this.lengthInterval(n); |
| const result = this.toVector(n.map(e => this.divisionInterval(e, length))); |
| if (result.some(r => !r.isFinite())) { |
| return this.constants().unboundedVector[result.length]; |
| } |
| return result; |
| }, |
| }; |
| |
| protected normalizeIntervalImpl(n: readonly number[]): FPVector { |
| return this.runVectorToVectorOp(this.toVector(n), this.NormalizeIntervalOp); |
| } |
| |
| public abstract readonly normalizeInterval: (n: readonly number[]) => FPVector; |
| |
| private readonly PowIntervalOp: ScalarPairToIntervalOp = { |
| // pow(x, y) has no explicit domain restrictions, but inherits the x <= 0 |
| // domain restriction from log2(x). Invoking log2Interval(x) in impl will |
| // enforce this, so there is no need to wrap the impl call here. |
| impl: (x: number, y: number): FPInterval => { |
| return this.exp2Interval(this.multiplicationInterval(y, this.log2Interval(x))); |
| }, |
| }; |
| |
| protected powIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.PowIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of pow(x, y) */ |
| public abstract readonly powInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| private readonly RadiansIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.multiplicationInterval(n, 0.017453292519943295474); |
| }, |
| }; |
| |
| protected radiansIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.RadiansIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of radians(x) */ |
| public abstract readonly radiansInterval: (n: number) => FPInterval; |
| |
| private readonly ReflectIntervalOp: VectorPairToVectorOp = { |
| impl: (x: readonly number[], y: readonly number[]): FPVector => { |
| assert( |
| x.length === y.length, |
| `ReflectIntervalOp received x (${x}) and y (${y}) with different numbers of elements` |
| ); |
| |
| // reflect(x, y) = x - 2.0 * dot(x, y) * y |
| // = x - t * y, t = 2.0 * dot(x, y) |
| // x = incident vector |
| // y = normal of reflecting surface |
| const t = this.multiplicationInterval(2.0, this.dotInterval(x, y)); |
| const rhs = this.multiplyVectorByScalar(y, t); |
| const result = this.runScalarPairToIntervalOpVectorComponentWise( |
| this.toVector(x), |
| rhs, |
| this.SubtractionIntervalOp |
| ); |
| |
| if (result.some(r => !r.isFinite())) { |
| return this.constants().unboundedVector[result.length]; |
| } |
| return result; |
| }, |
| }; |
| |
| protected reflectIntervalImpl(x: readonly number[], y: readonly number[]): FPVector { |
| assert( |
| x.length === y.length, |
| `reflect is only defined for vectors with the same number of elements` |
| ); |
| return this.runVectorPairToVectorOp(this.toVector(x), this.toVector(y), this.ReflectIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of reflect(x, y) */ |
| public abstract readonly reflectInterval: ( |
| x: readonly number[], |
| y: readonly number[] |
| ) => FPVector; |
| |
| /** |
| * refract is a singular function in the sense that it is the only builtin that |
| * takes in (FPVector, FPVector, F32/F16) and returns FPVector and is basically |
| * defined in terms of other functions. |
| * |
| * Instead of implementing all the framework code to integrate it with its |
| * own operation type, etc, it instead has a bespoke implementation that is a |
| * composition of other builtin functions that use the framework. |
| */ |
| protected refractIntervalImpl(i: readonly number[], s: readonly number[], r: number): FPVector { |
| assert( |
| i.length === s.length, |
| `refract is only defined for vectors with the same number of elements` |
| ); |
| |
| const r_squared = this.multiplicationInterval(r, r); |
| const dot = this.dotInterval(s, i); |
| const dot_squared = this.multiplicationInterval(dot, dot); |
| const one_minus_dot_squared = this.subtractionInterval(1, dot_squared); |
| const k = this.subtractionInterval( |
| 1.0, |
| this.multiplicationInterval(r_squared, one_minus_dot_squared) |
| ); |
| |
| if (!k.isFinite() || k.containsZeroOrSubnormals()) { |
| // There is a discontinuity at k == 0, due to sqrt(k) being calculated, so exiting early |
| return this.constants().unboundedVector[this.toVector(i).length]; |
| } |
| |
| if (k.end < 0.0) { |
| // if k is negative, then the zero vector is the valid response |
| return this.constants().zeroVector[this.toVector(i).length]; |
| } |
| |
| const dot_times_r = this.multiplicationInterval(dot, r); |
| const k_sqrt = this.sqrtInterval(k); |
| const t = this.additionInterval(dot_times_r, k_sqrt); // t = r * dot(i, s) + sqrt(k) |
| |
| const result = this.runScalarPairToIntervalOpVectorComponentWise( |
| this.multiplyVectorByScalar(i, r), |
| this.multiplyVectorByScalar(s, t), |
| this.SubtractionIntervalOp |
| ); // (i * r) - (s * t) |
| |
| if (result.some(r => !r.isFinite())) { |
| return this.constants().unboundedVector[result.length]; |
| } |
| return result; |
| } |
| |
| /** Calculate acceptance interval vectors of reflect(i, s, r) */ |
| public abstract readonly refractInterval: ( |
| i: readonly number[], |
| s: readonly number[], |
| r: number |
| ) => FPVector; |
| |
| private readonly RemainderIntervalOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| // x % y = x - y * trunc(x/y) |
| return this.subtractionInterval( |
| x, |
| this.multiplicationInterval(y, this.truncInterval(this.divisionInterval(x, y))) |
| ); |
| }, |
| }; |
| |
| /** Calculate an acceptance interval for x % y */ |
| protected remainderIntervalImpl(x: number, y: number): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.RemainderIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval for x % y */ |
| public abstract readonly remainderInterval: (x: number, y: number) => FPInterval; |
| |
| private readonly RoundIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| const k = Math.floor(n); |
| const diff_before = n - k; |
| const diff_after = k + 1 - n; |
| if (diff_before < diff_after) { |
| return this.correctlyRoundedInterval(k); |
| } else if (diff_before > diff_after) { |
| return this.correctlyRoundedInterval(k + 1); |
| } |
| |
| // n is in the middle of two integers. |
| // The tie breaking rule is 'k if k is even, k + 1 if k is odd' |
| if (k % 2 === 0) { |
| return this.correctlyRoundedInterval(k); |
| } |
| return this.correctlyRoundedInterval(k + 1); |
| }, |
| }; |
| |
| protected roundIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.RoundIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of round(x) */ |
| public abstract readonly roundInterval: (n: number) => FPInterval; |
| |
| /** |
| * The definition of saturate does not specify which version of clamp to use. |
| * Using min-max here, since it has wider acceptance intervals, that include |
| * all of median's. |
| */ |
| protected saturateIntervalImpl(n: number): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(n), |
| this.toInterval(0.0), |
| this.toInterval(1.0), |
| this.ClampMinMaxIntervalOp |
| ); |
| } |
| |
| /*** Calculate an acceptance interval of saturate(n) as clamp(n, 0.0, 1.0) */ |
| public abstract readonly saturateInterval: (n: number) => FPInterval; |
| |
| private readonly SignIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| if (n > 0.0) { |
| return this.correctlyRoundedInterval(1.0); |
| } |
| if (n < 0.0) { |
| return this.correctlyRoundedInterval(-1.0); |
| } |
| |
| return this.correctlyRoundedInterval(0.0); |
| }, |
| }; |
| |
| protected signIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.SignIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of sign(x) */ |
| public abstract readonly signInterval: (n: number) => FPInterval; |
| |
| private readonly SinIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| assert(this.kind === 'f32' || this.kind === 'f16'); |
| const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7; |
| return this.absoluteErrorInterval(Math.sin(n), abs_error); |
| }, |
| domain: () => { |
| return this.constants().negPiToPiInterval; |
| }, |
| }; |
| |
| protected sinIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.SinIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of sin(x) */ |
| public abstract readonly sinInterval: (n: number) => FPInterval; |
| |
| private readonly SinhIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| // sinh(x) = (exp(x) - exp(-x)) * 0.5 |
| const minus_n = this.negationInterval(n); |
| return this.multiplicationInterval( |
| this.subtractionInterval(this.expInterval(n), this.expInterval(minus_n)), |
| 0.5 |
| ); |
| }, |
| }; |
| |
| protected sinhIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.SinhIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of sinh(x) */ |
| public abstract readonly sinhInterval: (n: number) => FPInterval; |
| |
| private readonly SmoothStepOp: ScalarTripleToIntervalOp = { |
| impl: (low: number, high: number, x: number): FPInterval => { |
| // For clamp(foo, 0.0, 1.0) the different implementations of clamp provide |
| // the same value, so arbitrarily picking the minmax version to use. |
| // t = clamp((x - low) / (high - low), 0.0, 1.0) |
| // prettier-ignore |
| const t = this.clampMedianInterval( |
| this.divisionInterval( |
| this.subtractionInterval(x, low), |
| this.subtractionInterval(high, low)), |
| 0.0, |
| 1.0); |
| // Inherited from t * t * (3.0 - 2.0 * t) |
| // prettier-ignore |
| return this.multiplicationInterval( |
| t, |
| this.multiplicationInterval(t, |
| this.subtractionInterval(3.0, |
| this.multiplicationInterval(2.0, t)))); |
| }, |
| }; |
| |
| protected smoothStepIntervalImpl(low: number, high: number, x: number): FPInterval { |
| return this.runScalarTripleToIntervalOp( |
| this.toInterval(low), |
| this.toInterval(high), |
| this.toInterval(x), |
| this.SmoothStepOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of smoothStep(low, high, x) */ |
| public abstract readonly smoothStepInterval: (low: number, high: number, x: number) => FPInterval; |
| |
| private readonly SqrtIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.divisionInterval(1.0, this.inverseSqrtInterval(n)); |
| }, |
| }; |
| |
| protected sqrtIntervalImpl(n: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.SqrtIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of sqrt(x) */ |
| public abstract readonly sqrtInterval: (n: number | FPInterval) => FPInterval; |
| |
| private readonly StepIntervalOp: ScalarPairToIntervalOp = { |
| impl: (edge: number, x: number): FPInterval => { |
| if (edge <= x) { |
| return this.correctlyRoundedInterval(1.0); |
| } |
| return this.correctlyRoundedInterval(0.0); |
| }, |
| }; |
| |
| protected stepIntervalImpl(edge: number, x: number): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(edge), |
| this.toInterval(x), |
| this.StepIntervalOp |
| ); |
| } |
| |
| /** |
| * Calculate an acceptance 'interval' for step(edge, x) |
| * |
| * step only returns two possible values, so its interval requires special |
| * interpretation in CTS tests. |
| * This interval will be one of four values: [0, 0], [0, 1], [1, 1] & [-∞, +∞]. |
| * [0, 0] and [1, 1] indicate that the correct answer in point they encapsulate. |
| * [0, 1] should not be treated as a span, i.e. 0.1 is acceptable, but instead |
| * indicate either 0.0 or 1.0 are acceptable answers. |
| * [-∞, +∞] is treated as unbounded interval, since an unbounded or |
| * infinite value was passed in. |
| */ |
| public abstract readonly stepInterval: (edge: number, x: number) => FPInterval; |
| |
| private readonly SubtractionIntervalOp: ScalarPairToIntervalOp = { |
| impl: (x: number, y: number): FPInterval => { |
| const difference: number = x - y; |
| // To support unbounded precision we need to handle the special case for very large vs small values |
| // We can resuse the function that is used by addition since floating point is symmetric for negative |
| // and positive values. |
| const large_val = Math.abs(x) > Math.abs(y) ? x : -y; |
| const small_val = Math.abs(x) > Math.abs(y) ? -y : x; |
| return this.correctlyRoundedIntervalWithUnboundedPrecisionForAddition( |
| difference, |
| large_val, |
| small_val |
| ); |
| }, |
| }; |
| |
| protected subtractionIntervalImpl(x: number | FPInterval, y: number | FPInterval): FPInterval { |
| return this.runScalarPairToIntervalOp( |
| this.toInterval(x), |
| this.toInterval(y), |
| this.SubtractionIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x - y */ |
| public abstract readonly subtractionInterval: ( |
| x: number | FPInterval, |
| y: number | FPInterval |
| ) => FPInterval; |
| |
| protected subtractionMatrixMatrixIntervalImpl(x: Array2D<number>, y: Array2D<number>): FPMatrix { |
| return this.runScalarPairToIntervalOpMatrixMatrixComponentWise( |
| this.toMatrix(x), |
| this.toMatrix(y), |
| this.SubtractionIntervalOp |
| ); |
| } |
| |
| /** Calculate an acceptance interval of x - y, when x and y are matrices */ |
| public abstract readonly subtractionMatrixMatrixInterval: ( |
| x: Array2D<number>, |
| y: Array2D<number> |
| ) => FPMatrix; |
| |
| private readonly TanIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.divisionInterval(this.sinInterval(n), this.cosInterval(n)); |
| }, |
| }; |
| |
| protected tanIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.TanIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of tan(x) */ |
| public abstract readonly tanInterval: (n: number) => FPInterval; |
| |
| private readonly TanhIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| const approx_abs_error = 1.0e-5; |
| return this.spanIntervals( |
| this.divisionInterval(this.sinhInterval(n), this.coshInterval(n)), |
| this.absoluteErrorInterval(Math.tanh(n), approx_abs_error) |
| ); |
| }, |
| }; |
| |
| protected tanhIntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.TanhIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of tanh(x) */ |
| public abstract readonly tanhInterval: (n: number) => FPInterval; |
| |
| private readonly TransposeIntervalOp: MatrixToMatrixOp = { |
| impl: (m: Array2D<number>): FPMatrix => { |
| const num_cols = m.length; |
| const num_rows = m[0].length; |
| const result: FPInterval[][] = [...Array(num_rows)].map(_ => [...Array(num_cols)]); |
| |
| for (let i = 0; i < num_cols; i++) { |
| for (let j = 0; j < num_rows; j++) { |
| result[j][i] = this.correctlyRoundedInterval(m[i][j]); |
| } |
| } |
| return this.toMatrix(result); |
| }, |
| }; |
| |
| protected transposeIntervalImpl(m: Array2D<number>): FPMatrix { |
| return this.runMatrixToMatrixOp(this.toMatrix(m), this.TransposeIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of transpose(m) */ |
| public abstract readonly transposeInterval: (m: Array2D<number>) => FPMatrix; |
| |
| private readonly TruncIntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| return this.correctlyRoundedInterval(Math.trunc(n)); |
| }, |
| }; |
| |
| protected truncIntervalImpl(n: number | FPInterval): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.TruncIntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of trunc(x) */ |
| public abstract readonly truncInterval: (n: number | FPInterval) => FPInterval; |
| } |
| |
| // Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this |
| // executes before they are defined. |
| const kF32UnboundedInterval = new FPInterval( |
| 'f32', |
| Number.NEGATIVE_INFINITY, |
| Number.POSITIVE_INFINITY |
| ); |
| const kF32ZeroInterval = new FPInterval('f32', 0); |
| |
| class F32Traits extends FPTraits { |
| private static _constants: FPConstants = { |
| positive: { |
| min: kValue.f32.positive.min, |
| max: kValue.f32.positive.max, |
| infinity: kValue.f32.positive.infinity, |
| nearest_max: kValue.f32.positive.nearest_max, |
| less_than_one: kValue.f32.positive.less_than_one, |
| subnormal: { |
| min: kValue.f32.positive.subnormal.min, |
| max: kValue.f32.positive.subnormal.max, |
| }, |
| pi: { |
| whole: kValue.f32.positive.pi.whole, |
| three_quarters: kValue.f32.positive.pi.three_quarters, |
| half: kValue.f32.positive.pi.half, |
| third: kValue.f32.positive.pi.third, |
| quarter: kValue.f32.positive.pi.quarter, |
| sixth: kValue.f32.positive.pi.sixth, |
| }, |
| e: kValue.f32.positive.e, |
| }, |
| negative: { |
| min: kValue.f32.negative.min, |
| max: kValue.f32.negative.max, |
| infinity: kValue.f32.negative.infinity, |
| nearest_min: kValue.f32.negative.nearest_min, |
| less_than_one: kValue.f32.negative.less_than_one, |
| subnormal: { |
| min: kValue.f32.negative.subnormal.min, |
| max: kValue.f32.negative.subnormal.max, |
| }, |
| pi: { |
| whole: kValue.f32.negative.pi.whole, |
| three_quarters: kValue.f32.negative.pi.three_quarters, |
| half: kValue.f32.negative.pi.half, |
| third: kValue.f32.negative.pi.third, |
| quarter: kValue.f32.negative.pi.quarter, |
| sixth: kValue.f32.negative.pi.sixth, |
| }, |
| }, |
| bias: 127, |
| unboundedInterval: kF32UnboundedInterval, |
| zeroInterval: kF32ZeroInterval, |
| // Have to use the constants.ts values here, because values defined in the |
| // initializer cannot be referenced in the initializer |
| negPiToPiInterval: new FPInterval( |
| 'f32', |
| kValue.f32.negative.pi.whole, |
| kValue.f32.positive.pi.whole |
| ), |
| greaterThanZeroInterval: new FPInterval( |
| 'f32', |
| kValue.f32.positive.subnormal.min, |
| kValue.f32.positive.max |
| ), |
| negOneToOneInterval: new FPInterval('f32', -1, 1), |
| zeroVector: { |
| 2: [kF32ZeroInterval, kF32ZeroInterval], |
| 3: [kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval], |
| 4: [kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval], |
| }, |
| unboundedVector: { |
| 2: [kF32UnboundedInterval, kF32UnboundedInterval], |
| 3: [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| 4: [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| }, |
| unboundedMatrix: { |
| 2: { |
| 2: [ |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| ], |
| 3: [ |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| ], |
| }, |
| 3: { |
| 2: [ |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| ], |
| 3: [ |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| ], |
| }, |
| 4: { |
| 2: [ |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval], |
| ], |
| 3: [ |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| [ |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| kF32UnboundedInterval, |
| ], |
| ], |
| }, |
| }, |
| }; |
| |
| public constructor() { |
| super('f32'); |
| } |
| |
| public constants(): FPConstants { |
| return F32Traits._constants; |
| } |
| |
| // Utilities - Overrides |
| public readonly quantize = quantizeToF32; |
| public readonly correctlyRounded = correctlyRoundedF32; |
| public readonly isFinite = isFiniteF32; |
| public readonly isSubnormal = isSubnormalNumberF32; |
| public readonly flushSubnormal = flushSubnormalNumberF32; |
| public readonly oneULP = oneULPF32; |
| public readonly scalarBuilder = f32; |
| public readonly scalarRange = scalarF32Range; |
| public readonly sparseScalarRange = sparseScalarF32Range; |
| public readonly vectorRange = vectorF32Range; |
| public readonly sparseVectorRange = sparseVectorF32Range; |
| public readonly sparseMatrixRange = sparseMatrixF32Range; |
| |
| // Framework - Fundamental Error Intervals - Overrides |
| public readonly absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this); |
| public readonly correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this); |
| public readonly correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this); |
| public readonly ulpInterval = this.ulpIntervalImpl.bind(this); |
| |
| // Framework - API - Overrides |
| public readonly absInterval = this.absIntervalImpl.bind(this); |
| public readonly acosInterval = this.acosIntervalImpl.bind(this); |
| public readonly acoshAlternativeInterval = this.acoshAlternativeIntervalImpl.bind(this); |
| public readonly acoshPrimaryInterval = this.acoshPrimaryIntervalImpl.bind(this); |
| public readonly acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval]; |
| public readonly additionInterval = this.additionIntervalImpl.bind(this); |
| public readonly additionMatrixMatrixInterval = this.additionMatrixMatrixIntervalImpl.bind(this); |
| public readonly asinInterval = this.asinIntervalImpl.bind(this); |
| public readonly asinhInterval = this.asinhIntervalImpl.bind(this); |
| public readonly atanInterval = this.atanIntervalImpl.bind(this); |
| public readonly atan2Interval = this.atan2IntervalImpl.bind(this); |
| public readonly atanhInterval = this.atanhIntervalImpl.bind(this); |
| public readonly ceilInterval = this.ceilIntervalImpl.bind(this); |
| public readonly clampMedianInterval = this.clampMedianIntervalImpl.bind(this); |
| public readonly clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this); |
| public readonly clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval]; |
| public readonly cosInterval = this.cosIntervalImpl.bind(this); |
| public readonly coshInterval = this.coshIntervalImpl.bind(this); |
| public readonly crossInterval = this.crossIntervalImpl.bind(this); |
| public readonly degreesInterval = this.degreesIntervalImpl.bind(this); |
| public readonly determinantInterval = this.determinantIntervalImpl.bind(this); |
| public readonly distanceInterval = this.distanceIntervalImpl.bind(this); |
| public readonly divisionInterval = this.divisionIntervalImpl.bind(this); |
| public readonly dotInterval = this.dotIntervalImpl.bind(this); |
| public readonly expInterval = this.expIntervalImpl.bind(this); |
| public readonly exp2Interval = this.exp2IntervalImpl.bind(this); |
| public readonly faceForwardIntervals = this.faceForwardIntervalsImpl.bind(this); |
| public readonly floorInterval = this.floorIntervalImpl.bind(this); |
| public readonly fmaInterval = this.fmaIntervalImpl.bind(this); |
| public readonly fractInterval = this.fractIntervalImpl.bind(this); |
| public readonly inverseSqrtInterval = this.inverseSqrtIntervalImpl.bind(this); |
| public readonly ldexpInterval = this.ldexpIntervalImpl.bind(this); |
| public readonly lengthInterval = this.lengthIntervalImpl.bind(this); |
| public readonly logInterval = this.logIntervalImpl.bind(this); |
| public readonly log2Interval = this.log2IntervalImpl.bind(this); |
| public readonly maxInterval = this.maxIntervalImpl.bind(this); |
| public readonly minInterval = this.minIntervalImpl.bind(this); |
| public readonly mixImpreciseInterval = this.mixImpreciseIntervalImpl.bind(this); |
| public readonly mixPreciseInterval = this.mixPreciseIntervalImpl.bind(this); |
| public readonly mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval]; |
| public readonly modfInterval = this.modfIntervalImpl.bind(this); |
| public readonly multiplicationInterval = this.multiplicationIntervalImpl.bind(this); |
| public readonly multiplicationMatrixMatrixInterval = |
| this.multiplicationMatrixMatrixIntervalImpl.bind(this); |
| public readonly multiplicationMatrixScalarInterval = |
| this.multiplicationMatrixScalarIntervalImpl.bind(this); |
| public readonly multiplicationScalarMatrixInterval = |
| this.multiplicationScalarMatrixIntervalImpl.bind(this); |
| public readonly multiplicationMatrixVectorInterval = |
| this.multiplicationMatrixVectorIntervalImpl.bind(this); |
| public readonly multiplicationVectorMatrixInterval = |
| this.multiplicationVectorMatrixIntervalImpl.bind(this); |
| public readonly negationInterval = this.negationIntervalImpl.bind(this); |
| public readonly normalizeInterval = this.normalizeIntervalImpl.bind(this); |
| public readonly powInterval = this.powIntervalImpl.bind(this); |
| public readonly radiansInterval = this.radiansIntervalImpl.bind(this); |
| public readonly reflectInterval = this.reflectIntervalImpl.bind(this); |
| public readonly refractInterval = this.refractIntervalImpl.bind(this); |
| public readonly remainderInterval = this.remainderIntervalImpl.bind(this); |
| public readonly roundInterval = this.roundIntervalImpl.bind(this); |
| public readonly saturateInterval = this.saturateIntervalImpl.bind(this); |
| public readonly signInterval = this.signIntervalImpl.bind(this); |
| public readonly sinInterval = this.sinIntervalImpl.bind(this); |
| public readonly sinhInterval = this.sinhIntervalImpl.bind(this); |
| public readonly smoothStepInterval = this.smoothStepIntervalImpl.bind(this); |
| public readonly sqrtInterval = this.sqrtIntervalImpl.bind(this); |
| public readonly stepInterval = this.stepIntervalImpl.bind(this); |
| public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this); |
| public readonly subtractionMatrixMatrixInterval = |
| this.subtractionMatrixMatrixIntervalImpl.bind(this); |
| public readonly tanInterval = this.tanIntervalImpl.bind(this); |
| public readonly tanhInterval = this.tanhIntervalImpl.bind(this); |
| public readonly transposeInterval = this.transposeIntervalImpl.bind(this); |
| public readonly truncInterval = this.truncIntervalImpl.bind(this); |
| |
| // Framework - Cases |
| |
| // U32 -> Interval is used for testing f32 specific unpack* functions |
| /** |
| * @returns a Case for the param and the interval generator provided. |
| * The Case will use an interval comparator for matching results. |
| * @param param the param to pass in |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| private makeU32ToVectorCase( |
| param: number, |
| filter: IntervalFilter, |
| ...ops: ScalarToVector[] |
| ): Case | undefined { |
| param = Math.trunc(param); |
| |
| const vectors = ops.map(o => o(param)); |
| if (filter === 'finite' && vectors.some(v => !v.every(e => e.isFinite()))) { |
| return undefined; |
| } |
| return { |
| input: u32(param), |
| expected: anyOf(...vectors), |
| }; |
| } |
| |
| /** |
| * @returns an array of Cases for operations over a range of inputs |
| * @param params array of inputs to try |
| * @param filter what interval filtering to apply |
| * @param ops callbacks that implement generating an acceptance interval |
| */ |
| public generateU32ToIntervalCases( |
| params: readonly number[], |
| filter: IntervalFilter, |
| ...ops: ScalarToVector[] |
| ): Case[] { |
| return params.reduce((cases, e) => { |
| const c = this.makeU32ToVectorCase(e, filter, ...ops); |
| if (c !== undefined) { |
| cases.push(c); |
| } |
| return cases; |
| }, new Array<Case>()); |
| } |
| |
| // Framework - API |
| |
| private readonly QuantizeToF16IntervalOp: ScalarToIntervalOp = { |
| impl: (n: number): FPInterval => { |
| const rounded = correctlyRoundedF16(n); |
| const flushed = addFlushedIfNeededF16(rounded); |
| return this.spanIntervals(...flushed.map(f => this.toInterval(f))); |
| }, |
| }; |
| |
| protected quantizeToF16IntervalImpl(n: number): FPInterval { |
| return this.runScalarToIntervalOp(this.toInterval(n), this.QuantizeToF16IntervalOp); |
| } |
| |
| /** Calculate an acceptance interval of quantizeToF16(x) */ |
| public readonly quantizeToF16Interval = this.quantizeToF16IntervalImpl.bind(this); |
| |
| /** |
| * Once-allocated ArrayBuffer/views to avoid overhead of allocation when |
| * converting between numeric formats |
| * |
| * unpackData* is shared between all the unpack*Interval functions, so to |
| * avoid re-entrancy problems, they should not call each other or themselves |
| * directly or indirectly. |
| */ |
| private readonly unpackData = new ArrayBuffer(4); |
| private readonly unpackDataU32 = new Uint32Array(this.unpackData); |
| private readonly unpackDataU16 = new Uint16Array(this.unpackData); |
| private readonly unpackDataU8 = new Uint8Array(this.unpackData); |
| private readonly unpackDataI16 = new Int16Array(this.unpackData); |
| private readonly unpackDataI8 = new Int8Array(this.unpackData); |
| private readonly unpackDataF16 = new Float16Array(this.unpackData); |
| |
| private unpack2x16floatIntervalImpl(n: number): FPVector { |
| assert( |
| n >= kValue.u32.min && n <= kValue.u32.max, |
| 'unpack2x16floatInterval only accepts valid u32 values' |
| ); |
| this.unpackDataU32[0] = n; |
| if (this.unpackDataF16.some(f => !isFiniteF16(f))) { |
| return [this.constants().unboundedInterval, this.constants().unboundedInterval]; |
| } |
| |
| const result: FPVector = [ |
| this.quantizeToF16Interval(this.unpackDataF16[0]), |
| this.quantizeToF16Interval(this.unpackDataF16[1]), |
| ]; |
| |
| if (result.some(r => !r.isFinite())) { |
| return [this.constants().unboundedInterval, this.constants().unboundedInterval]; |
| } |
| return result; |
| } |
| |
| /** Calculate an acceptance interval vector for unpack2x16float(x) */ |
| public readonly unpack2x16floatInterval = this.unpack2x16floatIntervalImpl.bind(this); |
| |
| private unpack2x16snormIntervalImpl(n: number): FPVector { |
| assert( |
| n >= kValue.u32.min && n <= kValue.u32.max, |
| 'unpack2x16snormInterval only accepts valid u32 values' |
| ); |
| const op = (n: number): FPInterval => { |
| return this.ulpInterval(Math.max(n / 32767, -1), 3); |
| }; |
| |
| this.unpackDataU32[0] = n; |
| return [op(this.unpackDataI16[0]), op(this.unpackDataI16[1])]; |
| } |
| |
| /** Calculate an acceptance interval vector for unpack2x16snorm(x) */ |
| public readonly unpack2x16snormInterval = this.unpack2x16snormIntervalImpl.bind(this); |
| |
| private unpack2x16unormIntervalImpl(n: number): FPVector { |
| assert( |
| n >= kValue.u32.min && n <= kValue.u32.max, |
| 'unpack2x16unormInterval only accepts valid u32 values' |
| ); |
| const op = (n: number): FPInterval => { |
| return this.ulpInterval(n / 65535, 3); |
| }; |
| |
| this.unpackDataU32[0] = n; |
| return [op(this.unpackDataU16[0]), op(this.unpackDataU16[1])]; |
| } |
| |
| /** Calculate an acceptance interval vector for unpack2x16unorm(x) */ |
| public readonly unpack2x16unormInterval = this.unpack2x16unormIntervalImpl.bind(this); |
| |
| private unpack4x8snormIntervalImpl(n: number): FPVector { |
| assert( |
| n >= kValue.u32.min && n <= kValue.u32.max, |
| 'unpack4x8snormInterval only accepts valid u32 values' |
| ); |
| const op = (n: number): FPInterval => { |
| return this.ulpInterval(Math.max(n / 127, -1), 3); |
| }; |
| this.unpackDataU32[0] = n; |
| return [ |
| op(this.unpackDataI8[0]), |
| op(this.unpackDataI8[1]), |
| op(this.unpackDataI8[2]), |
| op(this.unpackDataI8[3]), |
| ]; |
| } |
| |
| /** Calculate an acceptance interval vector for unpack4x8snorm(x) */ |
| public readonly unpack4x8snormInterval = this.unpack4x8snormIntervalImpl.bind(this); |
| |
| private unpack4x8unormIntervalImpl(n: number): FPVector { |
| assert( |
| n >= kValue.u32.min && n <= kValue.u32.max, |
| 'unpack4x8unormInterval only accepts valid u32 values' |
| ); |
| const op = (n: number): FPInterval => { |
| return this.ulpInterval(n / 255, 3); |
| }; |
| |
| this.unpackDataU32[0] = n; |
| return [ |
| op(this.unpackDataU8[0]), |
| op(this.unpackDataU8[1]), |
| op(this.unpackDataU8[2]), |
| op(this.unpackDataU8[3]), |
| ]; |
| } |
| |
| /** Calculate an acceptance interval vector for unpack4x8unorm(x) */ |
| public readonly unpack4x8unormInterval = this.unpack4x8unormIntervalImpl.bind(this); |
| } |
| |
| // Need to separately allocate f32 traits, so they can be referenced by |
| // FPAbstractTraits for forwarding. |
| const kF32Traits = new F32Traits(); |
| |
| // Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this |
| // executes before they are defined. |
| const kAbstractUnboundedInterval = new FPInterval( |
| 'abstract', |
| Number.NEGATIVE_INFINITY, |
| Number.POSITIVE_INFINITY |
| ); |
| const kAbstractZeroInterval = new FPInterval('abstract', 0); |
| |
| // This is implementation is incomplete |
| class FPAbstractTraits extends FPTraits { |
| private static _constants: FPConstants = { |
| positive: { |
| min: kValue.f64.positive.min, |
| max: kValue.f64.positive.max, |
| infinity: kValue.f64.positive.infinity, |
| nearest_max: kValue.f64.positive.nearest_max, |
| less_than_one: kValue.f64.positive.less_than_one, |
| subnormal: { |
| min: kValue.f64.positive.subnormal.min, |
| max: kValue.f64.positive.subnormal.max, |
| }, |
| pi: { |
| whole: kValue.f64.positive.pi.whole, |
| three_quarters: kValue.f64.positive.pi.three_quarters, |
| half: kValue.f64.positive.pi.half, |
| third: kValue.f64.positive.pi.third, |
| quarter: kValue.f64.positive.pi.quarter, |
| sixth: kValue.f64.positive.pi.sixth, |
| }, |
| e: kValue.f64.positive.e, |
| }, |
| negative: { |
| min: kValue.f64.negative.min, |
| max: kValue.f64.negative.max, |
| infinity: kValue.f64.negative.infinity, |
| nearest_min: kValue.f64.negative.nearest_min, |
| less_than_one: kValue.f64.negative.less_than_one, |
| subnormal: { |
| min: kValue.f64.negative.subnormal.min, |
| max: kValue.f64.negative.subnormal.max, |
| }, |
| pi: { |
| whole: kValue.f64.negative.pi.whole, |
| three_quarters: kValue.f64.negative.pi.three_quarters, |
| half: kValue.f64.negative.pi.half, |
| third: kValue.f64.negative.pi.third, |
| quarter: kValue.f64.negative.pi.quarter, |
| sixth: kValue.f64.negative.pi.sixth, |
| }, |
| }, |
| bias: 1023, |
| unboundedInterval: kAbstractUnboundedInterval, |
| zeroInterval: kAbstractZeroInterval, |
| // Have to use the constants.ts values here, because values defined in the |
| // initializer cannot be referenced in the initializer |
| negPiToPiInterval: new FPInterval( |
| 'abstract', |
| kValue.f64.negative.pi.whole, |
| kValue.f64.positive.pi.whole |
| ), |
| greaterThanZeroInterval: new FPInterval( |
| 'abstract', |
| kValue.f64.positive.subnormal.min, |
| kValue.f64.positive.max |
| ), |
| negOneToOneInterval: new FPInterval('abstract', -1, 1), |
| |
| zeroVector: { |
| 2: [kAbstractZeroInterval, kAbstractZeroInterval], |
| 3: [kAbstractZeroInterval, kAbstractZeroInterval, kAbstractZeroInterval], |
| 4: [ |
| kAbstractZeroInterval, |
| kAbstractZeroInterval, |
| kAbstractZeroInterval, |
| kAbstractZeroInterval, |
| ], |
| }, |
| unboundedVector: { |
| 2: [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| 3: [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| 4: [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| }, |
| unboundedMatrix: { |
| 2: { |
| 2: [ |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| ], |
| 3: [ |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| ], |
| }, |
| 3: { |
| 2: [ |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| ], |
| 3: [ |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| ], |
| }, |
| 4: { |
| 2: [ |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| ], |
| 3: [ |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| [ |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| kAbstractUnboundedInterval, |
| ], |
| ], |
| }, |
| }, |
| }; |
| |
| public constructor() { |
| super('abstract'); |
| } |
| |
| public constants(): FPConstants { |
| return FPAbstractTraits._constants; |
| } |
| |
| // Utilities - Overrides |
| // number is represented as a f64 internally, so all number values are already |
| // quantized to f64 |
| public readonly quantize = (n: number) => { |
| return n; |
| }; |
| public readonly correctlyRounded = correctlyRoundedF64; |
| public readonly isFinite = Number.isFinite; |
| public readonly isSubnormal = isSubnormalNumberF64; |
| public readonly flushSubnormal = flushSubnormalNumberF64; |
| public readonly oneULP = (_target: number, _mode: FlushMode = 'flush'): number => { |
| unreachable(`'FPAbstractTraits.oneULP should never be called`); |
| }; |
| public readonly scalarBuilder = abstractFloat; |
| public readonly scalarRange = scalarF64Range; |
| public readonly sparseScalarRange = sparseScalarF64Range; |
| public readonly vectorRange = vectorF64Range; |
| public readonly sparseVectorRange = sparseVectorF64Range; |
| public readonly sparseMatrixRange = sparseMatrixF64Range; |
| |
| // Framework - Fundamental Error Intervals - Overrides |
| public readonly absoluteErrorInterval = this.unimplementedAbsoluteErrorInterval.bind(this); // Should use FP.f32 instead |
| public readonly correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this); |
| public readonly correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this); |
| public readonly ulpInterval = this.unimplementedUlpInterval.bind(this); // Should use FP.f32 instead |
| |
| // Framework - API - Overrides |
| public readonly absInterval = this.absIntervalImpl.bind(this); |
| public readonly acosInterval = this.unimplementedScalarToInterval.bind(this, 'acosInterval'); |
| public readonly acoshAlternativeInterval = this.unimplementedScalarToInterval.bind( |
| this, |
| 'acoshAlternativeInterval' |
| ); |
| public readonly acoshPrimaryInterval = this.unimplementedScalarToInterval.bind( |
| this, |
| 'acoshPrimaryInterval' |
| ); |
| public readonly acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval]; |
| public readonly additionInterval = this.unimplementedScalarPairToInterval.bind( |
| this, |
| 'additionInterval' |
| ); |
| public readonly additionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind( |
| this, |
| 'additionMatrixMatrixInterval' |
| ); |
| public readonly asinInterval = this.unimplementedScalarToInterval.bind(this, 'asinInterval'); |
| public readonly asinhInterval = this.unimplementedScalarToInterval.bind(this, 'asinhInterval'); |
| public readonly atanInterval = this.unimplementedScalarToInterval.bind(this, 'atanInterval'); |
| public readonly atan2Interval = this.unimplementedScalarPairToInterval.bind( |
| this, |
| 'atan2Interval' |
| ); |
| public readonly atanhInterval = this.unimplementedScalarToInterval.bind(this, 'atanhInterval'); |
| public readonly ceilInterval = this.ceilIntervalImpl.bind(this); |
| public readonly clampMedianInterval = this.clampMedianIntervalImpl.bind(this); |
| public readonly clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this); |
| public readonly clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval]; |
| public readonly cosInterval = this.unimplementedScalarToInterval.bind(this, 'cosInterval'); |
| public readonly coshInterval = this.unimplementedScalarToInterval.bind(this, 'coshInterval'); |
| public readonly crossInterval = this.unimplementedVectorPairToVector.bind(this, 'crossInterval'); |
| public readonly degreesInterval = this.unimplementedScalarToInterval.bind( |
| this, |
| 'degreesInterval' |
| ); |
| public readonly determinantInterval = this.unimplementedMatrixToInterval.bind( |
| this, |
| 'determinant' |
| ); |
| public readonly distanceInterval = this.unimplementedDistance.bind(this); |
| public readonly divisionInterval = this.unimplementedScalarPairToInterval.bind( |
| this, |
| 'divisionInterval' |
| ); |
| public readonly dotInterval = this.unimplementedVectorPairToInterval.bind(this, 'dotInterval'); |
| public readonly expInterval = this.unimplementedScalarToInterval.bind(this, 'expInterval'); |
| public readonly exp2Interval = this.unimplementedScalarToInterval.bind(this, 'exp2Interval'); |
| public readonly faceForwardIntervals = this.unimplementedFaceForward.bind(this); |
| public readonly floorInterval = this.floorIntervalImpl.bind(this); |
| public readonly fmaInterval = this.unimplementedScalarTripleToInterval.bind(this, 'fmaInterval'); |
| public readonly fractInterval = this.unimplementedScalarToInterval.bind(this, 'fractInterval'); |
| public readonly inverseSqrtInterval = this.unimplementedScalarToInterval.bind( |
| this, |
| 'inverseSqrtInterval' |
| ); |
| public readonly ldexpInterval = this.ldexpIntervalImpl.bind(this); |
| public readonly lengthInterval = this.unimplementedLength.bind(this); |
| public readonly logInterval = this.unimplementedScalarToInterval.bind(this, 'logInterval'); |
| public readonly log2Interval = this.unimplementedScalarToInterval.bind(this, 'log2Interval'); |
| public readonly maxInterval = this.maxIntervalImpl.bind(this); |
| public readonly minInterval = this.minIntervalImpl.bind(this); |
| public readonly mixImpreciseInterval = this.unimplementedScalarTripleToInterval.bind( |
| this, |
| 'mixImpreciseInterval' |
| ); |
| public readonly mixPreciseInterval = this.unimplementedScalarTripleToInterval.bind( |
| this, |
| 'mixPreciseInterval' |
| ); |
| public readonly mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval]; |
| public readonly modfInterval = this.modfIntervalImpl.bind(this); |
| public readonly multiplicationInterval = this.unimplementedScalarPairToInterval.bind( |
| this, |
| 'multiplicationInterval' |
| ); |
| public readonly multiplicationMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind( |
| this, |
| 'multiplicationMatrixMatrixInterval' |
| ); |
| public readonly multiplicationMatrixScalarInterval = this.unimplementedMatrixScalarToMatrix.bind( |
| this, |
| 'multiplicationMatrixScalarInterval' |
| ); |
| public readonly multiplicationScalarMatrixInterval = this.unimplementedScalarMatrixToMatrix.bind( |
| this, |
| 'multiplicationScalarMatrixInterval' |
| ); |
| public readonly multiplicationMatrixVectorInterval = this.unimplementedMatrixVectorToVector.bind( |
| this, |
| 'multiplicationMatrixVectorInterval' |
| ); |
| public readonly multiplicationVectorMatrixInterval = this.unimplementedVectorMatrixToVector.bind( |
| this, |
| 'multiplicationVectorMatrixInterval' |
| ); |
| public readonly negationInterval = this.negationIntervalImpl.bind(this); |
| public readonly normalizeInterval = this.unimplementedVectorToVector.bind( |
| this, |
| 'normalizeInterval' |
| ); |
| public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this, 'powInterval'); |
| public readonly radiansInterval = this.unimplementedScalarToInterval.bind(this, 'radiansImpl'); |
| public readonly reflectInterval = this.unimplementedVectorPairToVector.bind( |
| this, |
| 'reflectInterval' |
| ); |
| public readonly refractInterval = this.unimplementedRefract.bind(this); |
| public readonly remainderInterval = this.unimplementedScalarPairToInterval.bind( |
| this, |
| 'remainderInterval' |
| ); |
| public readonly roundInterval = this.roundIntervalImpl.bind(this); |
| public readonly saturateInterval = this.saturateIntervalImpl.bind(this); |
| public readonly signInterval = this.signIntervalImpl.bind(this); |
| public readonly sinInterval = this.unimplementedScalarToInterval.bind(this, 'sinInterval'); |
| public readonly sinhInterval = this.unimplementedScalarToInterval.bind(this, 'sinhInterval'); |
| public readonly smoothStepInterval = this.unimplementedScalarTripleToInterval.bind( |
| this, |
| 'smoothStepInterval' |
| ); |
| public readonly sqrtInterval = this.unimplementedScalarToInterval.bind(this, 'sqrtInterval'); |
| public readonly stepInterval = this.stepIntervalImpl.bind(this); |
| public readonly subtractionInterval = this.unimplementedScalarPairToInterval.bind( |
| this, |
| 'subtractionInterval' |
| ); |
| public readonly subtractionMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind( |
| this, |
| 'subtractionMatrixMatrixInterval' |
| ); |
| public readonly tanInterval = this.unimplementedScalarToInterval.bind(this, 'tanInterval'); |
| public readonly tanhInterval = this.unimplementedScalarToInterval.bind(this, 'tanhInterval'); |
| public readonly transposeInterval = this.transposeIntervalImpl.bind(this); |
| public readonly truncInterval = this.truncIntervalImpl.bind(this); |
| } |
| |
| // Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this |
| // executes before they are defined. |
| const kF16UnboundedInterval = new FPInterval( |
| 'f16', |
| Number.NEGATIVE_INFINITY, |
| Number.POSITIVE_INFINITY |
| ); |
| const kF16ZeroInterval = new FPInterval('f16', 0); |
| |
| // This is implementation is incomplete |
| class F16Traits extends FPTraits { |
| private static _constants: FPConstants = { |
| positive: { |
| min: kValue.f16.positive.min, |
| max: kValue.f16.positive.max, |
| infinity: kValue.f16.positive.infinity, |
| nearest_max: kValue.f16.positive.nearest_max, |
| less_than_one: kValue.f16.positive.less_than_one, |
| subnormal: { |
| min: kValue.f16.positive.subnormal.min, |
| max: kValue.f16.positive.subnormal.max, |
| }, |
| pi: { |
| whole: kValue.f16.positive.pi.whole, |
| three_quarters: kValue.f16.positive.pi.three_quarters, |
| half: kValue.f16.positive.pi.half, |
| third: kValue.f16.positive.pi.third, |
| quarter: kValue.f16.positive.pi.quarter, |
| sixth: kValue.f16.positive.pi.sixth, |
| }, |
| e: kValue.f16.positive.e, |
| }, |
| negative: { |
| min: kValue.f16.negative.min, |
| max: kValue.f16.negative.max, |
| infinity: kValue.f16.negative.infinity, |
| nearest_min: kValue.f16.negative.nearest_min, |
| less_than_one: kValue.f16.negative.less_than_one, |
| subnormal: { |
| min: kValue.f16.negative.subnormal.min, |
| max: kValue.f16.negative.subnormal.max, |
| }, |
| pi: { |
| whole: kValue.f16.negative.pi.whole, |
| three_quarters: kValue.f16.negative.pi.three_quarters, |
| half: kValue.f16.negative.pi.half, |
| third: kValue.f16.negative.pi.third, |
| quarter: kValue.f16.negative.pi.quarter, |
| sixth: kValue.f16.negative.pi.sixth, |
| }, |
| }, |
| bias: 15, |
| unboundedInterval: kF16UnboundedInterval, |
| zeroInterval: kF16ZeroInterval, |
| // Have to use the constants.ts values here, because values defined in the |
| // initializer cannot be referenced in the initializer |
| negPiToPiInterval: new FPInterval( |
| 'f16', |
| kValue.f16.negative.pi.whole, |
| kValue.f16.positive.pi.whole |
| ), |
| greaterThanZeroInterval: new FPInterval( |
| 'f16', |
| kValue.f16.positive.subnormal.min, |
| kValue.f16.positive.max |
| ), |
| negOneToOneInterval: new FPInterval('f16', -1, 1), |
| |
| zeroVector: { |
| 2: [kF16ZeroInterval, kF16ZeroInterval], |
| 3: [kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval], |
| 4: [kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval], |
| }, |
| unboundedVector: { |
| 2: [kF16UnboundedInterval, kF16UnboundedInterval], |
| 3: [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| 4: [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| }, |
| unboundedMatrix: { |
| 2: { |
| 2: [ |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| ], |
| 3: [ |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| ], |
| }, |
| 3: { |
| 2: [ |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| ], |
| 3: [ |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| ], |
| }, |
| 4: { |
| 2: [ |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval], |
| ], |
| 3: [ |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval], |
| ], |
| 4: [ |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| [ |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| kF16UnboundedInterval, |
| ], |
| ], |
| }, |
| }, |
| }; |
| |
| public constructor() { |
| super('f16'); |
| } |
| |
| public constants(): FPConstants { |
| return F16Traits._constants; |
| } |
| |
| // Utilities - Overrides |
| public readonly quantize = quantizeToF16; |
| public readonly correctlyRounded = correctlyRoundedF16; |
| public readonly isFinite = isFiniteF16; |
| public readonly isSubnormal = isSubnormalNumberF16; |
| public readonly flushSubnormal = flushSubnormalNumberF16; |
| public readonly oneULP = oneULPF16; |
| public readonly scalarBuilder = f16; |
| public readonly scalarRange = scalarF16Range; |
| public readonly sparseScalarRange = sparseScalarF16Range; |
| public readonly vectorRange = vectorF16Range; |
| public readonly sparseVectorRange = sparseVectorF16Range; |
| public readonly sparseMatrixRange = sparseMatrixF16Range; |
| |
| // Framework - Fundamental Error Intervals - Overrides |
| public readonly absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this); |
| public readonly correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this); |
| public readonly correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this); |
| public readonly ulpInterval = this.ulpIntervalImpl.bind(this); |
| |
| // Framework - API - Overrides |
| public readonly absInterval = this.absIntervalImpl.bind(this); |
| public readonly acosInterval = this.acosIntervalImpl.bind(this); |
| public readonly acoshAlternativeInterval = this.acoshAlternativeIntervalImpl.bind(this); |
| public readonly acoshPrimaryInterval = this.acoshPrimaryIntervalImpl.bind(this); |
| public readonly acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval]; |
| public readonly additionInterval = this.additionIntervalImpl.bind(this); |
| public readonly additionMatrixMatrixInterval = this.additionMatrixMatrixIntervalImpl.bind(this); |
| public readonly asinInterval = this.asinIntervalImpl.bind(this); |
| public readonly asinhInterval = this.asinhIntervalImpl.bind(this); |
| public readonly atanInterval = this.atanIntervalImpl.bind(this); |
| public readonly atan2Interval = this.atan2IntervalImpl.bind(this); |
| public readonly atanhInterval = this.atanhIntervalImpl.bind(this); |
| public readonly ceilInterval = this.ceilIntervalImpl.bind(this); |
| public readonly clampMedianInterval = this.clampMedianIntervalImpl.bind(this); |
| public readonly clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this); |
| public readonly clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval]; |
| public readonly cosInterval = this.cosIntervalImpl.bind(this); |
| public readonly coshInterval = this.coshIntervalImpl.bind(this); |
| public readonly crossInterval = this.crossIntervalImpl.bind(this); |
| public readonly degreesInterval = this.degreesIntervalImpl.bind(this); |
| public readonly determinantInterval = this.determinantIntervalImpl.bind(this); |
| public readonly distanceInterval = this.distanceIntervalImpl.bind(this); |
| public readonly divisionInterval = this.divisionIntervalImpl.bind(this); |
| public readonly dotInterval = this.dotIntervalImpl.bind(this); |
| public readonly expInterval = this.expIntervalImpl.bind(this); |
| public readonly exp2Interval = this.exp2IntervalImpl.bind(this); |
| public readonly faceForwardIntervals = this.faceForwardIntervalsImpl.bind(this); |
| public readonly floorInterval = this.floorIntervalImpl.bind(this); |
| public readonly fmaInterval = this.fmaIntervalImpl.bind(this); |
| public readonly fractInterval = this.fractIntervalImpl.bind(this); |
| public readonly inverseSqrtInterval = this.inverseSqrtIntervalImpl.bind(this); |
| public readonly ldexpInterval = this.ldexpIntervalImpl.bind(this); |
| public readonly lengthInterval = this.lengthIntervalImpl.bind(this); |
| public readonly logInterval = this.logIntervalImpl.bind(this); |
| public readonly log2Interval = this.log2IntervalImpl.bind(this); |
| public readonly maxInterval = this.maxIntervalImpl.bind(this); |
| public readonly minInterval = this.minIntervalImpl.bind(this); |
| public readonly mixImpreciseInterval = this.mixImpreciseIntervalImpl.bind(this); |
| public readonly mixPreciseInterval = this.mixPreciseIntervalImpl.bind(this); |
| public readonly mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval]; |
| public readonly modfInterval = this.modfIntervalImpl.bind(this); |
| public readonly multiplicationInterval = this.multiplicationIntervalImpl.bind(this); |
| public readonly multiplicationMatrixMatrixInterval = |
| this.multiplicationMatrixMatrixIntervalImpl.bind(this); |
| public readonly multiplicationMatrixScalarInterval = |
| this.multiplicationMatrixScalarIntervalImpl.bind(this); |
| public readonly multiplicationScalarMatrixInterval = |
| this.multiplicationScalarMatrixIntervalImpl.bind(this); |
| public readonly multiplicationMatrixVectorInterval = |
| this.multiplicationMatrixVectorIntervalImpl.bind(this); |
| public readonly multiplicationVectorMatrixInterval = |
| this.multiplicationVectorMatrixIntervalImpl.bind(this); |
| public readonly negationInterval = this.negationIntervalImpl.bind(this); |
| public readonly normalizeInterval = this.normalizeIntervalImpl.bind(this); |
| public readonly powInterval = this.powIntervalImpl.bind(this); |
| public readonly radiansInterval = this.radiansIntervalImpl.bind(this); |
| public readonly reflectInterval = this.reflectIntervalImpl.bind(this); |
| public readonly refractInterval = this.refractIntervalImpl.bind(this); |
| public readonly remainderInterval = this.remainderIntervalImpl.bind(this); |
| public readonly roundInterval = this.roundIntervalImpl.bind(this); |
| public readonly saturateInterval = this.saturateIntervalImpl.bind(this); |
| public readonly signInterval = this.signIntervalImpl.bind(this); |
| public readonly sinInterval = this.sinIntervalImpl.bind(this); |
| public readonly sinhInterval = this.sinhIntervalImpl.bind(this); |
| public readonly smoothStepInterval = this.smoothStepIntervalImpl.bind(this); |
| public readonly sqrtInterval = this.sqrtIntervalImpl.bind(this); |
| public readonly stepInterval = this.stepIntervalImpl.bind(this); |
| public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this); |
| public readonly subtractionMatrixMatrixInterval = |
| this.subtractionMatrixMatrixIntervalImpl.bind(this); |
| public readonly tanInterval = this.tanIntervalImpl.bind(this); |
| public readonly tanhInterval = this.tanhIntervalImpl.bind(this); |
| public readonly transposeInterval = this.transposeIntervalImpl.bind(this); |
| public readonly truncInterval = this.truncIntervalImpl.bind(this); |
| } |
| |
| export const FP = { |
| f32: kF32Traits, |
| f16: new F16Traits(), |
| abstract: new FPAbstractTraits(), |
| }; |
| |
| /** @returns the floating-point traits for `type` */ |
| export function fpTraitsFor(type: ScalarType): FPTraits { |
| switch (type.kind) { |
| case 'abstract-float': |
| return FP.abstract; |
| case 'f32': |
| return FP.f32; |
| case 'f16': |
| return FP.f16; |
| default: |
| unreachable(`unsupported type: ${type}`); |
| } |
| } |
| |
| /** @returns true if the value `value` is representable with `type` */ |
| export function isRepresentable(value: number, type: ScalarType) { |
| if (!Number.isFinite(value)) { |
| return false; |
| } |
| if (isFloatType(type)) { |
| const constants = fpTraitsFor(type).constants(); |
| return value >= constants.negative.min && value <= constants.positive.max; |
| } |
| |
| assert(false, `isRepresentable() is not yet implemented for type ${type}`); |
| } |