internal/number: change rounding again

Hopefully final attempt to work around golang/go#21714.

Prevent double rounding:
If we apply rounding down the road we get a
large number of decimals. Otherwise
we ask for the smallest representation.
Change test cases to not use numbers that
will round incorrectly.

Change-Id: If5c8fbcb27a867de28caf847e9ba828718482841
Reviewed-on: https://go-review.googlesource.com/61492
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/internal/number/decimal.go b/internal/number/decimal.go
index 62074e7..9b4035e 100644
--- a/internal/number/decimal.go
+++ b/internal/number/decimal.go
@@ -406,23 +406,35 @@
 	// By default we get the exact decimal representation.
 	verb := byte('g')
 	prec := -1
-	// Determine rounding, if possible. As the strconv API does not return the
-	// rounding accuracy (exact/rounded up|down), we can only round using
-	// ToNearestEven.
-	//   Something like this would work:
-	//   AppendDigits(dst []byte, x float64, base, size, prec int) (digits []byte, exp, accuracy int)
-	//
-	// TODO: At this point strconv's rounding is imprecise to the point that it
-	// is not useable for this purpose.
-	// See https://github.com/golang/go/issues/21714
-	// if r.Mode == ToNearestEven {
-	// 	if n := r.RoundSignificantDigits(); n >= 0 {
-	// 		prec = n
-	// 	} else if n = r.RoundFractionDigits(); n >= 0 {
-	// 		prec = n
-	// 		verb = 'f'
-	// 	}
-	// }
+	// As the strconv API does not return the rounding accuracy, we can only
+	// round using ToNearestEven.
+	if r.Mode == ToNearestEven {
+		if n := r.RoundSignificantDigits(); n >= 0 {
+			prec = n
+		} else if n = r.RoundFractionDigits(); n >= 0 {
+			prec = n
+			verb = 'f'
+		}
+	} else {
+		// TODO: At this point strconv's rounding is imprecise to the point that
+		// it is not useable for this purpose.
+		// See https://github.com/golang/go/issues/21714
+		// If rounding is requested, we ask for a large number of digits and
+		// round from there to simulate rounding only once.
+		// Ideally we would have strconv export an AppendDigits that would take
+		// a rounding mode and/or return an accuracy. Something like this would
+		// work:
+		// AppendDigits(dst []byte, x float64, base, size, prec int) (digits []byte, exp, accuracy int)
+		hasPrec := r.RoundSignificantDigits() >= 0
+		hasScale := r.RoundFractionDigits() >= 0
+		if hasPrec || hasScale {
+			// prec is the number of mantissa bits plus some extra for safety.
+			// We need at least the number of mantissa bits as decimals to
+			// accurately represent the floating point without rounding, as each
+			// bit requires one more decimal to represent: 0.5, 0.25, 0.125, ...
+			prec = 60
+		}
+	}
 
 	b := strconv.AppendFloat(d.Digits[:0], abs, verb, prec, size)
 	i := 0
diff --git a/internal/number/decimal_test.go b/internal/number/decimal_test.go
index 04aa8b2..97c7e25 100644
--- a/internal/number/decimal_test.go
+++ b/internal/number/decimal_test.go
@@ -256,12 +256,10 @@
 		rc  RoundingContext
 		out string
 	}{
-		// TODO: uncommented tests can be restored when convert does its own
-		// rounding.
-		// {-0.001, scale2, "-0.00"}, // not normalized
-		// {0.1234, prec3, "0.123"},
-		// {1234.0, prec3, "1230"},
-		// {1.2345e10, prec3, "12300000000"},
+		{-0.001, scale2, "-0.00"},
+		{0.1234, prec3, "0.123"},
+		{1234.0, prec3, "1230"},
+		{1.2345e10, prec3, "12300000000"},
 
 		{int8(-34), scale2, "-34"},
 		{int16(-234), scale2, "-234"},
@@ -273,18 +271,37 @@
 		{uint32(234), scale2, "234"},
 		{uint64(234), scale2, "234"},
 		{uint(234), scale2, "234"},
-		{-1e9, scale2, "-1000000000"},
-		{0.234, scale2away, "0.234"}, // rounding postponed as not ToNearestEven
+		{-1e9, scale2, "-1000000000.00"},
+		// The following two causes this result to have a lot of digits:
+		// 1) 0.234 cannot be accurately represented as a float64, and
+		// 2) as strconv does not support the rounding AwayFromZero, Convert
+		//    leaves the rounding to caller.
+		{0.234, scale2away,
+			"0.2340000000000000135447209004269097931683063507080078125"},
 
+		{0.0249, inc0_05, "0.00"},
+		{0.025, inc0_05, "0.00"},
+		{0.0251, inc0_05, "0.05"},
 		{0.03, inc0_05, "0.05"},
-		{0.025, inc0_05, "0"},
-		{0.075, inc0_05, "0.1"},
+		{0.049, inc0_05, "0.05"},
+		{0.05, inc0_05, "0.05"},
+		{0.051, inc0_05, "0.05"},
+		{0.0749, inc0_05, "0.05"},
+		{0.075, inc0_05, "0.10"},
+		{0.0751, inc0_05, "0.10"},
+		{324, inc50, "300"},
 		{325, inc50, "300"},
+		{326, inc50, "350"},
+		{349, inc50, "350"},
+		{350, inc50, "350"},
+		{351, inc50, "350"},
+		{374, inc50, "350"},
 		{375, inc50, "400"},
+		{376, inc50, "400"},
 
 		// Here the scale is 2, but the digits get shifted left. As we use
 		// AppendFloat to do the rounding an exta 0 gets added.
-		{0.123, roundShift, "0.123"},
+		{0.123, roundShift, "0.1230"},
 
 		{converter(3), scale2, "100"},
 
diff --git a/number/number_test.go b/number/number_test.go
index 96b1acb..3dcac36 100644
--- a/number/number_test.go
+++ b/number/number_test.go
@@ -64,8 +64,8 @@
 		want: "0.12",
 	}, {
 		desc: "max fraction overflow",
-		f:    Decimal(0.123, MaxFractionDigits(1e6)),
-		want: "0.123",
+		f:    Decimal(0.125, MaxFractionDigits(1e6)),
+		want: "0.125",
 	}, {
 		desc: "min integer overflow",
 		f:    Decimal(0, MinIntegerDigits(1e6)),
@@ -172,7 +172,7 @@
 		want: "123.45‰",
 	}, {
 		desc: "percent fraction",
-		f:    PerMille(0.12345, Scale(1)),
+		f:    PerMille(0.12344, Scale(1)),
 		want: "123.4‰",
 	}}
 	for _, tc := range testCases {