internal/number: fix rounding issues

- rounding with large scales
- correct interpretation for engineering
- default rounding is exact representation
- simplify increments

Change-Id: I6a4959974069f5e1706e396a836bd6989b93d60d
Reviewed-on: https://go-review.googlesource.com/60350
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/internal/number/decimal.go b/internal/number/decimal.go
index 0491c97..8d80a92 100644
--- a/internal/number/decimal.go
+++ b/internal/number/decimal.go
@@ -375,6 +375,24 @@
 		d.NaN = true
 		return
 	}
+	// Simple case: decimal notation
+	if r.Increment > 0 {
+		scale := int(r.MaxFractionDigits)
+		mult := 1.0
+		if scale > len(scales) {
+			mult = math.Pow(10, float64(scale))
+		} else {
+			mult = scales[scale]
+		}
+		// We multiply x instead of dividing inc as it gives less rounding
+		// issues.
+		x *= mult
+		x /= float64(r.Increment)
+		x = r.Mode.roundFloat(x)
+		x *= float64(r.Increment)
+		x /= mult
+	}
+
 	abs := x
 	if x < 0 {
 		d.Neg = true
@@ -384,64 +402,64 @@
 		d.Inf = true
 		return
 	}
-	// Simple case: decimal notation
-	scale := r.scale()
-	prec := r.precision()
-	if scale > 0 || r.Increment > 0 || prec == 0 {
-		if scale > len(scales) {
-			x *= math.Pow(10, float64(scale))
-		} else {
-			x *= scales[scale]
-		}
-		if r.Increment > 0 {
-			inc := float64(r.Increment)
-			x /= float64(inc)
-			x = r.Mode.roundFloat(x)
-			x *= inc
-		} else {
-			x = r.Mode.roundFloat(x)
-		}
-		d.fillIntDigits(uint64(math.Abs(x)))
-		d.Exp = int32(len(d.Digits) - scale)
-		return
-	}
 
