math/big: introduce Undef Accuracy, use for NaN operands/results

This change represents Accuracy as a bit pattern rather than
an ordered value; with a new value Undef which is both Below
and Above.

Change-Id: Ibb96294c1417fb3cf2c3cf2374c993b0a4e106b3
Reviewed-on: https://go-review.googlesource.com/6650
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/src/math/big/float.go b/src/math/big/float.go
index e133581..fa0cb2b 100644
--- a/src/math/big/float.go
+++ b/src/math/big/float.go
@@ -87,30 +87,31 @@
 )
 
 // Accuracy describes the rounding error produced by the most recent
-// operation that generated a Float value, relative to the exact value:
-//
-//  -1: below exact value
-//   0: exact value
-//  +1: above exact value
-//
+// operation that generated a Float value, relative to the exact value.
+// The accuracy may be Undef (either Below or Above) for operations on
+// and resulting in NaNs.
 type Accuracy int8
 
 // Constants describing the Accuracy of a Float.
 const (
-	Below Accuracy = -1
 	Exact Accuracy = 0
-	Above Accuracy = +1
+	Below Accuracy = 1 << 0
+	Above Accuracy = 1 << 1
+	Undef Accuracy = Below | Above
 )
 
 func (a Accuracy) String() string {
-	switch {
-	case a < 0:
-		return "below"
-	default:
+	switch a {
+	case Exact:
 		return "exact"
-	case a > 0:
+	case Below:
+		return "below"
+	case Above:
 		return "above"
+	case Undef:
+		return "undef"
 	}
+	panic(fmt.Sprintf("unknown accuracy %d", a))
 }
 
 // RoundingMode determines how a Float value is rounded to the
@@ -391,6 +392,9 @@
 	// handle zero, Inf, and NaN
 	m := uint32(len(z.mant)) // present mantissa length in words
 	if m == 0 {
+		if z.exp == nanExp {
+			z.acc = Undef
+		}
 		return
 	}
 	// m > 0 implies z.prec > 0 (checked by validate)
@@ -507,8 +511,8 @@
 	z.mant[0] &^= lsb - 1
 
 	// update accuracy
-	if z.neg {
-		z.acc = -z.acc
+	if z.acc != Exact && z.neg {
+		z.acc ^= Below | Above
 	}
 
 	if debugFloat {
@@ -751,9 +755,8 @@
 // Uint64 returns the unsigned integer resulting from truncating x
 // towards zero. If 0 <= x <= math.MaxUint64, the result is Exact
 // if x is an integer and Below otherwise.
-// The result is (0, Above) for x < 0, and (math.MaxUint64, Below)
-// for x > math.MaxUint64.
-// BUG(gri) not implemented for NaN
+// The result is (0, Above) for x < 0, (math.MaxUint64, Below)
+// for x > math.MaxUint64, and (0, Undef) for NaNs.
 func (x *Float) Uint64() (uint64, Accuracy) {
 	if debugFloat {
 		validate(x)
@@ -770,7 +773,7 @@
 			}
 			return math.MaxUint64, Below // +Inf
 		case nanExp:
-			panic("unimplemented")
+			return 0, Undef // NaN
 		}
 		panic("unreachable")
 	}
@@ -799,9 +802,8 @@
 // Int64 returns the integer resulting from truncating x towards zero.
 // If math.MinInt64 <= x <= math.MaxInt64, the result is Exact if x is
 // an integer, and Above (x < 0) or Below (x > 0) otherwise.
-// The result is (math.MinInt64, Above) for x < math.MinInt64, and
-// (math.MaxInt64, Below) for x > math.MaxInt64.
-// BUG(gri) incorrect result for NaN
+// The result is (math.MinInt64, Above) for x < math.MinInt64,
+// (math.MaxInt64, Below) for x > math.MaxInt64, and (0, Undef) for NaNs.
 func (x *Float) Int64() (int64, Accuracy) {
 	if debugFloat {
 		validate(x)
@@ -818,8 +820,7 @@
 			}
 			return math.MaxInt64, Below // +Inf
 		case nanExp:
-			// TODO(gri) fix this
-			return 0, Exact
+			return 0, Undef // NaN
 		}
 		panic("unreachable")
 	}
@@ -860,7 +861,7 @@
 
 // Float64 returns the closest float64 value of x
 // by rounding to nearest with 53 bits precision.
-// BUG(gri) accuracy incorrect for NaN, doesn't handle exponent overflow
+// BUG(gri) doesn't handle exponent overflow
 func (x *Float) Float64() (float64, Accuracy) {
 	if debugFloat {
 		validate(x)
@@ -882,7 +883,7 @@
 			}
 			return math.Inf(sign), Exact
 		case nanExp:
-			return math.NaN(), Exact
+			return math.NaN(), Undef
 		}
 		panic("unreachable")
 	}
@@ -906,7 +907,6 @@
 // for x > 0, and Above for x < 0.
 // If a non-nil *Int argument z is provided, Int stores
 // the result in z instead of allocating a new Int.
-// BUG(gri) accuracy incorrect for for NaN
 func (x *Float) Int(z *Int) (*Int, Accuracy) {
 	if debugFloat {
 		validate(x)
@@ -930,8 +930,7 @@
 			}
 			return nil, Below
 		case nanExp:
-			// TODO(gri) fix accuracy for NaN
-			return nil, Exact
+			return nil, Undef
 		}
 		panic("unreachable")
 	}
@@ -976,7 +975,6 @@
 // The result is Exact is x is not an Inf or NaN.
 // If a non-nil *Rat argument z is provided, Rat stores
 // the result in z instead of allocating a new Rat.
