feat: add/multiply arbitrarily many arguments; full cbrt(number)

Extends `add` and `multiply` generically so they can handle any number of
  arguments based on their binary implementations; they also pass a
  single argument through unchanged, and return 0 and 1, respectively, on
  no arguments.

  Modifies cbrt(number) so that when the returnTyping is `full`, returns
  all three complex cube roots.
This commit is contained in:
Glen Whitney 2025-12-12 11:19:25 -08:00
parent fbbc2502bd
commit b80b7832b7
6 changed files with 115 additions and 7 deletions

View file

@ -275,7 +275,8 @@ export class TypeDispatcher {
throw new ReferenceError(`no method or value for key '${key}'`) throw new ReferenceError(`no method or value for key '${key}'`)
} }
if (!Array.isArray(types)) types = [types] if (!Array.isArray(types)) types = [types]
if (types.some(T => T === Unknown)) { // Unknown and union, i.e., OneOf(...), stymie resolution:
if (types.some(T => T === Unknown || T.unifies)) {
if (!strategy) return this[key] if (!strategy) return this[key]
const thisTypeOf = whichType(this.types) const thisTypeOf = whichType(this.types)
return (...args) => { return (...args) => {

View file

@ -1,18 +1,53 @@
import assert from 'assert' import assert from 'assert'
import math from '#nanomath' import math from '#nanomath'
import {ReturnType, ReturnTyping} from '#core/Type.js' import {ReturnType, ReturnTyping, Unknown} from '#core/Type.js'
const {Complex, NumberT} = math.types const {Complex, NumberT} = math.types
describe('generic arithmetic', () => { describe('generic arithmetic', () => {
const cplx = math.complex
it('squares anything', () => { it('squares anything', () => {
const sq = math.square const sq = math.square
assert.strictEqual(sq(7), 49) assert.strictEqual(sq(7), 49)
assert.strictEqual(ReturnType(math.square.resolve([NumberT])), NumberT) assert.strictEqual(ReturnType(math.square.resolve([NumberT])), NumberT)
assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24)) assert.deepStrictEqual(sq(cplx(3, 4)), cplx(-7, 24))
const eyes = math.complex(0, 2) const eyes = cplx(0, 2)
assert.strictEqual(sq(eyes), -4) assert.strictEqual(sq(eyes), -4)
const sqFull = math.square.resolve(Complex(NumberT), ReturnTyping.full) const sqFull = math.square.resolve(Complex(NumberT), ReturnTyping.full)
assert.deepStrictEqual(sqFull(eyes), math.complex(-4, 0)) assert.deepStrictEqual(sqFull(eyes), cplx(-4, 0))
})
it('adds up multiple arguments', () => {
const add = math.add
assert.strictEqual(add(), 0)
assert.strictEqual(add(1), 1)
assert.deepStrictEqual(add(cplx(2)), cplx(2))
assert.deepStrictEqual(
ReturnType(add.resolve([Complex(NumberT)])), Complex(NumberT))
assert.strictEqual(add(3, 4, 5), 12)
assert.deepStrictEqual(add(6, cplx(0, 7), 8), cplx(14, 7))
assert.strictEqual(
ReturnType(add.resolve([NumberT, Complex(NumberT), NumberT])),
Unknown) // loses track of whether it comes out real or complex
assert.deepStrictEqual(
add(9, cplx(0, 10), cplx(11), cplx(0, -10)), 20)
const saveTyping = math.config.returnTyping
math.config.returnTyping = ReturnTyping.full
assert.strictEqual(
ReturnType(add.resolve([NumberT, Complex(NumberT), NumberT])),
Complex(NumberT)) // now definite
assert.deepStrictEqual(
add(9, cplx(0, 10), cplx(11), cplx(0, -10)), cplx(20))
math.config.returnTyping = saveTyping
})
it('multiplies multiple arguments', () => {
const mult = math.multiply
assert.strictEqual(mult(), 1)
assert.strictEqual(mult(2), 2)
assert.deepStrictEqual(mult(cplx(3)), cplx(3))
assert.strictEqual(mult(4, 5, 6), 120)
assert.deepStrictEqual(mult(7, cplx(0, 8), 9), cplx(0, 504))
assert.deepStrictEqual(mult(10, cplx(0, 1), cplx(0, 2), 3), -60)
}) })
}) })

View file

@ -1,4 +1,5 @@
import {ReturnsAs} from './helpers.js' import {iterateBinary, ReturnsAs} from './helpers.js'
import {plain} from "#number/helpers.js"
import {Returns, ReturnType, ReturnTyping} from '#core/Type.js' import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
import {match, Any} from '#core/TypePatterns.js' import {match, Any} from '#core/TypePatterns.js'
@ -18,3 +19,9 @@ export const square = match(Any, (math, T, strategy) => {
const mult = math.multiply.resolve([T, T], strategy) const mult = math.multiply.resolve([T, T], strategy)
return ReturnsAs(mult, t => mult(t, t)) return ReturnsAs(mult, t => mult(t, t))
}) })
export const add = iterateBinary('add')
add.push(plain(() => 0))
export const multiply = iterateBinary('multiply')
multiply.push(plain(() => 1))

