feat: config and approximate equality (#19)
All checks were successful
/ test (push) Successful in 17s
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:
parent
27fa4b0193
commit
70ce01d12b
27 changed files with 788 additions and 218 deletions
|
@ -4,4 +4,7 @@ import {BooleanT} from '#boolean/BooleanT.js'
|
|||
|
||||
export const NumberT = new Type(n => typeof n === 'number', {
|
||||
from: onType(BooleanT, math => math.number.resolve([BooleanT])),
|
||||
one: 1,
|
||||
zero: 0,
|
||||
nan: NaN
|
||||
})
|
||||
|
|
33
src/number/__test__/relational.spec.js
Normal file
33
src/number/__test__/relational.spec.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
const {exceeds, indistinguishable} = math
|
||||
|
||||
describe('number relational functions', () => {
|
||||
it('orders numbers correctly', () => {
|
||||
assert(exceeds(7, 2.2))
|
||||
assert(exceeds(0, -1.1))
|
||||
assert(exceeds(1e-35, 0))
|
||||
assert(exceeds(Infinity, 1e99))
|
||||
assert(exceeds(-1e101, -Infinity))
|
||||
assert(!exceeds(NaN, 0))
|
||||
assert(!exceeds(0, NaN))
|
||||
assert(!exceeds(NaN, NaN))
|
||||
assert(!exceeds(2, 2))
|
||||
})
|
||||
it('checks for exact equality', () => {
|
||||
assert(indistinguishable(0, 0))
|
||||
assert(indistinguishable(0, -0))
|
||||
assert(!indistinguishable(0, 1e-35))
|
||||
assert(!indistinguishable(NaN, NaN))
|
||||
assert(indistinguishable(Infinity, Infinity))
|
||||
})
|
||||
it('checks for approximate equality', () => {
|
||||
const rel = 1e-12
|
||||
const abs = 1e-15
|
||||
assert(indistinguishable(1, 1 + 0.9e-12, rel, abs))
|
||||
assert(indistinguishable(0, 1e-16, rel, abs))
|
||||
assert(!indistinguishable(1, 1 + 1.1e-12, rel, abs))
|
||||
assert(!indistinguishable(0, 1e-14, rel, abs))
|
||||
})
|
||||
})
|
|
@ -1,4 +1,5 @@
|
|||
export * as typeDefinition from './NumberT.js'
|
||||
export * as arithmetic from './arithmetic.js'
|
||||
export * as relational from './relational.js'
|
||||
export * as type from './type.js'
|
||||
export * as utils from './utils.js'
|
||||
|
|
|
@ -6,4 +6,11 @@ import {Returns} from '#core/Type.js'
|
|||
export const plain = f => onType(
|
||||
Array(f.length).fill(NumberT), Returns(NumberT, f))
|
||||
|
||||
export const boolnum = Returns(NumberT, p => p ? 1 : 0)
|
||||
// Takes a behavior returning boolean, and returns a factory
|
||||
// that returns that behavior if the boolean type is present,
|
||||
// and otherwise wraps the behavior to return 1 or 0.
|
||||
export const boolnum = behavior => math => {
|
||||
const {BooleanT} = math.types
|
||||
if (BooleanT) return Returns(BooleanT, behavior)
|
||||
return Returns(NumberT, (...args) => behavior(...args) ? 1 : 0)
|
||||
}
|
||||
|
|
37
src/number/relational.js
Normal file
37
src/number/relational.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import {onType} from '#core/helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Optional} from '#core/TypePatterns.js'
|
||||
import {boolnum} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
// In nanomath, we take the point of view that two comparators are primitive:
|
||||
// indistinguishable(a, b, relTol, absTol), and exceeds(a, b). All others
|
||||
// are defined generically in terms of these. They typically return BooleanT,
|
||||
// but in a numbers-only bundle, they return 1 or 0.
|
||||
|
||||
// Notice a feature of TypedDispatcher: if you specify one tolerance, you must
|
||||
// specify both.
|
||||
export const indistinguishable = onType(
|
||||
[NumberT, NumberT, Optional([NumberT, NumberT])],
|
||||
boolnum((a, b, [tolerances = [0, 0]]) => {
|
||||
const [relTol, absTol] = tolerances
|
||||
if (relTol < 0 || absTol < 0) {
|
||||
throw new RangeError(
|
||||
`Tolerances (relative: ${relTol}, absolute: ${absTol}) `
|
||||
+ 'must be nonnegative')
|
||||
}
|
||||
if (isNaN(a) || isNaN(b)) return false
|
||||
if (a === b) return true
|
||||
if (!isFinite(a) || !isFinite(b)) return false
|
||||
// |a-b| <= absTol or |a-b| <= relTol*max(|a|, |b|)
|
||||
const diff = Math.abs(a-b)
|
||||
if (diff <= absTol) return true
|
||||
const magnitude = Math.max(Math.abs(a), Math.abs(b))
|
||||
return diff <= relTol * magnitude
|
||||
})
|
||||
)
|
||||
|
||||
// Returns truthy if a (interpreted as completely precise) represents a
|
||||
// greater value than b (interpreted as completely precise). Note that even if
|
||||
// so, a and b might be indistinguishable() to some tolerances.
|
||||
export const exceeds = onType([NumberT, NumberT], boolnum((a, b) => a > b))
|
|
@ -1,4 +1,4 @@
|
|||
import {plain, boolnum} from './helpers.js'
|
||||
import {plain} from './helpers.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
@ -7,6 +7,7 @@ const num = f => Returns(NumberT, f)
|
|||
|
||||
export const number = plain(a => a)
|
||||
number.also(
|
||||
BooleanT, boolnum,
|
||||
// conversions from Boolean should be consistent with one and zero:
|
||||
BooleanT, num(p => p ? NumberT.one : NumberT.zero),
|
||||
[], num(() => 0)
|
||||
)
|
||||
|
|
|
@ -5,8 +5,4 @@ import {Returns} from '#core/Type.js'
|
|||
import {onType} from '#core/helpers.js'
|
||||
|
||||
export const clone = plain(a => a)
|
||||
export const isnan = onType(NumberT, math => {
|
||||
const {BooleanT} = math.types
|
||||
if (BooleanT) return Returns(BooleanT, a => isNaN(a))
|
||||
return Returns(NumberT, a => boolnum(isNaN(a)))
|
||||
})
|
||||
export const isnan = onType(NumberT, boolnum(isNaN))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue