diff --git a/context.go b/context.go index 9d9e2d8..55c1f86 100644 --- a/context.go +++ b/context.go @@ -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-- + } + 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 } diff --git a/gda_test.go b/gda_test.go index 8bd366e..7701098 100644 --- a/gda_test.go +++ b/gda_test.go @@ -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, } diff --git a/zeroroot_test.go b/zeroroot_test.go new file mode 100644 index 0000000..9a9b05e --- /dev/null +++ b/zeroroot_test.go @@ -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) + } + } +}