diff --git a/src/function/arithmetic/log.js b/src/function/arithmetic/log.js index 3dadeaa40e..262f1f225c 100644 --- a/src/function/arithmetic/log.js +++ b/src/function/arithmetic/log.js @@ -3,10 +3,10 @@ import { promoteLogarithm } from '../../utils/bigint.js' import { logNumber } from '../../plain/number/index.js' const name = 'log' -const dependencies = ['config', 'typed', 'typeOf', 'divideScalar', 'Complex'] +const dependencies = ['config', 'typed', 'typeOf', 'divideScalar', 'Complex', 'BigNumber'] const nlg16 = Math.log(16) -export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, typeOf, config, divideScalar, Complex }) => { +export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, typeOf, config, divideScalar, Complex, BigNumber }) => { /** * Calculate the logarithm of a value. * @@ -60,7 +60,17 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, t } }, - bigint: promoteLogarithm(nlg16, logNumber, config, complexLogNumber), + bigint: function (b) { + if (config.number === 'bigint' && config.numberFallback === 'BigNumber') { + const x = new BigNumber(b.toString()) + if (!x.isNegative() || config.predictable) { + return x.ln() + } else { + return complexLogNumber(x.toNumber()) + } + } + return promoteLogarithm(nlg16, logNumber, config, complexLogNumber)(b) + }, Complex: complexLog, diff --git a/src/function/arithmetic/log10.js b/src/function/arithmetic/log10.js index 8cde9a3d27..c006b7b36d 100644 --- a/src/function/arithmetic/log10.js +++ b/src/function/arithmetic/log10.js @@ -4,10 +4,10 @@ import { deepMap } from '../../utils/collection.js' import { factory } from '../../utils/factory.js' const name = 'log10' -const dependencies = ['typed', 'config', 'Complex'] +const dependencies = ['typed', 'config', 'Complex', 'BigNumber'] const log16 = log10Number(16) -export const createLog10 = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, Complex }) => { +export const createLog10 = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, Complex, BigNumber }) => { /** * Calculate the 10-base logarithm of a value. This is the same as calculating `log(x, 10)`. * @@ -51,7 +51,17 @@ export const createLog10 = /* #__PURE__ */ factory(name, dependencies, ({ typed, } }, - bigint: promoteLogarithm(log16, log10Number, config, complexLogNumber), + bigint: function (b) { + if (config.number === 'bigint' && config.numberFallback === 'BigNumber') { + const x = new BigNumber(b.toString()) + if (!x.isNegative() || config.predictable) { + return x.log() + } else { + return complexLogNumber(x.toNumber()) + } + } + return promoteLogarithm(log16, log10Number, config, complexLogNumber)(b) + }, Complex: complexLog, diff --git a/src/function/arithmetic/log2.js b/src/function/arithmetic/log2.js index 19b239f7cf..3eab422f25 100644 --- a/src/function/arithmetic/log2.js +++ b/src/function/arithmetic/log2.js @@ -4,9 +4,9 @@ import { deepMap } from '../../utils/collection.js' import { factory } from '../../utils/factory.js' const name = 'log2' -const dependencies = ['typed', 'config', 'Complex'] +const dependencies = ['typed', 'config', 'Complex', 'BigNumber'] -export const createLog2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, Complex }) => { +export const createLog2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, Complex, BigNumber }) => { /** * Calculate the 2-base of a value. This is the same as calculating `log(x, 2)`. * @@ -46,7 +46,17 @@ export const createLog2 = /* #__PURE__ */ factory(name, dependencies, ({ typed, } }, - bigint: promoteLogarithm(4, log2Number, config, complexLog2Number), + bigint: function (b) { + if (config.number === 'bigint' && config.numberFallback === 'BigNumber') { + const x = new BigNumber(b.toString()) + if (!x.isNegative() || config.predictable) { + return x.log(2) + } else { + return complexLog2Number(x.toNumber()) + } + } + return promoteLogarithm(4, log2Number, config, complexLog2Number)(b) + }, Complex: _log2Complex, diff --git a/src/utils/bigint.js b/src/utils/bigint.js index 4a163cfdfa..6763733cc7 100644 --- a/src/utils/bigint.js +++ b/src/utils/bigint.js @@ -16,12 +16,15 @@ */ export function promoteLogarithm (log16, numberLog, config, cplx) { return function (b) { + if (b === 0n) { + return numberLog(0) + } if (b > 0 || config.predictable) { - if (b <= 0) return NaN + if (b < 0) return NaN const s = b.toString(16) const s15 = s.substring(0, 15) return log16 * (s.length - s15.length) + numberLog(Number('0x' + s15)) } - return cplx(b.toNumber()) + return cplx(Number(b)) } } diff --git a/test/unit-tests/function/arithmetic/log-bigint-compat.test.js b/test/unit-tests/function/arithmetic/log-bigint-compat.test.js new file mode 100644 index 0000000000..e42ec30ebf --- /dev/null +++ b/test/unit-tests/function/arithmetic/log-bigint-compat.test.js @@ -0,0 +1,70 @@ +// Regression tests for bigint + high-precision floating point arithmetic (#3539) +import assert from 'assert' + +import { approxDeepEqual } from '../../../../tools/approx.js' +import math from '../../../../src/defaultInstance.js' + +describe('log-bigint-compat', function () { + it('log10(0n) returns -Infinity (no crash)', function () { + assert.strictEqual(math.log10(0n), -Infinity) + }) + + describe('with number: bigint and numberFallback: BigNumber', function () { + const m = math.create({ + number: 'bigint', + numberFallback: 'BigNumber', + precision: 80 + }) + + it('log returns BigNumber for positive bigint', function () { + const r = m.log(123n) + assert.strictEqual(m.typeOf(r), 'BigNumber') + // sanity: compare to double for closeness (not equality) + approxDeepEqual(Number(r.toString()), Math.log(123)) + }) + + it('log10(10n) -> BigNumber(1)', function () { + const r = m.log10(10n) + assert.strictEqual(m.typeOf(r), 'BigNumber') + assert.deepStrictEqual(r, m.bignumber(1)) + }) + + it('log2(1024n) -> BigNumber(10)', function () { + const r = m.log2(1024n) + assert.strictEqual(m.typeOf(r), 'BigNumber') + assert.deepStrictEqual(r, m.bignumber(10)) + }) + + it('floor(log10(1n)) -> BigNumber(0) without round error', function () { + const r = m.floor(m.log10(1n)) + assert.strictEqual(m.typeOf(r), 'BigNumber') + assert.deepStrictEqual(r, m.bignumber(0)) + }) + + it('negative bigint: predictable true -> NaN', function () { + const mp = m.create({ predictable: true }) + const r = mp.log(-5n) + assert.strictEqual(typeof r, 'number') + assert.ok(Number.isNaN(r)) + }) + + it('negative bigint: predictable false -> Complex (downgraded)', function () { + const r = m.log(-5n) + assert.strictEqual(m.typeOf(r), 'Complex') + }) + }) + + describe('with number: bigint and numberFallback: number', function () { + const m = math.create({ + number: 'bigint', + numberFallback: 'number' + }) + + it('promoteLogarithm path returns JS numbers', function () { + // log2 of a big power should be exact + assert.strictEqual(m.log2(2n ** 70n), 70) + // log10 of 10^16 + assert.strictEqual(m.log10(10n ** 16n), 16) + }) + }) +})