Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,23 @@ func (c *Context) rootSpecials(d, x *Decimal, factor int32) (bool, Condition, er
}
case 0:
d.Set(x)
d.Exponent /= factor
return true, 0, nil
// The ideal exponent is the operand's exponent divided by factor,
// rounded towards negative infinity; Go's / truncates towards zero.
e := x.Exponent / factor
if x.Exponent%factor != 0 && x.Exponent < 0 {
e--
Comment on lines +467 to +468

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Cbrt's zero exponent contract

Because rootSpecials is also called by Cbrt, flooring here changes positive cube-root results with negative, non-divisible exponents: for example, Cbrt("00.0") now returns 0.0 with exponent -1, while testdata/cuberoot-apd.decTest cases cbtx006–cbtx008 require exponent 0. TestGDA does not expose this regression because it compares zeros numerically, and the new targeted test only selects the contradictory negative-zero expectations. Restrict the flooring change to square root, or first define and consistently update and test the cube-root contract.

Useful? React with 👍 / 👎.

}
var res Condition
if etiny := c.etiny(); e < etiny {
e = etiny
res = Clamped
} else if e > c.MaxExponent {
e = c.MaxExponent
res = Clamped
}
d.Exponent = e
res, err := c.goError(res)
return true, res, err
}
return false, 0, nil
}
Expand Down
9 changes: 1 addition & 8 deletions gda_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -846,13 +846,6 @@ var GDAignore = map[string]bool{
}

var GDAignoreFlags = map[string]bool{
// unflagged clamped
"sqtx9024": true,
"sqtx9025": true,
"sqtx9026": true,
"sqtx9027": true,
"sqtx9038": true,
"sqtx9039": true,
"sqtx9040": true,
// apd does not implement the clamp context directive.
"sqtx9045": true,
}
66 changes: 66 additions & 0 deletions zeroroot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package apd

import "testing"

// TestRootSpecialsZeroExponent checks the exponent and conditions of a
// square root or cube root of zero. TestGDA compares finite results with
// Cmp, which considers every zero equal regardless of exponent, so it
// cannot check these cases even though testdata carries them.
func TestRootSpecialsZeroExponent(t *testing.T) {
tests := []struct {
id string
prec uint32
emax int32
emin int32
cbrt bool
x string
exp int32
neg bool
cond Condition
}{
// testdata/squareroot.decTest
{id: "sqtx006", prec: 9, emax: 384, emin: -383, x: "00.0", exp: -1},
{id: "sqtx009", prec: 9, emax: 384, emin: -383, x: "00.000", exp: -2},
{id: "sqtx017", prec: 9, emax: 384, emin: -383, x: "-0.0", exp: -1, neg: true},
{id: "sqtx019", prec: 9, emax: 384, emin: -383, x: "-00.000", exp: -2, neg: true},
{id: "sqtx9010", prec: 4, emax: 9, emin: -9, x: "0E-9", exp: -5},
{id: "sqtx9012", prec: 4, emax: 9, emin: -9, x: "0E-11", exp: -6},
{id: "sqtx9014", prec: 4, emax: 9, emin: -9, x: "0E-13", exp: -7},
{id: "sqtx9023", prec: 4, emax: 9, emin: -9, x: "0E-24", exp: -12},
{id: "sqtx9024", prec: 4, emax: 9, emin: -9, x: "0E-25", exp: -12, cond: Clamped},
{id: "sqtx9027", prec: 4, emax: 9, emin: -9, x: "0E-28", exp: -12, cond: Clamped},
{id: "sqtx9037", prec: 4, emax: 9, emin: -9, x: "0E+19", exp: 9},
{id: "sqtx9038", prec: 4, emax: 9, emin: -9, x: "0E+20", exp: 9, cond: Clamped},
{id: "sqtx9040", prec: 4, emax: 9, emin: -9, x: "0E+22", exp: 9, cond: Clamped},
// testdata/cuberoot-apd.decTest
{id: "cbtx015", prec: 9, emax: 384, emin: -383, cbrt: true, x: "-0.00", exp: -1, neg: true},
{id: "cbtx016", prec: 9, emax: 384, emin: -383, cbrt: true, x: "-00.0", exp: -1, neg: true},
{id: "cbtx017", prec: 9, emax: 384, emin: -383, cbrt: true, x: "-0E+9", exp: 3, neg: true},
{id: "cbtx020", prec: 9, emax: 384, emin: -383, cbrt: true, x: "-0E+12", exp: 4, neg: true},
}
for _, tc := range tests {
// Parse the operand exactly, as TestGDA does; the context under test
// applies to the operation, not to the literal.
x, _, err := NewFromString(tc.x)
if err != nil {
t.Fatalf("%s: %v", tc.id, err)
}
c := &Context{Precision: tc.prec, MaxExponent: tc.emax, MinExponent: tc.emin, Rounding: RoundHalfUp}
d := new(Decimal)
var res Condition
if tc.cbrt {
res, err = c.Cbrt(d, x)
} else {
res, err = c.Sqrt(d, x)
}
if err != nil {
t.Fatalf("%s: %v", tc.id, err)
}
if !d.IsZero() || d.Exponent != tc.exp || d.Negative != tc.neg {
t.Errorf("%s: got %#v, expected zero with exponent %d, negative %t", tc.id, d, tc.exp, tc.neg)
}
if res != tc.cond {
t.Errorf("%s: got conditions %q, expected %q", tc.id, res, tc.cond)
}
}
}