View file

@ -1,3 +1,24 @@
import {Returns, ReturnType} from '#core/Type.js' import {Returns, ReturnType} from '#core/Type.js'
import {match, Any, Multiple} from '#core/TypePatterns.js'
export const ReturnsAs = (g, f) => Returns(ReturnType(g), f) export const ReturnsAs = (g, f) => Returns(ReturnType(g), f)
export const iterateBinary = operation => [
match(Any, (_math, T) => Returns(T, t => t)),
match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest], strategy) => {
const op1 = math[operation].resolve([T, U], strategy)
const op2 = math[operation].resolve([ReturnType(op1), V])
let finalOp = op2
let restOps = []
for (const Typ of Rest) {
finalOp = math[operation].resolve([ReturnType(finalOp), Typ])
restOps.push(finalOp)
}
return ReturnsAs(finalOp, (t, u, v, rest) => {
let result = op2(op1(t, u), v)
for (let i = 0; i < rest.length; ++i) {
result = restOps[i](result, rest[i])
}
return result
})
})
]

View file

@ -30,4 +30,25 @@ describe('number arithmetic', () => {
assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2)) assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2))
math.config.returnTyping = ReturnTyping.free math.config.returnTyping = ReturnTyping.free
}) })
it('takes cube root of numbers appropriately', () => {
assert(isNaN(math.cbrt(NaN)))
assert.strictEqual(math.cbrt(8), 2)
assert.strictEqual(math.cbrt(-8), -2)
const cbrt10 = Math.cbrt(10)
assert.strictEqual(math.cbrt(10), cbrt10)
const saveTyping = math.config.returnTyping
math.config.returnTyping = ReturnTyping.full
assert(isNaN(math.cbrt(NaN)))
const cbrtIm = Math.sqrt(3) / 2
const cplx = math.complex
const rt8 = math.cbrt(8)
assert.deepStrictEqual(
rt8, [cplx(2), cplx(-1, 2 * cbrtIm), cplx(-1, -2 * cbrtIm)])
assert(math.equal(math.multiply.apply(null, rt8), cplx(8)))
assert.deepStrictEqual(
math.cbrt(10),
[cplx(cbrt10),
cplx(-cbrt10 / 2, cbrt10 * cbrtIm),
cplx(-cbrt10 / 2, -cbrt10 * cbrtIm)])
})
}) })

View file

@ -3,6 +3,7 @@ import {NumberT} from './NumberT.js'
import {OneOf, Returns, ReturnTyping, Undefined} from '#core/Type.js' import {OneOf, Returns, ReturnTyping, Undefined} from '#core/Type.js'
import {match} from '#core/TypePatterns.js' import {match} from '#core/TypePatterns.js'
import {Complex} from '#complex/Complex.js' import {Complex} from '#complex/Complex.js'
import {ReturnsAs} from '#generic/helpers.js'
const {conservative, full} = ReturnTyping const {conservative, full} = ReturnTyping
@ -15,7 +16,7 @@ export const add = [
match([NumberT, Undefined], Returns(NumberT, () => NaN)) match([NumberT, Undefined], Returns(NumberT, () => NaN))
] ]
export const divide = plain((a, b) => a / b) export const divide = plain((a, b) => a / b)
export const cbrt = plain(a => { const numberCbrt = a => {
if (a === 0) return a if (a === 0) return a
const negate = a < 0 const negate = a < 0
if (negate) a = -a if (negate) a = -a
@ -25,7 +26,29 @@ export const cbrt = plain(a => {
result = (a / (result * result) + (2 * result)) / 3 result = (a / (result * result) + (2 * result)) / 3
} }
return negate ? -result : result return negate ? -result : result
}
const cbrtIm = Math.sqrt(3) / 2
export const cbrt = match(NumberT, (math, _N, strategy) => {
if (strategy === full && math.types.Complex && math.types.Vector) {
// return vector of all three complex roots, real one first
const C = Complex(NumberT)
const vec = math.vector.resolve([C, C, C])
const promote = math.complex.resolve([NumberT])
const cplx = math.complex.resolve([NumberT, NumberT])
return ReturnsAs(vec, x => {
const realroot = numberCbrt(x)
if (!isFinite(realroot)) {
const full = cplx(realroot, realroot)
return(promote(realroot), full, full)
}
return vec(promote(realroot),
cplx(-realroot / 2, realroot * cbrtIm),
cplx(-realroot / 2, -realroot * cbrtIm))
})
}
return Returns(NumberT, numberCbrt)
}) })
export const invert = plain(a => 1/a) export const invert = plain(a => 1/a)
export const multiply = [ export const multiply = [
plain((a, b) => a * b), plain((a, b) => a * b),