From e49f4abfa3781180ff7f9b8ea6fe60d91b2a2cdd Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 23 Jul 2026 13:19:08 +0100 Subject: [PATCH 1/3] fix: mod of two bigints with a negative divisor mod(x, y) is documented as floored modulo (x - y * floor(x / y)), whose result takes the sign of the divisor, and the number, BigNumber and Fraction implementations all follow it. The bigint handler branched on the sign of the dividend and otherwise returned JavaScript's `%`, which takes the sign of the dividend, so for a negative divisor it produced the wrong sign: mod(10n, -4n) returned 2n instead of -2n. Compute the floored remainder for bigints directly, correcting the `%` result when its sign disagrees with the divisor, so bigint matches the other numeric types for every sign combination. --- src/function/arithmetic/mod.js | 12 ++++++++---- test/unit-tests/function/arithmetic/mod.test.js | 7 +++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index 6c00537d38..3e78d3c24b 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -83,12 +83,16 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c return x } - if (x < 0) { - const m = x % y - return m === 0n ? m : m + y + // floored modulo: the result takes the sign of the divisor, so that + // bigint matches the number, BigNumber and Fraction implementations + // (x - y * floor(x / y)). JavaScript's % takes the sign of the + // dividend, so correct it when the remainder and divisor disagree. + const m = x % y + if (m !== 0n && (m < 0n) !== (y < 0n)) { + return m + y } - return x % y + return m }, 'Fraction, Fraction': function (x, y) { diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index 5055a0f0b5..e5688ca128 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -48,6 +48,13 @@ describe('mod', function () { assert.strictEqual(mod(-8n, 4n), 0n) }) + it('should calculate the modulus of bigints for a negative divisor', function () { + assert.strictEqual(mod(10n, -4n), -2n) + assert.strictEqual(mod(-10n, -4n), -2n) + assert.strictEqual(mod(8n, -3n), -1n) + assert.strictEqual(mod(-8n, -3n), -2n) + }) + it('should handle precise approximation of float approximation', function () { approxEqual(mod(0.1, 0.01), 0) approxEqual(mod(0.15, 0.05), 0) From aace24da070c79ea089f0693b91dca209aa1be9b Mon Sep 17 00:00:00 2001 From: Yarchik Date: Wed, 4 Nov 2026 22:24:54 +0000 Subject: [PATCH 2/3] fix: mod of two negative bigints --- src/function/arithmetic/mod.js | 3 +-- test/unit-tests/function/arithmetic/invmod.test.js | 2 ++ test/unit-tests/function/arithmetic/mod.test.js | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index 4d2c6cfbd7..f75a19b63e 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -128,7 +128,6 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c const m = x % y - if (x < 0 || y < 0) return m === 0n ? m : m + y - return m + return m !== 0n && (m < 0n) !== (y < 0n) ? m + y : m } }) diff --git a/test/unit-tests/function/arithmetic/invmod.test.js b/test/unit-tests/function/arithmetic/invmod.test.js index f5b2bfde9c..e1f59fab1d 100644 --- a/test/unit-tests/function/arithmetic/invmod.test.js +++ b/test/unit-tests/function/arithmetic/invmod.test.js @@ -42,6 +42,8 @@ describe('invmod', function () { it('should calculate invmod for bigints', function () { assert.strictEqual(invmod(5n, 13n), 8n) assert.strictEqual(invmod(6n, -17n), -14n) + assert.strictEqual(invmod(2n, -7n), -3n) + assert.strictEqual(invmod(5n, -13n), -5n) }) it('should calculate invmod for mixed BigNumbers and Numbers', function () { diff --git a/test/unit-tests/function/arithmetic/mod.test.js b/test/unit-tests/function/arithmetic/mod.test.js index 4f680e96d2..f266e21625 100644 --- a/test/unit-tests/function/arithmetic/mod.test.js +++ b/test/unit-tests/function/arithmetic/mod.test.js @@ -53,6 +53,8 @@ describe('mod', function () { assert.strictEqual(mod(-10n, -4n), -2n) assert.strictEqual(mod(8n, -3n), -1n) assert.strictEqual(mod(-8n, -3n), -2n) + assert.strictEqual(mod(8n, -4n), 0n) + assert.strictEqual(mod(-8n, -4n), 0n) }) it('should handle precise approximation of float approximation', function () { @@ -118,6 +120,8 @@ describe('mod', function () { assert.strictEqual(mod(14, 8n), 6n) assert.strictEqual(mod(12.5, 37n), 12.5) assert.strictEqual(mod(14n, 8.5), 5.5) + assert.strictEqual(mod(-10, -4n), -2n) + assert.strictEqual(mod(-10n, -4), -2n) }) it('should calculate the modulus of mixed booleans and bignumbers', function () { From 4ff38efc1dc78380b6cb4ce81f27ebab13278125 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Thu, 5 Nov 2026 08:14:37 -0800 Subject: [PATCH 3/3] chore: Add comment, update AUTHORS and HISTORY --- AUTHORS | 4 ++-- HISTORY.md | 2 ++ src/function/arithmetic/mod.js | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 09402d47a8..722ec96a87 100644 --- a/AUTHORS +++ b/AUTHORS @@ -287,15 +287,15 @@ Dheemanth D <165369664+Dheemanth07@users.noreply.github.com> Adriano Fantini Chibuike Anselm Jos de Jong +Yarchik rrbharath Lê Vạn Bảo Trọng <32262183+chuanghiduoc@users.noreply.github.com> Hugohong258 -Spoko Dev Yash Goyal <24110399@iitgn.ac.in> Wei-Lun Li -waw666waw666 <184799977+waw666waw666@users.noreply.github.com> wulimammoth kevin <5299031+kevin9327@users.noreply.github.com> WenJing95 <126436871+WenJing95@users.noreply.github.com> +W <184799977+waw666waw666@users.noreply.github.com> # Generated by tools/update-authors.js diff --git a/HISTORY.md b/HISTORY.md index 0c4bd917ec..3c7c18f3ea 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,8 @@ # unpublished changes since 15.2.0 +- Fix: Adjust #3710 to ensure that mod of bigints ends up in intended + range for all sign combinations (#3684). Thanks @spokodev. - Fix: #3758 correct gcd and atan2 when computed on arguments including sparse matrices (#3751). Thanks @waw666waw666. - Fix: #3160, #3202 correct TypeScript types for various cases of matrix diff --git a/src/function/arithmetic/mod.js b/src/function/arithmetic/mod.js index f75a19b63e..36d506e633 100644 --- a/src/function/arithmetic/mod.js +++ b/src/function/arithmetic/mod.js @@ -127,7 +127,8 @@ export const createMod = /* #__PURE__ */ factory(name, dependencies, ({ typed, c } const m = x % y - + // Ensure result m is always in desired range: 0 ≤ m < y for y positive, + // y < m ≤ 0 for y negative: return m !== 0n && (m < 0n) !== (y < 0n) ? m + y : m } })