Skip to content

Preserve the spec exponent of exact integer powers (#161) - #162

Open
chiliec wants to merge 1 commit into
cockroachdb:masterfrom
chiliec:fix-pow-exact-integer-exponent
Open

chiliec wants to merge 1 commit into
cockroachdb:masterfrom
chiliec:fix-pow-exact-integer-exponent

Conversation

@chiliec

@chiliec chiliec commented Oct 18, 2026

Copy link
Copy Markdown

What

Context.Pow with an exact negative integer exponent no longer preserves the exponent that the General Decimal Arithmetic power operation specifies.

At precision 34, Pow(2, -1) returns 0.5000000000000000000000000000000000 (exponent -34) and reports Rounded; Pow(10, -2) returns 0.0100...0. apd v3.0.0 returned 0.5 and 0.01 with no flags. The GDA power operation says an exact integer-power result carries the exponent it would have under repeated multiplication (with the reciprocal of the base for a negative exponent), e.g. power('2','-3') ==> '0.125' (http://speleotrove.com/decimal/daops.html#refpower). Python's decimal returns 0.5 at the same precision.

The regression came in with 04d1483 ("apd: don't strip trailing zeros in Quo and Sqrt", v3.1.0): integerPower divides one by the computed power with Quo, which now keeps precision digits, so Pow inherited a division convention the power spec does not share. The GDA suite in gda_test.go compares with Cmp, which ignores the exponent, so powx101 power 2 -1 -> 0.5 still passed.

Reported in #161 (found via cue-lang/cue, which uses apd for arbitrary-precision numbers). This PR addresses the exact-exponent part only, not the separate precision-0 question in that issue.

Fix

In integerPower, after computing the reciprocal for a negative exponent, Reduce the quotient when it is exact (!Inexact). An exact reciprocal of an integer power has no genuine trailing zeros, so this strips only the precision padding and restores the spec exponent; the Rounded flag is no longer raised for exact results. Inexact reciprocals (e.g. 3**-1) are left untouched.

Tests

Added TestPowExactInteger in decimal_test.go asserting the text, exponent, and condition flags for exact positive and negative integer powers plus one inexact case.

Validation (go1.23.2, linux/amd64):

$ go test -run TestPowExactInteger -v ./
--- PASS: TestPowExactInteger (0.00s)
    --- PASS: TestPowExactInteger/2**-1
    --- PASS: TestPowExactInteger/2**-3
    --- PASS: TestPowExactInteger/10**-2
    --- PASS: TestPowExactInteger/5**-2
    --- PASS: TestPowExactInteger/2**3
    --- PASS: TestPowExactInteger/10**3
    --- PASS: TestPowExactInteger/3**-1
PASS

$ go test ./
ok  	github.com/cockroachdb/apd/v3	3.372s

The full suite (including the GDA gda_test.go power tests) passes on the branch.

Context.Pow computes negative integer powers as 1/x**|y| via Quo, which
since v3.1.0 keeps precision trailing zeros, so Pow(2, -1) at precision 34
returned 0.5000...0 (exponent -34, Rounded) instead of 0.5. The General
Decimal Arithmetic power operation specifies that an exact integer-power
result carries the exponent it would have under repeated multiplication,
e.g. power('2','-3') ==> '0.125'.

Reduce the quotient when the reciprocal is exact so the exact result keeps
its natural exponent and no longer reports Rounded. Inexact reciprocals are
left untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant