Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/function/arithmetic/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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,

Expand Down
16 changes: 13 additions & 3 deletions src/function/arithmetic/log10.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)`.
*
Expand Down Expand Up @@ -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,

Expand Down
16 changes: 13 additions & 3 deletions src/function/arithmetic/log2.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)`.
*
Expand Down Expand Up @@ -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,

Expand Down
7 changes: 5 additions & 2 deletions src/utils/bigint.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
70 changes: 70 additions & 0 deletions test/unit-tests/function/arithmetic/log-bigint-compat.test.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
})