diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts new file mode 100644 index 0000000..8fc32fb --- /dev/null +++ b/src/Complex/arithmetic.ts @@ -0,0 +1,139 @@ +import {Complex, UnderlyingReal, complex_binary} from './type.js' +import { + BBinary, Dependency, ConservativeUnary, ConservativeBinary, ImpType +} from '../core/Dispatcher.js' + +declare module "./type" { + interface ComplexReturn { + add: ConservativeBinary> + addReal: Params extends [infer Z, infer R] + ? [R] extends [UnderlyingReal] ? Z : never + : never + unaryMinus: ConservativeUnary> + conj: ConservativeUnary> + subtract: ConservativeBinary> + multiply: ConservativeBinary> + absquare: Params extends [infer Z] + ? Z extends Complex ? UnderlyingReal : never + : never + reciprocal: ConservativeUnary> + divide: ConservativeBinary> + divideByReal: Params extends [infer Z, infer R] + ? [R] extends [UnderlyingReal] ? Z : never + : never + // square root that remains the same type + conservativeSqrt: ConservativeUnary> + // Same as conservativeSqrt for complex numbers: + sqrt: ConservativeUnary> + + // complex square root of the real type of a complex: + complexSqrt: Params extends [infer T] ? Complex : never + } +} + +export const add = + (dep: Dependency<'add', [T,T]>): + ImpType<'add', [Complex, Complex]> => + (w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im)) + +export const addReal = + (dep: Dependency<'addReal', [T, UnderlyingReal]>): + ImpType<'addReal', [Complex, UnderlyingReal]> => + (z, r) => complex_binary(dep.addReal(z.re, r), z.im) + +export const unaryMinus = + (dep: Dependency<'unaryMinus', [T]>): + ImpType<'unaryMinus', [Complex]> => + z => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + +export const conj = + (dep: Dependency<'unaryMinus'|'conj', [T]>): + ImpType<'conj', [Complex]> => + z => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) + +export const subtract = + (dep: Dependency<'subtract', [T,T]>): + ImpType<'subtract', [Complex, Complex]> => + (w, z) => complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) + +export const multiply = + (dep: Dependency<'add', [T,T]> + & Dependency<'subtract', [T,T]> + & Dependency<'multiply', [T,T]> + & Dependency<'conj', [T]>): + ImpType<'multiply', [Complex, Complex]> => + (w, z) => { + const mult = dep.multiply + const realpart = dep.subtract( + mult( w.re, z.re), mult(dep.conj(w.im), z.im)) + const imagpart = dep.add( + mult(dep.conj(w.re), z.im), mult( w.im, z.re)) + return complex_binary(realpart, imagpart) + } + +export const absquare = + (dep: Dependency<'absquare', [T]> + & Dependency<'add', BBinary>>): + ImpType<'absquare', [Complex]> => + z => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + +export const divideByReal = + (dep: Dependency<'divideByReal', [T, UnderlyingReal]>): + ImpType<'divideByReal', [Complex, UnderlyingReal]> => + (z, r) => complex_binary( + dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + +export const reciprocal = + (dep: Dependency<'conj', [Complex]> + & Dependency<'absquare', [Complex]> + & Dependency<'divideByReal', [Complex, UnderlyingReal]>): + ImpType<'reciprocal', [Complex]> => + z => dep.divideByReal(dep.conj(z), dep.absquare(z)) + +export const divide = + (dep: Dependency<'multiply', [Complex, Complex]> + & Dependency<'reciprocal', [Complex]>): + ImpType<'divide', [Complex, Complex]> => + (w, z) => dep.multiply(w, dep.reciprocal(z)) + +export const complexSqrt = + (dep: Dependency<'conservativeSqrt', [T]> + & Dependency<'isSquare', [T]> + & Dependency<'complex', [T]> + & Dependency<'unaryMinus', [T]> + & Dependency<'zero', [T]> + & Dependency<'nan', [Complex]>): ImpType<'complexSqrt', [T]> => + r => { + if (dep.isSquare(r)) return dep.complex(dep.conservativeSqrt(r)) + const negative = dep.unaryMinus(r) + if (dep.isSquare(negative)) { + return complex_binary( + dep.zero(r), dep.conservativeSqrt(negative)) + } + // neither the real number or its negative is a square; could happen + // for example with bigint. So there is no square root. So we have to + // return the NaN of the type. + return dep.nan(dep.complex(r)) + } + +export const sqrt = + (dep: Dependency<'isReal', [Complex]> + & Dependency<'complexSqrt', [T]> + & Dependency<'absquare', [Complex]> + & Dependency<'conservativeSqrt', [UnderlyingReal]> + & Dependency<'addReal', [Complex,UnderlyingReal]> + & Dependency<'re', [Complex]> + & Dependency<'add', [UnderlyingReal,UnderlyingReal]> + & Dependency<'divideByReal', [Complex,UnderlyingReal]> + ): ImpType<'sqrt', [Complex]> => + z => { + if (dep.isReal(z)) return dep.complexSqrt(z.re) + const myabs = dep.conservativeSqrt(dep.absquare(z)) + const num = dep.addReal(z, myabs) + const r = dep.re(z) + const denomsq = dep.add(dep.add(myabs, myabs), dep.add(r, r)) + const denom = dep.conservativeSqrt(denomsq) + return dep.divideByReal(num, denom) + } + +export const conservativeSqrt = sqrt diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts new file mode 100644 index 0000000..eafc5ad --- /dev/null +++ b/src/Complex/predicate.ts @@ -0,0 +1,19 @@ +import {Complex} from './type.js' +import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' + +declare module "./type" { + interface ComplexReturn { + isReal: Signature], boolean> + isSquare: Signature], boolean> + } +} + +export const isReal = + (dep: Dependency<'equal', [T,T]> + & Dependency<'add', [T,T]> + & Dependency<'isReal', [T]> + ): ImpType<'isReal', [Complex]> => + z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) + +export const isSquare: ImpType<'isSquare', [Complex]> = + z => true // FIXME: not correct for Complex once we get there diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts new file mode 100644 index 0000000..2a57dc4 --- /dev/null +++ b/src/Complex/relational.ts @@ -0,0 +1,15 @@ +import {Complex} from './type.js' +import {BBinary, ImpType, Dependency} from '../core/Dispatcher.js' + +declare module "./type" { + interface ComplexReturn { + equal: Params extends BBinary + ? B extends Complex ? boolean : never + : never + } +} + +export const equal = + (dep: Dependency<'equal', [T,T]>): + ImpType<'equal', [Complex, Complex]> => + (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index b0bb5b7..a150700 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -4,6 +4,9 @@ import { export type Complex = {re: T; im: T;} +export type UnderlyingReal = + T extends Complex ? UnderlyingReal : T + export const Complex_type = { test: (dep: {testT: (z: unknown) => z is T}) => (z: unknown): z is Complex => @@ -25,7 +28,26 @@ export interface ComplexReturn { // can't take and use generic parameters, only fully instantiated types. complex: Params extends [infer U] ? Complex // unary case : Params extends BBinary ? Complex // binary case - : never + : never + + zero: Params extends [infer Z] // unary + ? Z extends Complex // of a Complex parameter + ? ImpReturns<'zero', T> extends T ? Z : never // that has its real 0 + : never + : never + one: Params extends [infer Z] // unary + ? Z extends Complex // of a Complex parameter + ? ImpReturns<'one'|'zero', T> extends T ? Z : never // has real 1, 0 + : never + : never + nan: Params extends [infer Z] // unary + ? Z extends Complex // of a Complex parameter + ? ImpReturns<'nan', T> extends T ? Z : never // has real NaN + : never + : never + re: Params extends [infer Z] + ? Z extends Complex ? UnderlyingReal : never + : never } export const complex_unary = @@ -33,3 +55,20 @@ export const complex_unary = t => ({re: t, im: dep.zero(t)}) export const complex_binary = (t: T, u: T): ImpReturns<'complex', [T,T]> => ({re: t, im: u}) + +export const zero = + (dep: Dependency<'zero', [T]>): ImpType<'zero', [Complex]> => + z => complex_binary(dep.zero(z.re), dep.zero(z.im)) + +export const one = + (dep: Dependency<'zero' | 'one', [T]>): ImpType<'one', [Complex]> => + z => // Must provide parameter T, else TS narrows to return type of dep.one + complex_binary(dep.one(z.re), dep.zero(z.im)) + +export const nan = + (dep: Dependency<'nan', [T]>): ImpType<'nan', [Complex]> => + z => complex_binary(dep.nan(z.re), dep.nan(z.im)) + +export const re = + (dep: Dependency<'re', [T]>): ImpType<'re', [Complex]> => + z => dep.re(z.re) diff --git a/src/core/Config.ts b/src/core/Config.ts index 3765328..c1eb24c 100644 --- a/src/core/Config.ts +++ b/src/core/Config.ts @@ -1,4 +1,5 @@ export type Config = { + epsilon: number predictable: boolean } diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index b8d7c60..aa4c6a9 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -66,12 +66,24 @@ export interface ReturnTypes {} export type Signature = CandidateParams extends ActualParams ? Returns : never -// A homogenous binary operation (comes up a lot, needs a better name?) +// A homogeneous binary parameter tuple (comes up a lot, needs a better name?) // Typical usage: `foo_impl: Params extends BBinary ? B : never` // says that this implementation takes two arguments, both of type B, and // returns the same type. export type BBinary = [B, B] +// A unary signature that preserves the type of its argument, which must +// extend the given Bound: +export type ConservativeUnary = + CandidateParams extends [infer T] ? T extends Bound ? T : never : never + +// A homogeneous binary signature that preserves the common type of its +// arguments, which must extend the given Bound: +export type ConservativeBinary = + CandidateParams extends BBinary + ? B extends Bound ? B : never + : never + // Helper for collecting return types // (Really just adds the literal string Suffix onto the keys of interface IFace) export type ForType = keyof IFace extends string diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index 9bca0ab..b02b09f 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,25 +1,43 @@ import {configDependency} from '../core/Config.js' -import {Signature, Dependency, ImpType} from '../core/Dispatcher.js' -import type {Complex} from '../Complex/type.js' +import { + Signature, ConservativeBinary, ConservativeUnary, Dependency, ImpType +} from '../core/Dispatcher.js' +import type {Complex, UnderlyingReal} from '../Complex/type.js' declare module "./type" { interface NumbersReturn { // This description loses information: some subtypes like NumInt or // Positive are closed under addition, but this says that the result // of add is just a number, not still of the reduced type - add: Signature - // Whereas this one would preserve information, but would lie + // add: Signature + + // Whereas this one preserves information, but lies // because it claims all subtypes of number are closed under addition, - // which is not true for `1 | 2 | 3`, for example. - // add: Params extends BBinary - // ? B extends number ? B : never - // : never - // + // which is not true for `1 | 2 | 3`, for example. But because in + // generics that use add we often need to assign the result of add + // to something of the exact generic type, generics using add won't + // compile unless we lie in this way and assert that add returns + // the subtype. + add: ConservativeBinary // Not sure how this will need to go when we introduce NumInt. - unaryMinus: Signature - subtract: Signature - multiply: Signature - divide: Signature + + addReal: Params extends [infer R, infer S] + ? R extends number ? S extends R ? R : never : never + : never + unaryMinus: ConservativeUnary + conj: ConservativeUnary + subtract: ConservativeBinary + multiply: ConservativeBinary + absquare: Params extends [infer R] + ? R extends number ? UnderlyingReal : never + : never + reciprocal: ConservativeUnary + divide: ConservativeBinary + divideByReal: Params extends [infer R, infer S] + ? R extends number ? S extends R ? R : never : never + : never + // best square root that remains the same type + conservativeSqrt: ConservativeUnary // Best we can do for sqrt at compile time, since actual return // type depends on config. Not sure how this will play out // when we make a number-only bundle, but at least the import type @@ -29,16 +47,23 @@ declare module "./type" { } export const add: ImpType<'add', [number, number]> = (a, b) => a + b +export const addReal = add export const unaryMinus: ImpType<'unaryMinus', [number]> = a => -a +export const conj: ImpType<'conj', [number]> = a => a export const subtract: ImpType<'subtract', [number, number]> = (a, b) => a - b export const multiply: ImpType<'multiply', [number, number]> = (a, b) => a * b +export const absquare: ImpType<'absquare', [number]> = a => a*a +export const reciprocal: ImpType<'reciprocal', [number]> = a => 1/a export const divide: ImpType<'divide', [number, number]> = (a, b) => a / b +export const divideByReal: ImpType<'divideByReal', [number, number]> = divide + +export const conservativeSqrt: ImpType<'conservativeSqrt', [number]> = + a => isNaN(a) ? NaN : Math.sqrt(a) + export const sqrt = (dep: configDependency & Dependency<'complex', [number, number]>): ImpType<'sqrt', [number]> => { - if (dep.config.predictable || !dep.complex) { - return a => isNaN(a) ? NaN : Math.sqrt(a) - } + if (dep.config.predictable || !dep.complex) return conservativeSqrt return a => { if (isNaN(a)) return NaN if (a >= 0) return Math.sqrt(a) diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts new file mode 100644 index 0000000..b8cc4c5 --- /dev/null +++ b/src/numbers/predicate.ts @@ -0,0 +1,11 @@ +import {Signature, ImpType} from '../core/Dispatcher.js' + +declare module "./type" { + interface NumbersReturn { + isReal: Signature + isSquare: Signature + } +} + +export const isReal: ImpType<'isReal', [number]> = a => true +export const isSquare: ImpType<'isSquare', [number]> = a => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts new file mode 100644 index 0000000..cd0a418 --- /dev/null +++ b/src/numbers/relational.ts @@ -0,0 +1,27 @@ +import {configDependency} from '../core/Config.js' +import {Signature, ImpType} from '../core/Dispatcher.js' + +const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 + +declare module "./type" { + interface NumbersReturn { + equal: Signature + } +} + +export const equal = + (dep: configDependency): ImpType<'equal', [number, number]> => + (x, y) => { + const eps = dep.config.epsilon + if (eps === null || eps === undefined) return x === y + if (x === y) return true + if (isNaN(x) || isNaN(y)) return false + + if (isFinite(x) && isFinite(y)) { + const diff = Math.abs(x - y) + if (diff < DBL_EPSILON) return true + return diff <= Math.max(Math.abs(x), Math.abs(y)) * eps + } + + return false + } diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 105ca46..46971fc 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,5 @@ import {ImpType} from '../core/Dispatcher.js' +import type {UnderlyingReal} from '../Complex/type.js' export const number_type = { before: ['Complex'], @@ -25,6 +26,19 @@ export interface NumbersReturn { // zero: Signature // makes complex fail to compile, because it worries that you might be // making `Complex` where zero would not return the right type. + + one: Params extends [infer T] + ? T extends number ? 1 extends T ? 1 : never : never + : never + nan: Params extends [infer T] + ? T extends number ? typeof NaN extends T ? typeof NaN : never : never + : never + re: Params extends [infer T] + ? T extends number ? UnderlyingReal : never + : never } export const zero: ImpType<'zero', [number]> = a => 0 +export const one: ImpType<'one', [number]> = a => 1 +export const nan: ImpType<'nan', [number]> = a => NaN +export const re: ImpType<'re', [number]> = a => a