From 73b58502155618adb5cf03abbf281dd544c16a64 Mon Sep 17 00:00:00 2001 From: Solaris-star <820622658@qq.com> Date: Tue, 21 Jul 2026 20:31:01 +0800 Subject: [PATCH] fix: keep exact negative powers of ten under default precision DivRound with PowPrecisionNegativeExponent (16) collapsed exact values like 10**-17 to zero. When the reciprocal rounds to zero, retry with enough digits for the positive power result. Fixes #394 --- decimal.go | 22 ++++++++++++++++++++-- decimal_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/decimal.go b/decimal.go index 608a12e..961f2c6 100644 --- a/decimal.go +++ b/decimal.go @@ -907,7 +907,7 @@ func (d Decimal) PowInt32(exp int32) (Decimal, error) { } if isExpNeg { - return New(1, 0).DivRound(result, int32(PowPrecisionNegativeExponent)), nil + return divForNegativePow(result, int32(PowPrecisionNegativeExponent)), nil } return result, nil @@ -955,12 +955,30 @@ func (d Decimal) powBigIntWithPrecision(exp *big.Int, precision int32) (Decimal, } if isExpNeg { - return New(1, 0).DivRound(result, precision), nil + return divForNegativePow(result, precision), nil } return result, nil } +// divForNegativePow computes 1/result with DivRound(precision). +// If the configured precision is too small to represent a non-zero reciprocal +// (e.g. 10**-17 with PowPrecisionNegativeExponent=16), retry with enough digits +// so exact powers of ten are not collapsed to zero (#394). +func divForNegativePow(result Decimal, precision int32) Decimal { + q := New(1, 0).DivRound(result, precision) + if !q.IsZero() || result.IsZero() { + return q + } + needed := int32(result.NumDigits()) + if needed < precision { + needed = precision + } + // One extra digit covers borderline powers of ten such as 10**n (n digits of 1 + // followed by zeros? 10**17 has 18 digits) — NumDigits already returns 18. + return New(1, 0).DivRound(result, needed) +} + // ExpHullAbrham calculates the natural exponent of decimal (e to the power of d) using Hull-Abraham algorithm. // OverallPrecision argument specifies the overall precision of the result (integer part + decimal part). // diff --git a/decimal_test.go b/decimal_test.go index 161facf..97b2290 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -2870,6 +2870,33 @@ func TestDecimal_PowInt32(t *testing.T) { } } +func TestDecimal_PowNegativeIntegerPowersOfTen(t *testing.T) { + // Regression for #394: default PowPrecisionNegativeExponent (16) used to + // DivRound 1/10**n to zero for n > 16 even though the value is exact. + for _, exp := range []int32{-16, -17, -18, -20} { + base := NewFromInt(10) + got, err := base.PowInt32(exp) + if err != nil { + t.Fatalf("PowInt32(10, %d) unexpected error: %v", exp, err) + } + want := New(1, exp) // 10**(-n) == 1e-n + if !got.Equal(want) { + t.Errorf("PowInt32(10, %d) = %s, want %s", exp, got, want) + } + got2 := base.Pow(NewFromInt(int64(exp))) + if !got2.Equal(want) { + t.Errorf("Pow(10, %d) = %s, want %s", exp, got2, want) + } + got3, err := base.PowBigInt(big.NewInt(int64(exp))) + if err != nil { + t.Fatalf("PowBigInt(10, %d) unexpected error: %v", exp, err) + } + if !got3.Equal(want) { + t.Errorf("PowBigInt(10, %d) = %s, want %s", exp, got3, want) + } + } +} + func TestDecimal_PowInt32_UndefinedResult(t *testing.T) { base := RequireFromString("0")