refactor(Complex): Now a template type!
This means that the real and imaginary parts of a Complex must now be the same type. This seems like a real benefit: a Complex with a number real part and a bigint imaginary part does not seem sensible. Note that this is now straining typed-function in (at least) the following ways: (1) In this change, it was necessary to remove the logic that the square root of a negative number calls complex square root, which then calls back to the number square root in its algorithm. (This was creating a circular reference in the typed-function which the old implementation of Complex was somehow sidestepping.) (2) typed-function could not follow conversions that would be allowed by uninstantiated templates (e.g. number => Complex<number> if the latter template has not been instantiated) and so the facility for instantiating a template was surfaced (and for example is called explicitly in the demo loader `extendToComplex`. Similarly, this necessitated making the unary signature of the `complex` conversion function explicit, rather than just via implicit conversion to Complex. (3) I find the order of implementations is mattering more in typed-function definitions, implying that typed-function's sorting algorithm is having trouble distinguishing alternatives. But otherwise, the conversion went quite smoothly and I think is a good demo of the power of this approach. And I expect that it will work even more smoothly if some of the underlying facilities (subtypes, template types) are integrated into typed-function.
This commit is contained in:
parent
845a2354c9
commit
1444b9828f
25 changed files with 240 additions and 157 deletions
|
@ -16,8 +16,8 @@ describe('The default full pocomath instance "math"', () => {
|
|||
assert.strictEqual(math.typeOf(-1.5), 'number')
|
||||
assert.strictEqual(math.typeOf(-42n), 'bigint')
|
||||
assert.strictEqual(math.typeOf(undefined), 'undefined')
|
||||
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger')
|
||||
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex')
|
||||
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex<bigint>')
|
||||
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex<number>')
|
||||
})
|
||||
|
||||
it('can subtract numbers', () => {
|
||||
|
@ -105,11 +105,9 @@ describe('The default full pocomath instance "math"', () => {
|
|||
|
||||
it('calculates multi-way gcds and lcms', () => {
|
||||
assert.strictEqual(math.gcd(30,105,42), 3)
|
||||
assert.ok(
|
||||
math.associate(
|
||||
math.lcm(
|
||||
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)),
|
||||
math.complex(1n,3n)))
|
||||
const gaussianLCM = math.lcm(
|
||||
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n))
|
||||
assert.strictEqual(math.associate(gaussianLCM, math.complex(1n,3n)), true)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -49,6 +49,15 @@ describe('complex', () => {
|
|||
assert.deepStrictEqual(
|
||||
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
|
||||
math.complex(4n, 5n))
|
||||
// And now works for NumInt, too!
|
||||
assert.deepStrictEqual(
|
||||
math.gcd(math.complex(53,56), math.complex(47, -13)),
|
||||
math.complex(4, 5))
|
||||
// But properly fails for general complex
|
||||
assert.throws(
|
||||
() => math.gcd(math.complex(5.3,5.6), math.complex(4.7, -1.3)),
|
||||
TypeError
|
||||
)
|
||||
})
|
||||
|
||||
it('computes floor', () => {
|
||||
|
|
|
@ -3,12 +3,14 @@ import math from '../src/pocomath.mjs'
|
|||
import PocomathInstance from '../src/core/PocomathInstance.mjs'
|
||||
import * as numbers from '../src/number/all.mjs'
|
||||
import * as numberAdd from '../src/number/add.mjs'
|
||||
import * as numberZero from '../src/number/zero.mjs'
|
||||
import {add as genericAdd} from '../src/generic/arithmetic.mjs'
|
||||
import * as complex from '../src/complex/all.mjs'
|
||||
import * as complexAdd from '../src/complex/add.mjs'
|
||||
import * as complexNegate from '../src/complex/negate.mjs'
|
||||
import * as complexComplex from '../src/complex/complex.mjs'
|
||||
import * as bigintAdd from '../src/bigint/add.mjs'
|
||||
import * as bigintZero from '../src/bigint/zero.mjs'
|
||||
import * as concreteSubtract from '../src/generic/subtract.concrete.mjs'
|
||||
import * as genericSubtract from '../src/generic/subtract.mjs'
|
||||
import extendToComplex from '../src/complex/extendToComplex.mjs'
|
||||
|
@ -54,9 +56,9 @@ describe('A custom instance', () => {
|
|||
math.complex(-5, -1))
|
||||
// And now floor has been activated for Complex as well, since the type
|
||||
// is present
|
||||
assert.deepStrictEqual(
|
||||
pm.floor(math.complex(1.9, 0)),
|
||||
math.complex(1))
|
||||
const fracComplex = math.complex(1.9, 0)
|
||||
const intComplex = math.complex(1)
|
||||
assert.deepStrictEqual(pm.floor(fracComplex), intComplex)
|
||||
// And the chain functions refresh themselves:
|
||||
assert.deepStrictEqual(
|
||||
pm.chain(5).add(pm.chain(0).complex(7).value).value, math.complex(5,7))
|
||||
|
@ -65,10 +67,11 @@ describe('A custom instance', () => {
|
|||
it("can defer definition of (even used) types", () => {
|
||||
const dt = new PocomathInstance('Deferred Types')
|
||||
dt.install(numberAdd)
|
||||
dt.install(numberZero) // for promoting numbers to complex, to fill in im
|
||||
dt.install({times: {
|
||||
'number,number': () => (m,n) => m*n,
|
||||
'Complex,Complex': ({complex}) => (w,z) => {
|
||||
return complex(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re)
|
||||
'Complex<T>,Complex<T>': ({'complex(T,T)': cplx}) => (w,z) => {
|
||||
return cplx(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re)
|
||||
}
|
||||
}})
|
||||
// complex type not present but should still be able to add numbers:
|
||||
|
@ -82,6 +85,7 @@ describe('A custom instance', () => {
|
|||
it("can selectively import in cute ways", async function () {
|
||||
const cherry = new PocomathInstance('cherry')
|
||||
cherry.install(numberAdd)
|
||||
cherry.install(numberZero) // for complex promotion
|
||||
await extendToComplex(cherry)
|
||||
cherry.install({add: genericAdd})
|
||||
/* Now we have an instance that supports addition for number and complex
|
||||
|
@ -124,12 +128,13 @@ describe('A custom instance', () => {
|
|||
inst.install(complexAdd)
|
||||
inst.install(complexComplex)
|
||||
inst.install(bigintAdd)
|
||||
inst.install(bigintZero) // for complex promotion
|
||||
assert.strictEqual(
|
||||
inst.typeMerge(6n, inst.complex(3n, 2n)),
|
||||
'Merge to GaussianInteger')
|
||||
'Merge to Complex<bigint>')
|
||||
assert.strictEqual(
|
||||
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
||||
'Merge to Complex')
|
||||
'Merge to Complex<number>')
|
||||
// The following is the current behavior, since 3 converts to 3+0i
|
||||
// which is technically the same Complex type as 3n+0ni.
|
||||
// This should clear up when Complex is templatized
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue