From 03a2bd6777f1fb2502963c46f51a485d42bb595c Mon Sep 17 00:00:00 2001 From: Michael Haddon Date: Thu, 4 Dec 2025 21:43:57 +0000 Subject: [PATCH] fix negative zero from multiplication/division by zero bigint --- bigint.go | 6 ++--- bigint_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/bigint.go b/bigint.go index 6271a40..3e2cd40 100644 --- a/bigint.go +++ b/bigint.go @@ -325,7 +325,7 @@ func addInline(xVal, yVal uint64, xNeg, yNeg bool) (zVal uint64, zNeg, ok bool) //gcassert:inline func mulInline(xVal, yVal uint64, xNeg, yNeg bool) (zVal uint64, zNeg, ok bool) { hi, lo := bits.Mul64(xVal, yVal) - neg := xNeg != yNeg + neg := xNeg != yNeg && lo != 0 overflow := hi != 0 return lo, neg, !overflow } @@ -336,7 +336,7 @@ func quoInline(xVal, yVal uint64, xNeg, yNeg bool) (quoVal uint64, quoNeg, ok bo return 0, false, false } quo := xVal / yVal - neg := xNeg != yNeg + neg := xNeg != yNeg && quo != 0 return quo, neg, true } @@ -346,7 +346,7 @@ func remInline(xVal, yVal uint64, xNeg, yNeg bool) (remVal uint64, remNeg, ok bo return 0, false, false } rem := xVal % yVal - return rem, xNeg, true + return rem, xNeg && rem != 0, true } /////////////////////////////////////////////////////////////////////////////// diff --git a/bigint_test.go b/bigint_test.go index 92a025f..0334643 100644 --- a/bigint_test.go +++ b/bigint_test.go @@ -1094,6 +1094,74 @@ func TestBigIntMulRangeZ(t *testing.T) { } } +func TestBigIntNoNegativeZero(t *testing.T) { + zero := NewBigInt(0) + negOne := NewBigInt(-1) + one := NewBigInt(1) + negTwo := NewBigInt(-2) + + t.Run("Mul", func(t *testing.T) { + // (-1) * 0 should be 0, not negative zero + var result BigInt + result.Mul(negOne, zero) + if result.Sign() != 0 { + t.Errorf("(-1) * 0: got Sign() = %d, want 0", result.Sign()) + } + if result.Cmp(zero) != 0 { + t.Errorf("(-1) * 0: got Cmp(0) = %d, want 0", result.Cmp(zero)) + } + + // 0 * (-1) should be 0, not negative zero + result.Mul(zero, negOne) + if result.Sign() != 0 { + t.Errorf("0 * (-1): got Sign() = %d, want 0", result.Sign()) + } + }) + + t.Run("Quo", func(t *testing.T) { + // 0 / (-1) should be 0, not negative zero + var result BigInt + result.Quo(zero, negOne) + if result.Sign() != 0 { + t.Errorf("0 / (-1): got Sign() = %d, want 0", result.Sign()) + } + if result.Cmp(zero) != 0 { + t.Errorf("0 / (-1): got Cmp(0) = %d, want 0", result.Cmp(zero)) + } + + // 1 / (-2) should be 0, not negative zero + result.Quo(one, negTwo) + if result.Sign() != 0 { + t.Errorf("1 / (-2): got Sign() = %d, want 0", result.Sign()) + } + }) + + t.Run("Rem", func(t *testing.T) { + // (-2) % 2 should be 0, not negative zero + var result BigInt + two := NewBigInt(2) + result.Rem(negTwo, two) + if result.Sign() != 0 { + t.Errorf("(-2) %% 2: got Sign() = %d, want 0", result.Sign()) + } + if result.Cmp(zero) != 0 { + t.Errorf("(-2) %% 2: got Cmp(0) = %d, want 0", result.Cmp(zero)) + } + }) + + t.Run("QuoRem", func(t *testing.T) { + // 0 / (-1) via QuoRem + var quo, rem BigInt + quo.QuoRem(zero, negOne, &rem) + if quo.Sign() != 0 { + t.Errorf("QuoRem(0, -1): quo Sign() = %d, want 0", quo.Sign()) + } + if rem.Sign() != 0 { + t.Errorf("QuoRem(0, -1): rem Sign() = %d, want 0", rem.Sign()) + } + }) +} + func TestBigIntBinomial(t *testing.T) { var z BigInt for _, test := range []struct {