-// BUG(gri) incorrect accuracy for Inf, NaN.
 func (x *Float) Rat(z *Rat) (*Rat, Accuracy) {
 	if debugFloat {
 		validate(x)
@@ -1000,8 +998,7 @@
 			}
 			return nil, Below
 		case nanExp:
-			// TODO(gri) fix accuracy
-			return nil, Exact
+			return nil, Undef
 		}
 		panic("unreachable")
 	}
@@ -1499,6 +1496,7 @@
 // Infinities with matching sign are equal.
 // NaN values are never equal.
 // BUG(gri) comparing NaN's is not implemented
+// (should we use Accuracy here for results?)
 func (x *Float) Cmp(y *Float) int {
 	if debugFloat {
 		validate(x)
diff --git a/src/math/big/float_test.go b/src/math/big/float_test.go
index aaf4970..aa1cb58 100644
--- a/src/math/big/float_test.go
+++ b/src/math/big/float_test.go
@@ -70,7 +70,7 @@
 		{1, 2, 0, 0, '*', (*Float).Mul},
 		{2, 0, 1, 0, '*', (*Float).Mul},
 
-		{0, 0, 0, 0, '/', (*Float).Quo}, // = +Inf
+		{0, 0, 0, 0, '/', (*Float).Quo}, // = Nan
 		{0, 2, 1, 2, '/', (*Float).Quo},
 		{1, 2, 0, 0, '/', (*Float).Quo}, // = +Inf
 		{2, 0, 1, 0, '/', (*Float).Quo},
@@ -78,7 +78,7 @@
 		z := make(test.z)
 		test.op(z, make(test.x), make(test.y))
 		got := 0
-		if !z.IsInf(0) {
+		if !z.IsInf(0) && !z.IsNaN() {
 			got = int(z.int64())
 		}
 		if got != test.want {
@@ -625,8 +625,8 @@
 	// test NaN
 	var f Float
 	f.SetFloat64(math.NaN())
-	if got, acc := f.Float64(); !math.IsNaN(got) || acc != Exact {
-		t.Errorf("got %g (%s, %s); want %g (exact)", got, f.Format('p', 0), acc, math.NaN())
+	if got, acc := f.Float64(); !math.IsNaN(got) || acc != Undef {
+		t.Errorf("got %g (%s, %s); want %g (undef)", got, f.Format('p', 0), acc, math.NaN())
 	}
 
 	// test basic rounding behavior (exhaustive rounding testing is done elsewhere)
@@ -765,7 +765,7 @@
 		{"18446744073709551616", math.MaxUint64, Below},
 		{"1e10000", math.MaxUint64, Below},
 		{"+Inf", math.MaxUint64, Below},
-		// {"NaN", 0, Exact}, TODO(gri) enable once implemented
+		{"NaN", 0, Undef},
 	} {
 		x := makeFloat(test.x)
 		out, acc := x.Uint64()
@@ -804,7 +804,7 @@
 		{"9223372036854775808", math.MaxInt64, Below},
 		{"1e10000", math.MaxInt64, Below},
 		{"+Inf", math.MaxInt64, Below},
-		// {"NaN", 0, Exact}, TODO(gri) enable once implemented
+		{"NaN", 0, Undef},
 	} {
 		x := makeFloat(test.x)
 		out, acc := x.Int64()
@@ -826,6 +826,7 @@
 		{"Inf", "nil", Below},
 		{"+Inf", "nil", Below},
 		{"-Inf", "nil", Above},
+		{"NaN", "nil", Undef},
 		{"1", "1", Exact},
 		{"-1", "-1", Exact},
 		{"1.23", "1", Below},
@@ -862,21 +863,23 @@
 func TestFloatRat(t *testing.T) {
 	for _, test := range []struct {
 		x, want string
+		acc     Accuracy
 	}{
-		{"0", "0/1"},
-		{"+0", "0/1"},
-		{"-0", "0/1"},
-		{"Inf", "nil"},
-		{"+Inf", "nil"},
-		{"-Inf", "nil"},
-		{"1", "1/1"},
-		{"-1", "-1/1"},
-		{"1.25", "5/4"},
-		{"-1.25", "-5/4"},
-		{"1e10", "10000000000/1"},
-		{"1p10", "1024/1"},
-		{"-1p-10", "-1/1024"},
-		{"3.14159265", "7244019449799623199/2305843009213693952"},
+		{"0", "0/1", Exact},
+		{"+0", "0/1", Exact},
+		{"-0", "0/1", Exact},
+		{"Inf", "nil", Below},
+		{"+Inf", "nil", Below},
+		{"-Inf", "nil", Above},
+		{"NaN", "nil", Undef},
+		{"1", "1/1", Exact},
+		{"-1", "-1/1", Exact},
+		{"1.25", "5/4", Exact},
+		{"-1.25", "-5/4", Exact},
+		{"1e10", "10000000000/1", Exact},
+		{"1p10", "1024/1", Exact},
+		{"-1p-10", "-1/1024", Exact},
+		{"3.14159265", "7244019449799623199/2305843009213693952", Exact},
 	} {
 		x := makeFloat(test.x).SetPrec(64)
 		res, acc := x.Rat(nil)
@@ -888,8 +891,10 @@
 			t.Errorf("%s: got %s; want %s", test.x, got, test.want)
 			continue
 		}
-		// TODO(gri) check accuracy
-		_ = acc
+		if acc != test.acc {
+			t.Errorf("%s: got %s; want %s", test.x, acc, test.acc)
+			continue
+		}
 
 		// inverse conversion
 		if res != nil {