feat: config and approximate equality (#19)
All checks were successful
/ test (push) Successful in 17s

Establishes a global config object for a TypeDispatcher instance, so far
  with just properties representing comparison tolerances. Begins a
  "relational" group of functions with basic approximate equality, and
  an initial primitive ordering comparison. Ensures that methods that
  depend on properties of `config` will be properly updated when those
  properties change.

Reviewed-on: #19
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
Glen Whitney 2025-04-16 04:23:48 +00:00 committed by Glen Whitney
parent 27fa4b0193
commit 70ce01d12b
27 changed files with 788 additions and 218 deletions

View file

@ -0,0 +1,82 @@
import assert from 'assert'
import math from '#nanomath'
import * as numbers from '#number/all.js'
import * as generics from '#generic/all.js'
import {ResolutionError} from '#core/helpers.js'
import {TypeDispatcher} from '#core/TypeDispatcher.js'
describe('generic relational functions', () => {
it('tests equality for anything, approx on numbers', () => {
const {equal} = math
assert(equal(undefined, undefined))
assert(equal(math.types.NumberT, math.types.NumberT))
assert(!equal(math.types.NumberT, math.types.BooleanT))
assert(!equal(undefined, math.types.NumberT))
assert(equal(1, 1))
assert(equal(true, 1)) // questionable but same as mathjs
assert(!equal(undefined, true))
assert(equal(1, 1 + 0.9e-12))
assert(equal(0, 1e-16))
assert(!equal(1, 1 + 1.1e-12))
assert(!equal(0, 1.1e-15))
})
it('adjusts equality when config changes', () => {
const jn = new TypeDispatcher(generics, numbers)
const {equal} = jn
assert.strictEqual(equal(1, 1 + 0.9e-12), 1)
assert.strictEqual(equal(0, 1e-16), 1)
assert.strictEqual(equal(1, 1 + 1.1e-12), 0)
assert.strictEqual(equal(0, 1.1e-15), 0)
jn.config.relTol = 1e-10
assert.strictEqual(equal(1, 1 + 1.1e-12), 1)
assert.strictEqual(equal(1, 1 + 1.1e-10), 0)
assert.strictEqual(equal(0, 1.1e-15), 0)
jn.config.absTol = 1e-13
assert.strictEqual(equal(1, 1 + 1.1e-12), 1)
assert.strictEqual(equal(1, 1 + 1.1e-10), 0)
assert.strictEqual(equal(0, 1.1e-15), 1)
assert.strictEqual(equal(0, 1.1e-13), 0)
})
it('performs three-way comparison', () => {
const {compare} = math
assert.strictEqual(compare(-0.4e-15, +0.4e-15), 0)
assert.strictEqual(compare(2.2, true), 1)
assert.strictEqual(compare(-Infinity, 7), -1)
assert(isNaN(compare(NaN, 0)))
assert.throws(() => compare(false, NaN), TypeError)
assert(isNaN(compare(NaN, NaN)))
assert.throws(() => compare(true, false), TypeError)
assert.throws(() => compare(undefined, -1), ResolutionError)
})
it('determines the sign of numeric values', () => {
const {sign} = math
assert.strictEqual(sign(-8e-16), 0)
assert.strictEqual(sign(Infinity), 1)
assert.strictEqual(sign(-8e-14), -1)
assert(isNaN(sign(NaN)))
assert.throws(() => sign(false), TypeError)
assert.throws(() => sign(undefined), ResolutionError)
})
it('computes inequalities', () => {
const {unequal, larger, largerEq, smaller, smallerEq} = math
assert(!unequal(undefined, undefined))
assert(!unequal(math.types.NumberT, math.types.NumberT))
assert(unequal(math.types.NumberT, math.types.BooleanT))
assert(unequal(undefined, math.types.NumberT))
assert(!unequal(1, 1))
assert(!unequal(true, 1)) // questionable but same as mathjs
assert(unequal(undefined, true))
assert(!unequal(1, 1 + 0.9e-12))
assert(!unequal(0, 1e-16))
assert(unequal(1, 1 + 1.1e-12))
assert(unequal(0, 1.1e-15))
assert(larger(true, 0.5))
assert(!larger(3 + 1e-16, 3))
assert(largerEq(0.5, false))
assert(largerEq(3 + 1e-16, 3))
assert(smallerEq(3 + 1e-16, 3))
assert(!smaller(3, 3 + 1e-16))
})
})

View file

@ -0,0 +1,13 @@
import assert from 'assert'
import math from '#nanomath'
describe('generic utility functions', () => {
it('tests whether an element is zero', () => {
const {isZero} = math
assert(!isZero(3))
assert(isZero(3e-16))
assert(isZero(false))
assert(!isZero(true))
assert(isZero(undefined))
})
})