-	// Nasty case (for non-decimal notation).
-	// Asides from being inefficient, this result is also wrong as it will
-	// apply ToNearestEven rounding regardless of the user setting.
-	// TODO: expose functionality in strconv so we can avoid this hack.
+	// 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: This only supports the nearest even rounding mode.
-
-	if prec > 0 {
-		prec--
+	if r.Mode == ToNearestEven {
+		// We can't round if limitations are placed on both the fraction and
+		// significant digits.
+		if r.MaxFractionDigits == 0 && r.MaxSignificantDigits > 0 {
+			prec = int(r.MaxSignificantDigits)
+		} else if r.isScientific() {
+			if r.MaxIntegerDigits == 1 && (r.MaxSignificantDigits == 0 ||
+				int(r.MaxFractionDigits+1) == int(r.MaxSignificantDigits)) {
+				verb = 'e'
+				prec = int(r.MaxFractionDigits)
+				prec += int(r.DigitShift)
+			}
+		} else if r.MaxFractionDigits > 0 && r.MaxSignificantDigits == 0 {
+			verb = 'f'
+			prec = int(r.MaxFractionDigits)
+		}
 	}
-	b := strconv.AppendFloat(d.Digits, abs, 'e', prec, size)
+
+	b := strconv.AppendFloat(d.Digits[:0], abs, verb, prec, size)
 	i := 0
 	k := 0
-	// No need to check i < len(b) as we always have an 'e'.
-	for {
+	beforeDot := 1
+	for i < len(b) {
 		if c := b[i]; '0' <= c && c <= '9' {
 			b[k] = c - '0'
 			k++
-		} else if c != '.' {
+			d.Exp += int32(beforeDot)
+		} else if c == '.' {
+			beforeDot = 0
+			d.Exp = int32(k)
+		} else {
 			break
 		}
 		i++
 	}
 	d.Digits = b[:k]
-	i += len("e")
-	pSign := i
-	exp := 0
-	for i++; i < len(b); i++ {
-		exp *= 10
-		exp += int(b[i] - '0')
+	if i != len(b) {
+		i += len("e")
+		pSign := i
+		exp := 0
+		for i++; i < len(b); i++ {
+			exp *= 10
+			exp += int(b[i] - '0')
+		}
+		if b[pSign] == '-' {
+			exp = -exp
+		}
+		d.Exp = int32(exp) + 1
 	}
-	if b[pSign] == '-' {
-		exp = -exp
-	}
-	d.Exp = int32(exp) + 1
 }
 
 func (d *Decimal) fillIntDigits(x uint64) {
diff --git a/internal/number/decimal_test.go b/internal/number/decimal_test.go
index b017b39..ecb695a 100644
--- a/internal/number/decimal_test.go
+++ b/internal/number/decimal_test.go
@@ -265,16 +265,15 @@
 		{uint32(234), scale2, "234"},
 		{uint64(234), scale2, "234"},
 		{uint(234), scale2, "234"},
-		{-0.001, scale2, "-0"},
+		{-0.001, scale2, "-0.00"}, // not normalized
 		{-1e9, scale2, "-1000000000.00"},
-		{0.234, scale2, "0.23"},
-		{0.234, scale2away, "0.24"},
+		{0.234, scale2away, "0.234"}, // rounding postponed as not ToNearestEven
 		{0.1234, prec3, "0.123"},
 		{1234.0, prec3, "1230"},
 		{1.2345e10, prec3, "12300000000"},
 
 		{0.03, inc0_05, "0.05"},
-		{0.025, inc0_05, "0"},
+		{0.025, inc0_05, "0.00"}, // not normalized
 		{0.075, inc0_05, "0.10"},
 		{325, inc50, "300"},
 		{375, inc50, "400"},
diff --git a/internal/number/format.go b/internal/number/format.go
index 44116fa..4104789 100755
--- a/internal/number/format.go
+++ b/internal/number/format.go
@@ -56,12 +56,16 @@
 // given language.
 func (f *Formatter) InitScientific(t language.Tag) {
 	f.init(t, tagToScientific)
+	f.Pattern.MinFractionDigits = 0
+	f.Pattern.MaxFractionDigits = -1
 }
 
 // InitEngineering initializes a Formatter using the default Pattern for the
 // given language.
 func (f *Formatter) InitEngineering(t language.Tag) {
 	f.init(t, tagToScientific)
+	f.Pattern.MinFractionDigits = 0
+	f.Pattern.MaxFractionDigits = -1
 	f.Pattern.MaxIntegerDigits = 3
 	f.Pattern.MinIntegerDigits = 1
 }
@@ -303,13 +307,6 @@
 	if numInt == 0 {
 		numInt = 1
 	}
-	maxSig := int(r.MaxFractionDigits) + numInt
-	minSig := int(r.MinFractionDigits) + numInt
-
-	if maxSig > 0 {
-		// TODO: really round to zero?
-		n.round(ToZero, maxSig)
-	}
 
 	// If a maximum number of integers is specified, the minimum must be 1
 	// and the exponent is grouped by this number (e.g. for engineering)
@@ -326,10 +323,14 @@
 		numInt += d
 	}
 
+	if maxSig := int(r.MaxFractionDigits); maxSig >= 0 {
+		n.round(r.Mode, maxSig+numInt)
+	}
+
 	n.Comma = uint8(numInt)
 	n.End = int32(len(n.Digits))
-	if n.End < int32(minSig) {
-		n.End = int32(minSig)
+	if minSig := int32(r.MinFractionDigits) + int32(numInt); n.End < minSig {
+		n.End = minSig
 	}
 	return n
 }
diff --git a/internal/number/format_test.go b/internal/number/format_test.go
index fc7c36d..1273818 100755
--- a/internal/number/format_test.go
+++ b/internal/number/format_test.go
@@ -289,22 +289,25 @@
 		pattern: "##0E00",
 		test: pairs{
 			"100":     "100\u202f×\u202f10⁰⁰",
-			"12345":   "10\u202f×\u202f10⁰³",
-			"123.456": "100\u202f×\u202f10⁰⁰",
+			"12345":   "12\u202f×\u202f10⁰³",
+			"123.456": "123\u202f×\u202f10⁰⁰",
 		},
 	}, {
 		pattern: "##0.###E00",
 		test: pairs{
-			"100":     "100\u202f×\u202f10⁰⁰",
-			"12345":   "12.34\u202f×\u202f10⁰³",
-			"123.456": "123.4\u202f×\u202f10⁰⁰",
+			"100":      "100\u202f×\u202f10⁰⁰",
+			"12345":    "12.345\u202f×\u202f10⁰³",
+			"123456":   "123.456\u202f×\u202f10⁰³",
+			"123.456":  "123.456\u202f×\u202f10⁰⁰",
+			"123.4567": "123.457\u202f×\u202f10⁰⁰",
 		},
 	}, {
 		pattern: "##0.000E00",
 		test: pairs{
-			"100":     "100.0\u202f×\u202f10⁰⁰",
-			"12345":   "12.34\u202f×\u202f10⁰³",
-			"123.456": "123.4\u202f×\u202f10⁰⁰",
+			"100":     "100.000\u202f×\u202f10⁰⁰",
+			"12345":   "12.345\u202f×\u202f10⁰³",
+			"123.456": "123.456\u202f×\u202f10⁰⁰",
+			"12.3456": "12.346\u202f×\u202f10⁰⁰",
 		},
 	}, {
 		pattern: "@@E0",
@@ -498,7 +501,8 @@
 	}{
 		{f.InitDecimal, "123456.78", "123,456.78"},
 		{f.InitScientific, "123456.78", "1.23\u202f×\u202f10⁵"},
-		{f.InitEngineering, "123456.78", "123\u202f×\u202f10³"},
+		{f.InitEngineering, "123456.78", "123.46\u202f×\u202f10³"},
+		{f.InitEngineering, "1234", "1.23\u202f×\u202f10³"},
 
 		{f.InitPercent, "0.1234", "12.34%"},
 		{f.InitPerMille, "0.1234", "123.40‰"},
diff --git a/internal/number/pattern.go b/internal/number/pattern.go
index 89c2a1c..4c3e815 100644
--- a/internal/number/pattern.go
+++ b/internal/number/pattern.go
@@ -55,11 +55,11 @@
 // It contains all information needed to determine the "visible digits" as
 // required by the pluralization rules.
 type RoundingContext struct {
-	Increment uint32 // if > 0, round to Increment * 10^-scale()
+	Increment uint32
 	// TODO: unify these two fields so that there is a more unambiguous meaning
 	// of how precision is handled.
-	MaxSignificantDigits int16 // -1 is infinite precision
-	MaxFractionDigits    uint16
+	MaxSignificantDigits int16 // -1 is unlimited
+	MaxFractionDigits    int16 // -1 is unlimited
 
 	Mode RoundingMode
 
@@ -88,7 +88,7 @@
 // SetScale fixes the RoundingContext to a fixed number of fraction digits.
 func (r *RoundingContext) SetScale(scale int) {
 	r.MinFractionDigits = uint8(scale)
-	r.MaxFractionDigits = uint16(scale)
+	r.MaxFractionDigits = int16(scale)
 }
 
 func (r *RoundingContext) SetPrecision(prec int) {
@@ -414,7 +414,7 @@
 func (p *parser) normalizeSigDigitsWithExponent() state {
 	p.MinIntegerDigits, p.MaxIntegerDigits = 1, 1
 	p.MinFractionDigits = p.MinSignificantDigits - 1
-	p.MaxFractionDigits = uint16(p.MaxSignificantDigits) - 1
+	p.MaxFractionDigits = p.MaxSignificantDigits - 1
 	p.MinSignificantDigits, p.MaxSignificantDigits = 0, 0
 	return p.exponent
 }
diff --git a/internal/number/pattern_test.go b/internal/number/pattern_test.go
index f551456..af08379 100644
--- a/internal/number/pattern_test.go
+++ b/internal/number/pattern_test.go
@@ -154,6 +154,17 @@
 		},
 	},
 }, {
+	"##0.###E00",
+	&Pattern{
+		FormatWidth: 10,
+		RoundingContext: RoundingContext{
+			MinIntegerDigits:  1,
+			MaxIntegerDigits:  3,
+			MaxFractionDigits: 3,
+			MinExponentDigits: 2,
+		},
+	},
+}, {
 	"##00.0#E0",
 	&Pattern{
 		FormatWidth: 9,
diff --git a/message/fmt_test.go b/message/fmt_test.go
index b22420d..2110bb5 100755
--- a/message/fmt_test.go
+++ b/message/fmt_test.go
@@ -475,10 +475,8 @@
 	{"%.4b", float32(1.0), "8388608p-23"},
 	{"%.4b", -1.0, "-4503599627370496p-52"},
 	// Test correct f.intbuf boundary checks.
-	// TODO: the following cases won't work because of rounding errors. We can
-	// fix this if we expose the internals of strconv.
-	// {"%.68f", 1.0, zeroFill("1.", 68, "")},   // TODO(bug): rounding error
-	// {"%.68f", -1.0, zeroFill("-1.", 68, "")}, // TODO(bug): rounding error
+	{"%.68f", 1.0, zeroFill("1.", 68, "")},
+	{"%.68f", -1.0, zeroFill("-1.", 68, "")},
 	// float infinites and NaNs
 	{"%f", posInf, "∞"},
 	{"%.1f", negInf, "-∞"},
diff --git a/message/print.go b/message/print.go
index 7e4642b..07feaa2 100644
--- a/message/print.go
+++ b/message/print.go
@@ -284,7 +284,7 @@
 	f.MinIntegerDigits = 1
 	f.MaxIntegerDigits = 0
 	f.MinFractionDigits = uint8(minFrac)
-	f.MaxFractionDigits = uint16(maxFrac)
+	f.MaxFractionDigits = int16(maxFrac)
 	p.setFlags(f)
 	f.PadRune = 0
 	if p.fmt.widPresent {
@@ -313,7 +313,7 @@
 	} else {
 		f.SetPrecision(maxFrac + 1)
 		f.MinFractionDigits = uint8(minFrac)
-		f.MaxFractionDigits = uint16(maxFrac)
+		f.MaxFractionDigits = int16(maxFrac)
 	}
 	f.MinExponentDigits = 2
 	p.setFlags(f)