Commit bb298754 authored by Robert Griesemer's avatar Robert Griesemer

math/big: implement missing special cases for binary operations

Change-Id: I9fc12b1a9b1554523e08839c1ff46c8668217ba1
Reviewed-on: https://go-review.googlesource.com/8381Reviewed-by: 's avatarAlan Donovan <adonovan@google.com>
parent d6dff636
...@@ -73,7 +73,7 @@ type ErrNaN struct { ...@@ -73,7 +73,7 @@ type ErrNaN struct {
// NewFloat allocates and returns a new Float set to x, // NewFloat allocates and returns a new Float set to x,
// with precision 53 and rounding mode ToNearestEven. // with precision 53 and rounding mode ToNearestEven.
// NewFloat panics with ErrNan if x is a NaN. // NewFloat panics with ErrNaN if x is a NaN.
func NewFloat(x float64) *Float { func NewFloat(x float64) *Float {
if math.IsNaN(x) { if math.IsNaN(x) {
panic(ErrNaN{"NewFloat(NaN)"}) panic(ErrNaN{"NewFloat(NaN)"})
...@@ -1400,8 +1400,9 @@ func (x *Float) ucmp(y *Float) int { ...@@ -1400,8 +1400,9 @@ func (x *Float) ucmp(y *Float) int {
// it is changed to the larger of x's or y's precision before the operation. // it is changed to the larger of x's or y's precision before the operation.
// Rounding is performed according to z's precision and rounding mode; and // Rounding is performed according to z's precision and rounding mode; and
// z's accuracy reports the result error relative to the exact (not rounded) // z's accuracy reports the result error relative to the exact (not rounded)
// result. // result. Add panics with ErrNaN if x and y are infinities with opposite
// BUG(gri) Float.Add panics if an operand is Inf. // signs. The value of z is undefined in that case.
//
// BUG(gri) When rounding ToNegativeInf, the sign of Float values rounded to 0 is incorrect. // BUG(gri) When rounding ToNegativeInf, the sign of Float values rounded to 0 is incorrect.
func (z *Float) Add(x, y *Float) *Float { func (z *Float) Add(x, y *Float) *Float {
if debugFloat { if debugFloat {
...@@ -1413,24 +1414,8 @@ func (z *Float) Add(x, y *Float) *Float { ...@@ -1413,24 +1414,8 @@ func (z *Float) Add(x, y *Float) *Float {
z.prec = umax32(x.prec, y.prec) z.prec = umax32(x.prec, y.prec)
} }
// special cases if x.form == finite && y.form == finite {
if x.form != finite || y.form != finite { // x + y (commom case)
if x.form > finite || y.form > finite {
// TODO(gri) handle Inf separately
panic("Inf operand")
}
if x.form == zero {
z.Set(y)
if z.form == zero {
z.neg = x.neg && y.neg // -0 + -0 == -0
}
return z
}
// y == ±0
return z.Set(x)
}
// x, y != 0
z.neg = x.neg z.neg = x.neg
if x.neg == y.neg { if x.neg == y.neg {
// x + y == x + y // x + y == x + y
...@@ -1446,13 +1431,42 @@ func (z *Float) Add(x, y *Float) *Float { ...@@ -1446,13 +1431,42 @@ func (z *Float) Add(x, y *Float) *Float {
z.usub(y, x) z.usub(y, x)
} }
} }
return z
}
if x.form == inf && y.form == inf && x.neg != y.neg {
// +Inf + -Inf
// -Inf + +Inf
// value of z is undefined but make sure it's valid
z.acc = Exact
z.form = zero
z.neg = false
panic(ErrNaN{"addition of infinities with opposite signs"})
}
if x.form == zero && y.form == zero {
// ±0 + ±0
z.acc = Exact
z.form = zero
z.neg = x.neg && y.neg // -0 + -0 == -0
return z return z
}
if x.form == inf || y.form == zero {
// ±Inf + y
// x + ±0
return z.Set(x)
}
// ±0 + y
// x + ±Inf
return z.Set(y)
} }
// Sub sets z to the rounded difference x-y and returns z. // Sub sets z to the rounded difference x-y and returns z.
// Precision, rounding, and accuracy reporting are as for Add. // Precision, rounding, and accuracy reporting are as for Add.
// BUG(gri) Float.Sub panics if an operand is Inf. // Sub panics with ErrNaN if x and y are infinities with equal
// signs. The value of z is undefined in that case.
func (z *Float) Sub(x, y *Float) *Float { func (z *Float) Sub(x, y *Float) *Float {
if debugFloat { if debugFloat {
x.validate() x.validate()
...@@ -1463,24 +1477,8 @@ func (z *Float) Sub(x, y *Float) *Float { ...@@ -1463,24 +1477,8 @@ func (z *Float) Sub(x, y *Float) *Float {
z.prec = umax32(x.prec, y.prec) z.prec = umax32(x.prec, y.prec)
} }
// special cases if x.form == finite && y.form == finite {
if x.form != finite || y.form != finite { // x - y (common case)
if x.form > finite || y.form > finite {
// TODO(gri) handle Inf separately
panic("Inf operand")
}
if x.form == zero {
z.Neg(y)
if z.form == zero {
z.neg = x.neg && !y.neg // -0 - 0 == -0
}
return z
}
// y == ±0
return z.Set(x)
}
// x, y != 0
z.neg = x.neg z.neg = x.neg
if x.neg != y.neg { if x.neg != y.neg {
// x - (-y) == x + y // x - (-y) == x + y
...@@ -1496,13 +1494,42 @@ func (z *Float) Sub(x, y *Float) *Float { ...@@ -1496,13 +1494,42 @@ func (z *Float) Sub(x, y *Float) *Float {
z.usub(y, x) z.usub(y, x)
} }
} }
return z
}
if x.form == inf && y.form == inf && x.neg == y.neg {
// +Inf - +Inf
// -Inf - -Inf
// value of z is undefined but make sure it's valid
z.acc = Exact
z.form = zero
z.neg = false
panic(ErrNaN{"subtraction of infinities with equal signs"})
}
if x.form == zero && y.form == zero {
// ±0 - ±0
z.acc = Exact
z.form = zero
z.neg = x.neg && !y.neg // -0 - +0 == -0
return z return z
}
if x.form == inf || y.form == zero {
// ±Inf - y
// x - ±0
return z.Set(x)
}
// ±0 - y
// x - ±Inf
return z.Neg(y)
} }
// Mul sets z to the rounded product x*y and returns z. // Mul sets z to the rounded product x*y and returns z.
// Precision, rounding, and accuracy reporting are as for Add. // Precision, rounding, and accuracy reporting are as for Add.
// BUG(gri) Float.Mul panics if an operand is Inf. // Mul panics with ErrNaN if one operand is zero and the other
// operand an infinity. The value of z is undefined in that case.
func (z *Float) Mul(x, y *Float) *Float { func (z *Float) Mul(x, y *Float) *Float {
if debugFloat { if debugFloat {
x.validate() x.validate()
...@@ -1515,28 +1542,39 @@ func (z *Float) Mul(x, y *Float) *Float { ...@@ -1515,28 +1542,39 @@ func (z *Float) Mul(x, y *Float) *Float {
z.neg = x.neg != y.neg z.neg = x.neg != y.neg
// special cases if x.form == finite && y.form == finite {
if x.form != finite || y.form != finite { // x * y (common case)
if x.form > finite || y.form > finite { z.umul(x, y)
// TODO(gri) handle Inf separately return z
panic("Inf operand")
} }
// x == ±0 || y == ±0
z.acc = Exact z.acc = Exact
if x.form == zero && y.form == inf || x.form == inf && y.form == zero {
// ±0 * ±Inf
// ±Inf * ±0
// value of z is undefined but make sure it's valid
z.form = zero z.form = zero
return z z.neg = false
panic(ErrNaN{"multiplication of zero with infinity"})
} }
// x, y != 0 if x.form == inf || y.form == inf {
z.umul(x, y) // ±Inf * y
// x * ±Inf
z.form = inf
return z
}
// ±0 * y
// x * ±0
z.form = zero
return z return z
} }
// Quo sets z to the rounded quotient x/y and returns z. // Quo sets z to the rounded quotient x/y and returns z.
// Precision, rounding, and accuracy reporting are as for Add. // Precision, rounding, and accuracy reporting are as for Add.
// Quo panics is both operands are 0. // Quo panics with ErrNaN if both operands are zero or infinities.
// BUG(gri) Float.Quo panics if an operand is Inf. // The value of z is undefined in that case.
func (z *Float) Quo(x, y *Float) *Float { func (z *Float) Quo(x, y *Float) *Float {
if debugFloat { if debugFloat {
x.validate() x.validate()
...@@ -1549,29 +1587,32 @@ func (z *Float) Quo(x, y *Float) *Float { ...@@ -1549,29 +1587,32 @@ func (z *Float) Quo(x, y *Float) *Float {
z.neg = x.neg != y.neg z.neg = x.neg != y.neg
// special cases if x.form == finite && y.form == finite {
z.acc = Exact // x / y (common case)
if x.form != finite || y.form != finite { z.uquo(x, y)
if x.form > finite || y.form > finite { return z
// TODO(gri) handle Inf separately
panic("Inf operand")
}
// x == ±0 || y == ±0
if x.form == zero {
if y.form == zero {
panic("0/0")
} }
z.acc = Exact
if x.form == zero && y.form == zero || x.form == inf && y.form == inf {
// ±0 / ±0
// ±Inf / ±Inf
// value of z is undefined but make sure it's valid
z.form = zero z.form = zero
return z z.neg = false
panic(ErrNaN{"division of zero by zero or infinity by infinity"})
} }
// y == ±0
z.form = inf if x.form == zero || y.form == inf {
// ±0 / y
// x / ±Inf
z.form = zero
return z return z
} }
// x, y != 0 // x / ±0
z.uquo(x, y) // ±Inf / y
z.form = inf
return z return z
} }
......
...@@ -1438,7 +1438,7 @@ func TestFloatQuoSmoke(t *testing.T) { ...@@ -1438,7 +1438,7 @@ func TestFloatQuoSmoke(t *testing.T) {
// TestFloatArithmeticSpecialValues tests that Float operations produce the // TestFloatArithmeticSpecialValues tests that Float operations produce the
// correct results for combinations of zero (±0), finite (±1 and ±2.71828), // correct results for combinations of zero (±0), finite (±1 and ±2.71828),
// and non-finite (±Inf) operands. // and infinite (±Inf) operands.
func TestFloatArithmeticSpecialValues(t *testing.T) { func TestFloatArithmeticSpecialValues(t *testing.T) {
zero := 0.0 zero := 0.0
args := []float64{math.Inf(-1), -2.71828, -1, -zero, zero, 1, 2.71828, math.Inf(1)} args := []float64{math.Inf(-1), -2.71828, -1, -zero, zero, 1, 2.71828, math.Inf(1)}
...@@ -1456,38 +1456,53 @@ func TestFloatArithmeticSpecialValues(t *testing.T) { ...@@ -1456,38 +1456,53 @@ func TestFloatArithmeticSpecialValues(t *testing.T) {
t.Errorf("Float(%g) == %g (%s)", x, got, acc) t.Errorf("Float(%g) == %g (%s)", x, got, acc)
} }
for _, y := range args { for _, y := range args {
// At the moment an Inf operand always leads to a panic (known bug).
// TODO(gri) remove this once the bug is fixed.
if math.IsInf(x, 0) || math.IsInf(y, 0) {
continue
}
yy.SetFloat64(y) yy.SetFloat64(y)
var op string var (
var z float64 op string
z float64
f func(z, x, y *Float) *Float
)
switch i { switch i {
case 0: case 0:
op = "+" op = "+"
z = x + y z = x + y
got.Add(xx, yy) f = (*Float).Add
case 1: case 1:
op = "-" op = "-"
z = x - y z = x - y
got.Sub(xx, yy) f = (*Float).Sub
case 2: case 2:
op = "*" op = "*"
z = x * y z = x * y
got.Mul(xx, yy) f = (*Float).Mul
case 3: case 3:
if x == 0 && y == 0 {
// TODO(gri) check for ErrNaN
continue // 0/0 panics with ErrNaN
}
op = "/" op = "/"
z = x / y z = x / y
got.Quo(xx, yy) f = (*Float).Quo
default: default:
panic("unreachable") panic("unreachable")
} }
var errnan bool // set if execution of f panicked with ErrNaN
// protect execution of f
func() {
defer func() {
if p := recover(); p != nil {
_ = p.(ErrNaN) // re-panic if not ErrNaN
errnan = true
}
}()
f(got, xx, yy)
}()
if math.IsNaN(z) {
if !errnan {
t.Errorf("%5g %s %5g = %5s; want ErrNaN panic", x, op, y, got)
}
continue
}
if errnan {
t.Errorf("%5g %s %5g panicked with ErrNan; want %5s", x, op, y, want)
continue
}
want.SetFloat64(z) want.SetFloat64(z)
if !alike(got, want) { if !alike(got, want) {
t.Errorf("%5g %s %5g = %5s; want %5s", x, op, y, got, want) t.Errorf("%5g %s %5g = %5s; want %5s", x, op, y, got, want)
...@@ -1614,7 +1629,7 @@ func TestFloatArithmeticRounding(t *testing.T) { ...@@ -1614,7 +1629,7 @@ func TestFloatArithmeticRounding(t *testing.T) {
} }
// TestFloatCmpSpecialValues tests that Cmp produces the correct results for // TestFloatCmpSpecialValues tests that Cmp produces the correct results for
// combinations of zero (±0), finite (±1 and ±2.71828), and non-finite (±Inf) // combinations of zero (±0), finite (±1 and ±2.71828), and infinite (±Inf)
// operands. // operands.
func TestFloatCmpSpecialValues(t *testing.T) { func TestFloatCmpSpecialValues(t *testing.T) {
zero := 0.0 zero := 0.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment