From b80b7832b760b85adcd6c49002c05e9daab2a1e8 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 12 Dec 2025 11:19:25 -0800 Subject: [PATCH] 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. --- src/core/TypeDispatcher.js | 3 +- src/generic/__test__/arithmetic.spec.js | 43 ++++++++++++++++++++++--- src/generic/arithmetic.js | 9 +++++- src/generic/helpers.js | 21 ++++++++++++ src/number/__test__/arithmetic.spec.js | 21 ++++++++++++ src/number/arithmetic.js | 25 +++++++++++++- 6 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index cca4932..e1ae44e 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -275,7 +275,8 @@ export class TypeDispatcher { throw new ReferenceError(`no method or value for key '${key}'`) } 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] const thisTypeOf = whichType(this.types) return (...args) => { diff --git a/src/generic/__test__/arithmetic.spec.js b/src/generic/__test__/arithmetic.spec.js index 35d72bd..ef57d12 100644 --- a/src/generic/__test__/arithmetic.spec.js +++ b/src/generic/__test__/arithmetic.spec.js @@ -1,18 +1,53 @@ import assert from 'assert' import math from '#nanomath' -import {ReturnType, ReturnTyping} from '#core/Type.js' +import {ReturnType, ReturnTyping, Unknown} from '#core/Type.js' const {Complex, NumberT} = math.types describe('generic arithmetic', () => { + const cplx = math.complex it('squares anything', () => { const sq = math.square assert.strictEqual(sq(7), 49) assert.strictEqual(ReturnType(math.square.resolve([NumberT])), NumberT) - assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24)) - const eyes = math.complex(0, 2) + assert.deepStrictEqual(sq(cplx(3, 4)), cplx(-7, 24)) + const eyes = cplx(0, 2) assert.strictEqual(sq(eyes), -4) 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) }) }) diff --git a/src/generic/arithmetic.js b/src/generic/arithmetic.js index 92ebf9b..6395fcc 100644 --- a/src/generic/arithmetic.js +++ b/src/generic/arithmetic.js @@ -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 {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) 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)) diff --git a/src/generic/helpers.js b/src/generic/helpers.js index a244712..71d27d4 100644 --- a/src/generic/helpers.js +++ b/src/generic/helpers.js @@ -1,3 +1,24 @@ 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 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 + }) + }) +] diff --git a/src/number/__test__/arithmetic.spec.js b/src/number/__test__/arithmetic.spec.js index 8a2a973..7a473fd 100644 --- a/src/number/__test__/arithmetic.spec.js +++ b/src/number/__test__/arithmetic.spec.js @@ -30,4 +30,25 @@ describe('number arithmetic', () => { assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2)) 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)]) + }) }) diff --git a/src/number/arithmetic.js b/src/number/arithmetic.js index 9ad8ac0..1e86ce9 100644 --- a/src/number/arithmetic.js +++ b/src/number/arithmetic.js @@ -3,6 +3,7 @@ import {NumberT} from './NumberT.js' import {OneOf, Returns, ReturnTyping, Undefined} from '#core/Type.js' import {match} from '#core/TypePatterns.js' import {Complex} from '#complex/Complex.js' +import {ReturnsAs} from '#generic/helpers.js' const {conservative, full} = ReturnTyping @@ -15,7 +16,7 @@ export const add = [ match([NumberT, Undefined], Returns(NumberT, () => NaN)) ] export const divide = plain((a, b) => a / b) -export const cbrt = plain(a => { +const numberCbrt = a => { if (a === 0) return a const negate = a < 0 if (negate) a = -a @@ -25,7 +26,29 @@ export const cbrt = plain(a => { result = (a / (result * result) + (2 * result)) / 3 } 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 multiply = [ plain((a, b) => a * b),