import {ReturnsAs} from './helpers.js' import {match} from '#core/helpers.js' import {Returns} from '#core/Type.js' import {Any, Passthru, matched} from '#core/TypePatterns.js' import {boolnum} from '#number/helpers.js' export const equal = match([Any, Any], (math, [T, U]) => { // Finding the correct signature of `indistinguishable` to use for // testing (approximate) equality is tricky, because T or U might // need to be converted for the sake of comparison, and some types // allow tolerances for equality and others don't. So the plan is // we first look up without tolerances, then we check the config for // the matching type, and then we look up with tolerances. let exactChecker try { exactChecker = math.indistinguishable.resolve([T, U]) } catch { // can't compare, so no way they can be equal return boolnum(() => false)(math) } // Get the type of the first argument to the matching checker: const ByType = matched(exactChecker.template).flat()[0] // Now see if there are tolerances for that type: const typeConfig = math.resolve('config', [ByType]) if ('relTol' in typeConfig) { try { const {relTol, absTol} = typeConfig const RT = math.typeOf(relTol) const AT = math.typeOf(absTol) const approx = math.indistinguishable.resolve([T, U, RT, AT]) return ReturnsAs( approx, (t, u) => approx(t, u, relTol, absTol)) } catch {} // fall through to case with no tolerances } // either no tolerances or no matching signature for indistinguishable return exactChecker }) // now that we have `equal` and `exceeds`, pretty much everything else should // be easy: export const compare = match([Any, Any], (math, [T, U]) => { const eq = math.equal.resolve([T, U]) const gt = math.exceeds.resolve([T, U]) const zero = math.zero(T) // asymmetry here is unfortunate, but we have // to pick some argument's zero to use, so the first argument seemed // the most reasonable. const one = math.one(T) const negOne = math.negate(one) if (math.typeOf(negOne) !== T) { throw new TypeError( `Cannot 'compare()' type '${T}' that has no negative one.`) } const hasnanT = math.hasnan(T) const hasnanU = math.hasnan(U) if (!hasnanT && !hasnanU) { return Returns(T, (t, u) => eq(t, u) ? zero : gt(t, u) ? one : negOne) } if (hasnanU && !hasnanT) { throw new TypeError( `can't compare type ${T} without NaN and type ${U} with NaN`) } const isTnan = hasnanT && math.isnan.resolve([T]) const isUnan = hasnanU && math.isnan.resolve([U]) const nanT = hasnanT && math.nan(T) return Returns(T, (t, u) => { if (hasnanT && isTnan(t)) return nanT if (hasnanU && isUnan(u)) return nanT // not a typo, stay in T if (eq(t, u)) return zero return gt(t, u) ? one : negOne }) }) export const sign = match(Any, (math, T) => { const zero = math.zero(T) const comp = math.compare.resolve([T, T]) return ReturnsAs(comp, t => comp(t, zero)) }) export const unequal = match(Passthru, (math, types) => { const eq = math.equal.resolve(types) return ReturnsAs(eq, (...args) => !eq(...args)) }) export const larger = match([Any, Any], (math, [T, U]) => { const eq = math.equal.resolve([T, U]) const bigger = math.exceeds.resolve([T, U]) return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math) }) export const largerEq = match([Any, Any], (math, [T, U]) => { const eq = math.equal.resolve([T, U]) const bigger = math.exceeds.resolve([T, U]) return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(t, u)) }) export const smaller = match([Any, Any], (math, [T, U]) => { const eq = math.equal.resolve([T, U]) const bigger = math.exceeds.resolve([U, T]) return boolnum((t, u) => !eq(t, u) && bigger(u, t))(math) }) export const smallerEq = match([Any, Any], (math, [T, U]) => { const eq = math.equal.resolve([T, U]) const bigger = math.exceeds.resolve([U, T]) return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(u, t)) })