math/big: clean up Float.SetPrec, use shorter internal representation

Change-Id: I9b78085adc12cbd240d0b8b48db6810ddb2aeadd
Reviewed-on: https://go-review.googlesource.com/5991
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/src/math/big/float.go b/src/math/big/float.go
index adb914d..3dedf1d 100644
--- a/src/math/big/float.go
+++ b/src/math/big/float.go
@@ -61,7 +61,7 @@
 	neg  bool
 	mant nat
 	exp  int32
-	prec uint // TODO(gri) make this a 32bit field
+	prec uint32
 }
 
 // TODO(gri) provide a couple of Example tests showing typical Float intialization
@@ -77,8 +77,9 @@
 // values have an empty mantissa and a 0 or infExp exponent, respectively.
 
 const (
-	MaxExp = math.MaxInt32 // largest supported exponent magnitude
-	infExp = -MaxExp - 1   // exponent for Inf values
+	MaxExp  = math.MaxInt32  // largest supported exponent magnitude
+	infExp  = -MaxExp - 1    // exponent for Inf values
+	MaxPrec = math.MaxUint32 // largest (theoretically) supported precision; likely memory-limited
 )
 
 // NewInf returns a new infinite Float value with value +Inf (sign >= 0),
@@ -150,11 +151,32 @@
 // SetPrec sets z's precision to prec and returns the (possibly) rounded
 // value of z. Rounding occurs according to z's rounding mode if the mantissa
 // cannot be represented in prec bits without loss of precision.
+// If prec == 0, the result is ±0 for finite z, and ±Inf for infinite z,
+// with the sign set according to z. If prec > MaxPrec, it is set to MaxPrec.
 func (z *Float) SetPrec(prec uint) *Float {
+	z.acc = Exact // optimistically assume no rounding is needed
+	// handle special case
+	if prec == 0 {
+		z.prec = 0
+		if len(z.mant) != 0 {
+			// truncate and compute accuracy
+			z.mant = z.mant[:0]
+			z.exp = 0
+			acc := Below
+			if z.neg {
+				acc = Above
+			}
+			z.acc = acc
+		}
+		return z
+	}
+	// general case
+	if prec > MaxPrec {
+		prec = MaxPrec
+	}
 	old := z.prec
-	z.acc = Exact
-	z.prec = prec
-	if prec < old {
+	z.prec = uint32(prec)
+	if z.prec < old {
 		z.round(0)
 	}
 	return z
@@ -259,7 +281,7 @@
 		return len(x.mant) == 0 && x.exp != infExp
 	}
 	// x.exp > 0
-	return x.prec <= uint(x.exp) || x.minPrec() <= uint(x.exp) // not enough bits for fractional mantissa
+	return x.prec <= uint32(x.exp) || x.minPrec() <= uint(x.exp) // not enough bits for fractional mantissa
 }
 
 // IsInf reports whether x is an infinity, according to sign.
@@ -320,7 +342,7 @@
 	z.acc = Exact
 
 	// handle zero and Inf
-	m := uint(len(z.mant)) // present mantissa length in words
+	m := uint32(len(z.mant)) // present mantissa length in words
 	if m == 0 {
 		if z.exp != infExp {
 			z.exp = 0
@@ -351,8 +373,8 @@
 	//   1     1        >  0.5, < 1.0
 
 	// bits > z.prec: mantissa too large => round
-	r := bits - z.prec - 1 // rounding bit position; r >= 0
-	rbit := z.mant.bit(r)  // rounding bit
+	r := uint(bits - z.prec - 1) // rounding bit position; r >= 0
+	rbit := z.mant.bit(r)        // rounding bit
 	if sbit == 0 {
 		sbit = z.mant.sticky(r)
 	}
@@ -567,9 +589,9 @@
 	// TODO(gri) can be more efficient if z.prec > 0
 	// but small compared to the size of x, or if there
 	// are many trailing 0's.
-	bits := uint(x.BitLen())
+	bits := uint32(x.BitLen())
 	if z.prec == 0 {
-		z.prec = umax(bits, 64)
+		z.prec = umax32(bits, 64)
 	}
 	z.acc = Exact
 	z.neg = x.neg
@@ -599,7 +621,7 @@
 	a.SetInt(x.Num())
 	b.SetInt(x.Denom())
 	if z.prec == 0 {
-		z.prec = umax(a.prec, b.prec)
+		z.prec = umax32(a.prec, b.prec)
 	}
 	return z.Quo(&a, &b)
 }
@@ -1126,7 +1148,7 @@
 	}
 
 	if z.prec == 0 {
-		z.prec = umax(x.prec, y.prec)
+		z.prec = umax32(x.prec, y.prec)
 	}
 
 	// TODO(gri) what about -0?
