diff --git a/.gitignore b/.gitignore index 0dc139f..c3b8f6b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # Typescript # emitted code obj +build # ---> Node # Logs @@ -129,6 +130,9 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +# Webstiorm +.idea + # yarn v2 .yarn/cache .yarn/unplugged diff --git a/README.md b/README.md index 34649c2..1c75e65 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # typocomath -A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. \ No newline at end of file +A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state. diff --git a/package.json5 b/package.json5 index 874d5e3..5c19d9e 100644 --- a/package.json5 +++ b/package.json5 @@ -4,7 +4,7 @@ description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept', main: 'index.ts', scripts: { - test: 'echo "Error: no test specified" && exit 1', + go: 'tsc && node build/infer.js' }, keywords: [ 'math', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 095704b..c9669ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,14 +1,17 @@ -lockfileVersion: 5.4 +lockfileVersion: '6.0' -specifiers: - typescript: ^4.9.3 +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false devDependencies: - typescript: 4.9.3 + typescript: + specifier: ^4.9.3 + version: 4.9.3 packages: - /typescript/4.9.3: + /typescript@4.9.3: resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==} engines: {node: '>=4.2.0'} hasBin: true diff --git a/src/Complex/all.ts b/src/Complex/all.ts index 9d417b8..3ff6311 100644 --- a/src/Complex/all.ts +++ b/src/Complex/all.ts @@ -1,8 +1,6 @@ -import {ForType} from '../core/Dispatcher.js' import * as Complex from './native.js' +import * as complex from './arithmetic.js' + +export { complex } export {Complex} - -declare module "../core/Dispatcher" { - interface ImplementationTypes extends ForType<'Complex', typeof Complex> {} -} diff --git a/src/Complex/arithmetic.ts b/src/Complex/arithmetic.ts new file mode 100644 index 0000000..a9d0d42 --- /dev/null +++ b/src/Complex/arithmetic.ts @@ -0,0 +1,129 @@ +import {Complex, complex_binary, FnComplexUnary} from './type.js' +import type { + FnAbsSquare, + FnAdd, + FnAddReal, + FnConj, FnConservativeSqrt, FnDivide, + FnDivideByReal, FnIsReal, FnIsSquare, + FnMultiply, FnNaN, FnRe, FnReciprocal, FnSqrt, + FnSubtract, + FnUnaryMinus, FnZero +} from '../interfaces/arithmetic' + +export const add = + (dep: { + add: FnAdd + }): FnAdd> => + (w, z) => complex_binary(dep.add(w.re, z.re), dep.add(w.im, z.im)) + +export const addReal = + (dep: { + addReal: FnAddReal + }): FnAddReal, T> => + (z, r) => complex_binary(dep.addReal(z.re, r), z.im) + +export const unaryMinus = + (dep: { + unaryMinus: FnUnaryMinus + }): FnUnaryMinus> => + (z) => complex_binary(dep.unaryMinus(z.re), dep.unaryMinus(z.im)) + +export const conj = + (dep: { + unaryMinus: FnUnaryMinus, + conj: FnConj + }) : FnConj> => + (z) => complex_binary(dep.conj(z.re), dep.unaryMinus(z.im)) + +export const subtract = + (dep: { + subtract: FnSubtract + }): FnSubtract> => + (w, z) => complex_binary(dep.subtract(w.re, z.re), dep.subtract(w.im, z.im)) + +export const multiply = + (dep: { + add: FnAdd, + subtract: FnSubtract, + multiply: FnMultiply, + conj: FnConj + }) => + (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: { + add: FnAdd, + absquare: FnAbsSquare + }): FnAbsSquare, U> => + (z) => dep.add(dep.absquare(z.re), dep.absquare(z.im)) + +export const divideByReal = + (dep: { + divideByReal: FnDivideByReal + }): FnDivideByReal, T> => + (z, r) => complex_binary(dep.divideByReal(z.re, r), dep.divideByReal(z.im, r)) + +export const reciprocal = + (dep: { + conj: FnConj>, + absquare: FnAbsSquare, T>, + divideByReal: FnDivideByReal, T> + }): FnReciprocal> => + (z) => dep.divideByReal(dep.conj(z), dep.absquare(z)) + +export const divide = + (dep: { + multiply: FnMultiply>, + reciprocal: FnReciprocal>, + }): FnDivide> => + (w, z) => dep.multiply(w, dep.reciprocal(z)) + +export const complexSqrt = + (dep: { + conservativeSqrt: FnConservativeSqrt, + isSquare: FnIsSquare, + complex: FnComplexUnary, + unaryMinus: FnUnaryMinus, + zero: FnZero, + nan: FnNaN> + }) => + (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: { + isReal: FnIsReal>, + complexSqrt: FnSqrt, + conservativeSqrt: FnConservativeSqrt, + absquare: FnAbsSquare, T>, + addReal: FnAddReal, T>, + divideByReal: FnDivideByReal, T>, + add: FnAdd, + re: FnRe, T>, + }) => + (z: Complex): Complex | T => { + 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..557ea83 --- /dev/null +++ b/src/Complex/predicate.ts @@ -0,0 +1,14 @@ +import { Complex } from './type.js' +import {FnEqual} from '../interfaces/relational' +import {FnAdd, FnIsReal, FnIsSquare} from '../interfaces/arithmetic' + +export const isReal = + (dep: { + equal: FnEqual, + add: FnAdd, + isReal: FnIsReal + }): FnIsReal> => + (z) => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) + +export const isSquare = + (): FnIsSquare> => (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..9807d71 --- /dev/null +++ b/src/Complex/relational.ts @@ -0,0 +1,8 @@ +import { Complex } from './type.js' +import {FnEqual} from '../interfaces/relational' + +export const equal = + (dep: { + equal: FnEqual + }): FnEqual> => + (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 affbedc..3b4e82d 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -1,22 +1,58 @@ -import {joinTypes, typeOfDependency, Dependency} from '../core/Dispatcher.js' +import { + joinTypes, typeOfDependency, Dependency, +} from '../core/Dispatcher.js' +import type {FnNaN, FnOne, FnRe, FnZero} from '../interfaces/arithmetic.js' -export type Complex = {re: T; im: T;} +export type Complex = { re: T; im: T; } export const Complex_type = { - test: (dep: {testT: (z: unknown) => z is T}) => + test: (dep: { testT: (z: unknown) => z is T }) => (z: unknown): z is Complex => - typeof z === 'object' && 're' in z && 'im' in z - && dep.testT(z.re) && dep.testT(z.im), + typeof z === 'object' && z != null && 're' in z && 'im' in z + && dep.testT(z['re']) && dep.testT(z['im']), infer: (dep: typeOfDependency) => (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), from: { T: (dep: Dependency<'zero', [T]>) => (t: T) => - ({re: t, im: dep.zero(t)}), - Complex: (dep: {convert: (from: U) => T}) => - (z: Complex) => ({re: dep.convert(z.re), im: dep.convert(z.im)}) + ({ re: t, im: dep.zero(t) }), + Complex: (dep: { convert: (from: U) => T }) => + (z: Complex) => ({ re: dep.convert(z.re), im: dep.convert(z.im) }) } } -export const complex_unary = (dep: Dependency<'zero', [T]>) => - (t: T) => ({re: t, im: dep.zero(t)}) -export const complex_binary = (t: T, u: T) => ({re: t, im: u}) +export type FnComplexUnary = (t: T) => Complex + +export const complex_unary = + (dep: { + zero: FnZero + }): FnComplexUnary => + (t) => ({ re: t, im: dep.zero(t) }) + +export type FnComplexBinary = (re: T, im: T) => Complex + +export const complex_binary = (t: T, u: T): Complex => ({ re: t, im: u }) + +export const zero = + (dep: { + zero: FnZero + }): FnZero> => + (z) => complex_binary(dep.zero(z.re), dep.zero(z.im)) + +export const one = + (dep: { + zero: FnZero, + one: FnOne + }): FnOne> => + (z) => complex_binary(dep.one(z.re), dep.zero(z.im)) + +export const nan = + (dep: { + nan: FnNaN + }): FnNaN> => + (z) => complex_binary(dep.nan(z.re), dep.nan(z.im)) + +export const re = + (dep: { + re: FnRe + }): FnRe, T> => + (z) => dep.re(z.re) diff --git a/src/all.ts b/src/all.ts index 192c7be..e2e83f1 100644 --- a/src/all.ts +++ b/src/all.ts @@ -1,2 +1,3 @@ export * from './numbers/all.js' export * from './Complex/all.js' +export * from './generic/all.js' 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 bed8f0d..9b005d9 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -9,16 +9,62 @@ type TypeName = string type Parameter = TypeName -type Signature = Parameter[] +type InputSignature = Parameter[] +type DependenciesType = Record -export interface ImplementationTypes {} export type typeOfDependency = {typeOf: (x: unknown) => TypeName} -// Helper for collecting implementations -// (Really just suffixes the type name onto the keys of exports) -export type ForType = keyof Exports extends string - ? {[K in keyof Exports as `${K}_${T}`]: Exports[K]} - : never +// All of the implementations must publish descriptions of their +// return types into the following interface, using the format +// described just below: +export interface ReturnTypes {} + +/***** + To describe one implementation for a hypothetical operation `foo`, there + should be a property of the interface whose name starts with `foo` and whose + next character, if any, is an underscore. The type of this property + must be the return type of that implementation when Params matches the + parameter types of the implementation, and `never` otherwise. + Thus to describe an implementation that takes a number and a string and + returns a boolean, for example, you could write + ``` + declare module "Dispatcher" { + interface ReturnTypes { + foo_example: Params extends [number, string] ? boolean : never + } + } + ``` + If there is another, generic implementation that takes one argument + of any type and returns a Vector of that type, you can say + ``` + ... + foo_generic: Params extends [infer T] ? Vector : never + ... + ``` + In practice, each subdirectory corresponding to a type, like Complex, + defines an interface, like `ComplexReturn` for the implementations + in that subdirectory, which can mostly be defined without suffixes because + there's typically just a single implementation within that domain. + Then the module responsible for collating all of the implementations for + that type inserts all of the properties of that interface into `ReturnTypes` + suitably suffixed to avoid collisions. + + One might think that simply defining an implementation for `foo` + of type `(n: number, s: string) => boolean` would provide all of the same + information as the type of the key `foo_example` in the ReturnTypes + interface above, but in practice TypeScript has challenges in extracting + types relating to functions. (In particular, there is no + way to get the specialized return type of a generic function when it is + called on aguments whose specific types match the generic parameters.) + Hence the need for this additional mechanism to specify return types, in + a way readily suited for TypeScript type computations. +*****/ + +// Helpers for specifying signatures + +// A basic signature with concrete types +export type Signature = + CandidateParams extends ActualParams ? Returns : never //dummy implementation for now export function joinTypes(a: TypeName, b: TypeName) { @@ -26,27 +72,27 @@ export function joinTypes(a: TypeName, b: TypeName) { return 'any' } -/** - * Build up to Dependency type lookup - */ -type DependenciesType = Record +// Used to filter keys that match a given operation name +type BeginsWith = Name | `${Name}_${string}` -type FinalShape = - FuncType extends (arg: DependenciesType) => Function - ? ReturnType : FuncType +// Look up the return type of an implementation based on its name +// and the parameters it takes +export type ImpReturns = + {[K in keyof ReturnTypes]: K extends BeginsWith + ? ReturnTypes[K] : never}[keyof ReturnTypes] -type BeginsWith = `${Name}${string}` - -type DependencyTypes = - {[K in keyof Ob]: K extends BeginsWith - ? FinalShape extends (...args: Params) => any - ? FinalShape - : never - : never} +// The type of an implementation (with dependencies satisfied, +// based on its name and the parameters it takes +export type ImpType = + (...args: Params) => ImpReturns +// The type of a dependency on an implementation based on its name +// and the parameters it takes (just a simple object with one property +// named the same as the operation, of value type equal to the type of +// that implementation. These can be `&`ed together in case of multiple +// dependencies: export type Dependency = - {[N in Name]: - DependencyTypes[keyof ImplementationTypes]} + {[N in Name]: ImpType} // Now types used in the Dispatcher class itself @@ -64,9 +110,9 @@ type SpecificationsGroup = Record export class Dispatcher { installSpecification( name: string, - signature: Signature, + signature: InputSignature, returns: TypeName, - dependencies: Record, + dependencies: Record, behavior: Function // possible todo: constrain this type based // on the signature, return type, and dependencies. Not sure if // that's really possible, though. diff --git a/src/generic/all.ts b/src/generic/all.ts new file mode 100644 index 0000000..1b1b8a4 --- /dev/null +++ b/src/generic/all.ts @@ -0,0 +1,3 @@ +import * as generic from './arithmetic.js' + +export { generic } diff --git a/src/generic/arithmetic.ts b/src/generic/arithmetic.ts new file mode 100644 index 0000000..a1379df --- /dev/null +++ b/src/generic/arithmetic.ts @@ -0,0 +1,7 @@ +import type { FnMultiply, FnSquare } from "../interfaces/arithmetic" + +export const square = + (dep: { + multiply: FnMultiply + }): FnSquare => + (z) => dep.multiply(z, z) diff --git a/src/index.ts b/src/index.ts index bb83486..297b271 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,19 @@ import {Dispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' export default new Dispatcher(Specifications) + +import {Complex} from './Complex/type.js' +import {absquare as absquare_complex} from './Complex/arithmetic.js' + +const mockRealAdd = (a: number, b: number) => a+b +const mockComplexAbsquare = (z: Complex) => z.re*z.re + z.im*z.im + +const quatAbsquare = absquare_complex({ + add: mockRealAdd, + absquare: mockComplexAbsquare +}) + +const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}}) +const typeTest: typeof myabs = 7 // check myabs is just a number + +console.log('Result is', myabs) diff --git a/src/infer.ts b/src/infer.ts new file mode 100644 index 0000000..769b5c9 --- /dev/null +++ b/src/infer.ts @@ -0,0 +1,88 @@ +/** + * Idea: instead of writing TypeScript, and inferring the JS-pocomath signature + * from TS that via a TypeScript plugin, we can maybe do this the other way + * around: take the JS-pocomath signature as base (and source of truth), and + * infer TypeScript interfaces from that using infer in template literal types. + */ + +// TODO: get generics working +// TODO: how to pass config? + +const create = createFactory<{ + number: number + bigint: bigint + string: string + any: any +}>() + + +// These are our string based interfaces, which we can use both in typed-function and in TypeScript: +const Multiply = 'multiply(number,number)=>number' +const Square = 'square(number)=>number' +const Zero = 'zero(number)=>number' + +// TODO: turn a generic string like `(T,T)=>T` into a concrete one like `(number,number)=>number` +// const MultiplyNumber = ResolveGeneric<'multiply', number> + +const createSquare = create(Square, [Multiply, Zero], dep => + x => dep.multiply(x, x) +) + +// the code works in JS, and works in TS +const multiply = (a: number, b: number) => a * b +const zero = (a: number) => 0 +const square = createSquare({ multiply, zero }) +console.log('square', square(8)) // 64 + +function createFactory>() { + type BaseTypeNames = string & keyof BaseTypes + type ResolveType = BaseTypes[TypeName] + + type Value = K + + type ResolveArguments = S extends '' + ? [] + : S extends `${infer Arg extends BaseTypeNames},${infer Tail}` + ? [ResolveType, ...ResolveArguments] + : S extends `${infer Arg extends BaseTypeNames}` + ? [ResolveType] + : never + + type DependencyRecord = + FnType extends Value + ? K extends `${infer Name}(${infer Args})=>${infer ReturnType extends BaseTypeNames}` + ? Record) => ResolveType> + : never + : never + + type CreatedFunctionType = + FnType extends Value + ? K extends `${infer Name}(${infer Args})=>${infer ReturnType extends BaseTypeNames}` + ? (...args: ResolveArguments) => ResolveType + : never + : never + + // inspired by: https://stackoverflow.com/questions/68391632/infer-type-from-array-literal + type DependenciesRecord< + Arr extends Array>, + Result extends Record = {} + > = Arr extends [] + ? Result + : Arr extends [infer H, ...infer Tail] + ? Tail extends Array> + ? H extends Value + ? DependenciesRecord> + : never + : never + : never + + return function create[], W extends Value>( + signature: W, + dependencies: [...Dependencies], + callback: (deps: DependenciesRecord<[...Dependencies]>) => CreatedFunctionType + ) { + console.log('Creating typed-function with', { signature, dependencies }) + // TODO: create a typed-function for real + return callback + } +} diff --git a/src/interfaces/arithmetic.ts b/src/interfaces/arithmetic.ts new file mode 100644 index 0000000..91b59bf --- /dev/null +++ b/src/interfaces/arithmetic.ts @@ -0,0 +1,28 @@ +// shared interfaces + +import { Complex } from "../Complex/type" + +// Note: right now I've added an 'Fn*' prefix, +// so it is clear that the type hold a function type definition +// This is not necessary though, it is just a naming convention. +export type FnAdd = (a: T, b: T) => T +export type FnAddReal = (a: T, b: U) => T +export type FnUnaryMinus = (a: T) => T +export type FnConj = (a: T) => T +export type FnSubtract = (a: T, b: T) => T +export type FnMultiply = (a: T, b: T) => T +export type FnAbsSquare = (a: T) => U +export type FnReciprocal = (a: T) => T +export type FnDivide = (a: T, b: T) => T +export type FnDivideByReal = (a: T, b: U) => T +export type FnConservativeSqrt = (a: T) => T +export type FnSqrt = (a: T) => T | Complex +export type FnSquare = (z: T) => T + +export type FnIsReal = (a: T) => boolean +export type FnIsSquare = (a: T) => boolean + +export type FnZero = (a: T) => T +export type FnOne = (a: T) => T +export type FnNaN = (a: T) => T +export type FnRe = (a: T) => U diff --git a/src/interfaces/relational.ts b/src/interfaces/relational.ts new file mode 100644 index 0000000..2865f77 --- /dev/null +++ b/src/interfaces/relational.ts @@ -0,0 +1,3 @@ + +export type FnEqual = (a: T, b: T) => boolean +export type FnUnequal = (a: T, b: T) => boolean diff --git a/src/numbers/all.ts b/src/numbers/all.ts index 5aea220..deb4a8e 100644 --- a/src/numbers/all.ts +++ b/src/numbers/all.ts @@ -1,8 +1,3 @@ -import {ForType} from '../core/Dispatcher.js' import * as numbers from './native.js' export {numbers} - -declare module "../core/Dispatcher" { - interface ImplementationTypes extends ForType<'numbers', typeof numbers> {} -} diff --git a/src/numbers/arithmetic.ts b/src/numbers/arithmetic.ts index e78d9ec..7142797 100644 --- a/src/numbers/arithmetic.ts +++ b/src/numbers/arithmetic.ts @@ -1,20 +1,29 @@ -import {configDependency} from '../core/Config.js' -import {Dependency} from '../core/Dispatcher.js' +import { Config } from '../core/Config.js' +import type { FnComplexBinary } from '../Complex/type.js' +import { FnAdd, FnConj, FnSubtract, FnUnaryMinus, FnMultiply, FnAbsSquare, FnReciprocal, FnDivide, FnConservativeSqrt, FnSqrt } from '../interfaces/arithmetic.js' + +export const add: FnAdd = (a, b) => a + b +export const addReal = add +export const unaryMinus: FnUnaryMinus = (a) => -a +export const conj: FnConj = (a) => a +export const subtract: FnSubtract = (a, b) => a - b +export const multiply: FnMultiply = (a, b) => a * b +export const absquare: FnAbsSquare = (a) => a * a +export const reciprocal: FnReciprocal = (a) => 1 / a +export const divide: FnDivide = (a, b) => a / b +export const divideByReal = divide + +export const conservativeSqrt: FnConservativeSqrt = (a) => isNaN(a) ? NaN : Math.sqrt(a) -export const add = (a: number, b: number) => a + b -export const unaryMinus = (a: number) => -a -export const subtract = (a: number, b: number) => a - b -export const multiply = (a: number, b: number) => a * b -export const divide = (a: number, b: number) => a / b export const sqrt = - (dep: configDependency - & Dependency<'complex', [number, number]>) => { - if (dep.config.predictable || !dep.complex) { - return (a: number) => isNaN(a) ? NaN : Math.sqrt(a) - } - return (a: number) => { - if (isNaN(a)) return NaN - if (a >= 0) return Math.sqrt(a) - return dep.complex(0, Math.sqrt(unaryMinus(a))) - } - } + (dep: { + config: Config, + complex: FnComplexBinary + }): FnSqrt => { + if (dep.config.predictable || !dep.complex) return conservativeSqrt + return a => { + if (isNaN(a)) return NaN + if (a >= 0) return Math.sqrt(a) + return dep.complex(0, Math.sqrt(unaryMinus(a))) + } + } diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts new file mode 100644 index 0000000..4015c55 --- /dev/null +++ b/src/numbers/predicate.ts @@ -0,0 +1,4 @@ +import type { FnIsReal, FnIsSquare } from "../interfaces/arithmetic" + +export const isReal: FnIsReal = (a) => true +export const isSquare: FnIsSquare = (a) => a >= 0 diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts new file mode 100644 index 0000000..ae9a63d --- /dev/null +++ b/src/numbers/relational.ts @@ -0,0 +1,27 @@ +import { Config } from '../core/Config.js' +import type { FnEqual, FnUnequal } from '../interfaces/relational.js' + +const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 + +export const equal = + (dep: { + config: Config + }): FnEqual => (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 + } + +export const unequal = (dep: { + equal: FnEqual +}): FnUnequal => + (x, y) => !dep.equal(x, y) diff --git a/src/numbers/type.ts b/src/numbers/type.ts index 67dbd29..77336ef 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,7 +1,12 @@ +import type { FnNaN, FnOne, FnZero, FnRe } from "../interfaces/arithmetic" + export const number_type = { before: ['Complex'], test: (n: unknown): n is number => typeof n === 'number', - from: {string: s => +s} + from: { string: (s: string) => +s } } -export const zero = (a: number) => 0 +export const zero: FnZero = (a) => 0 +export const one: FnOne = (a) => 1 +export const nan: FnNaN = (a) => NaN +export const re: FnRe = (a) => a diff --git a/tsconfig.json b/tsconfig.json index aae3a94..797c2d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,6 @@ "compilerOptions": { "target": "ES2022", "rootDir": "./src", - "outDir": "./obj" + "outDir": "./build" } }