From b7358c84932514736dab937eb45c2d24051dcd0d Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:11:36 -0700 Subject: [PATCH] fix: restore BigNumber precision state when decimal.js trig throws precision-limit errors Fixes #3676 --- src/type/bignumber/BigNumber.js | 31 +++++++++++++++++++ .../function/trigonometry/tan.test.js | 11 +++++++ .../type/bignumber/BigNumber.test.js | 28 +++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/src/type/bignumber/BigNumber.js b/src/type/bignumber/BigNumber.js index 9b424f97ac..38ceaabf22 100644 --- a/src/type/bignumber/BigNumber.js +++ b/src/type/bignumber/BigNumber.js @@ -3,11 +3,42 @@ import { factory } from '../../utils/factory.js' const name = 'BigNumber' const dependencies = ['?on', 'config'] +const trigonometricMethods = [ + 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', + 'cos', 'cosh', 'sin', 'sinh', 'tan', 'tanh', + 'cosine', 'hyperbolicCosine', 'hyperbolicSine', 'hyperbolicTangent', + 'inverseCosine', 'inverseHyperbolicCosine', 'inverseHyperbolicSine', + 'inverseHyperbolicTangent', 'inverseSine', 'inverseTangent', 'sine', 'tangent' +] export const createBigNumberClass = /* #__PURE__ */ factory(name, dependencies, ({ on, config }) => { const BigNumber = Decimal.clone({ precision: config.precision, modulo: Decimal.EUCLID }) BigNumber.prototype = Object.create(BigNumber.prototype) + trigonometricMethods.forEach(method => { + const originalMethod = BigNumber.prototype[method] + + BigNumber.prototype[method] = function (...args) { + const precision = BigNumber.precision + const rounding = BigNumber.rounding + + try { + return originalMethod.apply(this, args) + } catch (error) { + if (error instanceof Error && + error.message.includes('Precision limit exceeded') && + !error.message.includes('decimal.js trigonometric functions are limited')) { + error.message += '; decimal.js trigonometric functions are limited to approximately ' + + `1000 digits of working precision (configured precision is ${config.precision})` + } + throw error + } finally { + BigNumber.precision = precision + BigNumber.rounding = rounding + } + } + }) + /** * Attach type information */ diff --git a/test/unit-tests/function/trigonometry/tan.test.js b/test/unit-tests/function/trigonometry/tan.test.js index 90b6e6c13a..10d97601d7 100644 --- a/test/unit-tests/function/trigonometry/tan.test.js +++ b/test/unit-tests/function/trigonometry/tan.test.js @@ -41,6 +41,17 @@ describe('tan', function () { assert.deepStrictEqual(bigTan(bigPi.div(4)).toString(), '0.999999999999999999999') }) + it('should restore BigNumber precision after a decimal.js precision error', function () { + const highPrecisionMath = math.create({ number: 'BigNumber', precision: 509 }) + + assert.throws( + () => highPrecisionMath.evaluate('tan(pi/2)'), + /decimal\.js trigonometric functions are limited/ + ) + assert.strictEqual(highPrecisionMath.BigNumber.precision, 509) + assert.strictEqual(highPrecisionMath.evaluate('1.1 + 2.2').toString(), '3.3') + }) + it('should return the tangent of a complex number', function () { const re = 0.00376402564150425 const im = 1.00323862735360980 diff --git a/test/unit-tests/type/bignumber/BigNumber.test.js b/test/unit-tests/type/bignumber/BigNumber.test.js index a6dcd03eec..23efb8c88c 100644 --- a/test/unit-tests/type/bignumber/BigNumber.test.js +++ b/test/unit-tests/type/bignumber/BigNumber.test.js @@ -32,4 +32,32 @@ describe('BigNumber', function () { assert.strictEqual(a.toJSON(), '2') }) + + it('should restore precision and rounding when a trigonometric method throws', function () { + const bigmath = math.create({ number: 'BigNumber', precision: 509 }) + const BigNumber = bigmath.BigNumber + + assert.throws( + () => bigmath.pi.div(2).tangent(), + error => { + assert.ok(error instanceof Error) + assert.match(error.message, /decimal\.js trigonometric functions are limited/) + assert.match(error.message, /configured precision is 509/) + return true + } + ) + assert.strictEqual(BigNumber.precision, 509) + assert.strictEqual(BigNumber.rounding, Decimal.ROUND_HALF_UP) + + bigmath.config({ precision: 100 }) + + const UnwrappedBigNumber = Decimal.clone({ precision: 100, modulo: Decimal.EUCLID }) + const value = bigmath.pi.div(2) + const expected = new UnwrappedBigNumber(value.toString()).tan() + + assert.strictEqual(BigNumber.precision, 100) + assert.strictEqual(bigmath.tan(value).toString(), expected.toString()) + + bigmath.config({ precision: 509 }) + }) })