Repository navigation
Context.Pow: exact negative integer powers are padded to the precision since v3.1.0, and precision 0 silently rounds to about 11 digits #161
Description
Context.Pow with an integer exponent has two problems, both visible with the program below (apd v3.2.3).
1. Exact results of negative integer powers no longer get the spec's exponent
With precision 34, Pow(2, -1) returns 0.5000000000000000000000000000000000 (exponent -34) and reports Rounded, and Pow(10, -2) returns 0.01000000000000000000000000000000000. apd v3.0.0 returned 0.5 and 0.01 with no flags, which is what the General Decimal Arithmetic power operation specifies: when "the right-hand side has an integral value and the result is finite and can be expressed exactly within precision digits ... its exponent will be that which would result if the operation were calculated by repeated multiplication (if the second operand is negative then the reciprocal of the first operand is used, with the absolute value of the second operand determining the multiplications)", with the example power('2', '-3') ==> '0.125' (http://speleotrove.com/decimal/daops.html#refpower). Python's decimal returns 0.5 at the same precision.
The change came with 04d1483 ("apd: don't strip trailing zeros in Quo and Sqrt", first released in v3.1.0): integerPower divides one by the computed power with Quo, which now keeps precision digits, so Pow inherits a division convention that the power spec does not share. The GDA test suite in gda_test.go compares results with Cmp, which ignores the exponent, so powx101 power 2 -1 -> 0.5 still passes.
2. Precision 0 is accepted but rounds to about 11 digits
BaseContext documents precision 0 as disabling rounding, and Quo, Ln and Exp reject it with "Context may not have 0 Precision for this operation". Pow instead computes with an internal precision of max(c.Precision, digits(x)) + 10, so BaseContext.Pow(2, 200) returns 1606938044300000000000000000000000000000000000000000000000000 (11 significant digits, Inexact|Rounded) although the exact 61-digit value is cheap to compute, and BaseContext.Pow(2, 0.5) returns 1.4142135624 where Exp and Ln would have returned an error. Either computing an integer power exactly at precision 0, or returning the same zero-precision error as Quo, would be consistent; silently producing 11 digits is neither.
Rounding Pow(2, 200) to 34 digits at precision 34 is correct per the spec; this issue is only about the exponent of exact results and the precision-0 behavior.
Found via https://github.com/cue-lang/cue, which uses apd for its arbitrary-precision numbers.
package main
import (
"fmt"
"github.com/cockroachdb/apd/v3"
)
func pow(c *apd.Context, xs, ys string) {
x, _, _ := apd.NewFromString(xs)
y, _, _ := apd.NewFromString(ys)
var d apd.Decimal
res, err := c.Pow(&d, x, y)
fmt.Printf("prec=%d %s**%s = %s exp=%d cond=%v err=%v\n", c.Precision, xs, ys, d.Text('f'), d.Exponent, res, err)
}
func main() {
c34 := apd.BaseContext.WithPrecision(34)
pow(c34, "2", "-1") // want 0.5 exp=-1 with no flags
pow(c34, "10", "-2") // want 0.01
c0 := &apd.BaseContext
pow(c0, "2", "200") // want the exact 61-digit value, or an error
pow(c0, "2", "0.5") // Ln and Exp refuse precision 0
}Output:
prec=34 2**-1 = 0.5000000000000000000000000000000000 exp=-34 cond=rounded err=<nil>
prec=34 10**-2 = 0.01000000000000000000000000000000000 exp=-35 cond=rounded err=<nil>
prec=0 2**200 = 1606938044300000000000000000000000000000000000000000000000000 exp=50 cond=inexact, rounded err=<nil>
prec=0 2**0.5 = 1.4142135624 exp=-10 cond=inexact err=<nil>
Activity