@@ -1167,7 +1189,7 @@
 	}
 
 	if z.prec == 0 {
-		z.prec = umax(x.prec, y.prec)
+		z.prec = umax32(x.prec, y.prec)
 	}
 
 	// TODO(gri) what about -0?
@@ -1207,7 +1229,7 @@
 	}
 
 	if z.prec == 0 {
-		z.prec = umax(x.prec, y.prec)
+		z.prec = umax32(x.prec, y.prec)
 	}
 
 	// TODO(gri) handle Inf
@@ -1236,7 +1258,7 @@
 	}
 
 	if z.prec == 0 {
-		z.prec = umax(x.prec, y.prec)
+		z.prec = umax32(x.prec, y.prec)
 	}
 
 	// TODO(gri) handle Inf
@@ -1337,7 +1359,7 @@
 	return 0
 }
 
-func umax(x, y uint) uint {
+func umax32(x, y uint32) uint32 {
 	if x > y {
 		return x
 	}
diff --git a/src/math/big/float_test.go b/src/math/big/float_test.go
index 6391bee..bbab767 100644
--- a/src/math/big/float_test.go
+++ b/src/math/big/float_test.go
@@ -104,6 +104,58 @@
 	return &x
 }
 
+func TestFloatSetPrec(t *testing.T) {
+	for _, test := range []struct {
+		x    string
+		prec uint
+		want string
+		acc  Accuracy
+	}{
+		// prec 0
+		{"0", 0, "0", Exact},
+		{"-0", 0, "-0", Exact},
+		{"-Inf", 0, "-Inf", Exact},
+		{"+Inf", 0, "+Inf", Exact},
+		{"123", 0, "0", Below},
+		{"-123", 0, "-0", Above},
+
+		// prec at upper limit
+		{"0", MaxPrec, "0", Exact},
+		{"0", MaxPrec + 1, "0", Exact},
+		{"-0", MaxPrec, "-0", Exact},
+		{"-0", MaxPrec + 1, "-0", Exact},
+		{"-Inf", MaxPrec, "-Inf", Exact},
+		{"+Inf", MaxPrec + 1, "+Inf", Exact},
+		{"-Inf", MaxPrec, "-Inf", Exact},
+		{"+Inf", MaxPrec + 1, "+Inf", Exact},
+
+		// just a few regular cases - general rounding is tested elsewhere
+		{"1.5", 1, "2", Above},
+		{"-1.5", 1, "-2", Below},
+		{"123", 1e6, "123", Exact},
+		{"-123", 1e6, "-123", Exact},
+	} {
+		x := makeFloat(test.x).SetPrec(test.prec)
+		prec := test.prec
+		if prec > MaxPrec {
+			prec = MaxPrec
+		}
+		if got := x.Prec(); got != prec {
+			t.Errorf("%s.SetPrec(%d).Prec() == %d; want %d", test.x, test.prec, got, prec)
+		}
+		if got, acc := x.String(), x.Acc(); got != test.want || acc != test.acc {
+			t.Errorf("%s.SetPrec(%d) = %s (%s); want %s (%s)", test.x, test.prec, got, acc, test.want, test.acc)
+		}
+		// look inside x and check correct value for x.exp
+		if len(x.mant) == 0 {
+			// ±0 or ±Inf
+			if x.exp != 0 && x.exp != infExp {
+				t.Errorf("%s.SetPrec(%d): incorrect exponent %d", test.x, test.prec, x.exp)
+			}
+		}
+	}
+}
+
 func TestFloatSign(t *testing.T) {
 	for _, test := range []struct {
 		x string
diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go
index 96ccd60..b1b028c 100644
--- a/src/math/big/floatconv.go
+++ b/src/math/big/floatconv.go
@@ -277,11 +277,11 @@
 
 	// adjust mantissa to use exactly x.prec bits
 	m := x.mant
-	switch w := uint(len(x.mant)) * _W; {
+	switch w := uint32(len(x.mant)) * _W; {
 	case w < x.prec:
-		m = nat(nil).shl(m, x.prec-w)
+		m = nat(nil).shl(m, uint(x.prec-w))
 	case w > x.prec:
-		m = nat(nil).shr(m, w-x.prec)
+		m = nat(nil).shr(m, uint(w-x.prec))
 	}
 
 	buf = append(buf, m.decimalString()...)