internal/number: introduce Digits

Digits are needed by pluralization.

add setScale, update, setPrecision

Change-Id: If7ac134e8371f232421e69c1abe58a52296b5948
Reviewed-on: https://go-review.googlesource.com/58550
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 fd62310..7c28dc1 100644
--- a/internal/number/decimal.go
+++ b/internal/number/decimal.go
@@ -27,11 +27,37 @@
 
 const maxIntDigits = 20
 
-// A Decimal represents floating point number represented in digits of the base
-// in which a number is to be displayed. Digits represents a number [0, 1.0),
-// and the absolute value represented by Decimal is Digits * 10^Exp.
-// Leading and trailing zeros may be omitted and Exp may point outside a valid
-// position in Digits.
+// A Decimal represents a floating point number in decimal format.
+// Digits represents a number [0, 1.0), and the absolute value represented by
+// Decimal is Digits * 10^Exp. Leading and trailing zeros may be omitted and Exp
+// may point outside a valid position in Digits.
+//
+// Examples:
+//      Number     Decimal
+//      12345      Digits: [1, 2, 3, 4, 5], Exp: 5
+//      12.345     Digits: [1, 2, 3, 4, 5], Exp: 2
+//      12000      Digits: [1, 2],          Exp: 5
+//      12000.00   Digits: [1, 2],          Exp: 5
+//      0.00123    Digits: [1, 2, 3],       Exp: -2
+//      0          Digits: [],              Exp: 0
+type Decimal struct {
+	digits
+
+	buf [maxIntDigits]byte
+}
+
+type digits struct {
+	Digits []byte // mantissa digits, big-endian
+	Exp    int32  // exponent
+	Neg    bool
+	Inf    bool // Takes precedence over Digits and Exp.
+	NaN    bool // Takes precedence over Inf.
+}
+
+// Digits represents a floating point number represented in digits of the
+// base in which a number is to be displayed. It is similar to Decimal, but
+// keeps track of trailing fraction zeros and the comma placement for
+// engineering notation.
 //
 // Examples:
 //      Number     Decimal
@@ -47,20 +73,23 @@
 //      1.23e4     Digits: [1, 2, 3],       Exp: 5, End: 3, Comma: 1
 //    engineering
 //      12.3e3     Digits: [1, 2, 3],       Exp: 5, End: 3, Comma: 2
-type Decimal struct {
-	Digits []byte // mantissa digits, big-endian
-	Exp    int32  // exponent
+type Digits struct {
+	digits
 	// End indicates the end position of the number.
 	End int32 // For decimals Exp <= End. For scientific len(Digits) <= End.
 	// Comma is used for the comma position for scientific (always 0 or 1) and
 	// engineering notation (always 0, 1, 2, or 3).
 	Comma uint8
+	// IsScientific indicates whether this number is to be rendered as a
+	// scientific number.
+	IsScientific bool
+}
 
-	Neg bool
-	Inf bool // Takes precedence over Digits and Exp.
-	NaN bool // Takes precedence over Inf.
-
-	buf [maxIntDigits]byte
+func (d *Digits) NumFracDigits() int {
+	if d.Exp >= d.End {
+		return 0
+	}
+	return int(d.End - d.Exp)
 }
 
 // normalize returns a new Decimal with leading and trailing zeros removed.
@@ -142,7 +171,7 @@
 	return buf
 }
 
-func (d *Decimal) round(mode RoundingMode, n int) {
+func (d *digits) round(mode RoundingMode, n int) {
 	if n >= len(d.Digits) {
 		return
 	}
@@ -218,7 +247,7 @@
 	return i
 }
 
-func (x *Decimal) roundUp(n int) {
+func (x *digits) roundUp(n int) {
 	if n < 0 || n >= len(x.Digits) {
 		return // nothing to do
 	}
@@ -239,7 +268,7 @@
 	// x already trimmed
 }
 
-func (x *Decimal) roundDown(n int) {
+func (x *digits) roundDown(n int) {
 	if n < 0 || n >= len(x.Digits) {
 		return // nothing to do
 	}
@@ -249,7 +278,7 @@
 
 // trim cuts off any trailing zeros from x's mantissa;
 // they are meaningless for the value of x.
-func trim(x *Decimal) {
+func trim(x *digits) {
 	i := len(x.Digits)
 	for i > 0 && x.Digits[i-1] == 0 {
 		i--
diff --git a/internal/number/decimal_test.go b/internal/number/decimal_test.go
index e21b8a3..11d7644 100644
--- a/internal/number/decimal_test.go
+++ b/internal/number/decimal_test.go
@@ -86,11 +86,11 @@
 		want string
 	}{
 		{want: "0"},
-		{Decimal{Digits: nil, Exp: 1000}, "0"}, // exponent of 1000 is ignored
-		{Decimal{Digits: byteNum("12345"), Exp: 0}, "0.12345"},
-		{Decimal{Digits: byteNum("12345"), Exp: -3}, "0.00012345"},
-		{Decimal{Digits: byteNum("12345"), Exp: +3}, "123.45"},
-		{Decimal{Digits: byteNum("12345"), Exp: +10}, "1234500000"},
+		{Decimal{digits: digits{Digits: nil, Exp: 1000}}, "0"}, // exponent of 1000 is ignored
+		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: 0}}, "0.12345"},
+		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: -3}}, "0.00012345"},
+		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: +3}}, "123.45"},
+		{Decimal{digits: digits{Digits: byteNum("12345"), Exp: +10}}, "1234500000"},
 	} {
 		if got := test.x.String(); got != test.want {
 			t.Errorf("%v == %q; want %q", test.x, got, test.want)
diff --git a/internal/number/format.go b/internal/number/format.go
index dd703a7..9c764dc 100755
--- a/internal/number/format.go
+++ b/internal/number/format.go
@@ -81,17 +81,29 @@
 
 func (f *Formatter) Append(dst []byte, x interface{}) []byte {
 	var d Decimal
-	d.Convert(&f.RoundingContext, x)
-	return f.Format(dst, &d)
+	r := &f.RoundingContext
+	d.Convert(r, x)
+	return f.Render(dst, FormatDigits(&d, r))
+}
+
+func FormatDigits(d *Decimal, r *RoundingContext) Digits {
+	if r.isScientific() {
+		return scientificVisibleDigits(r, d)
+	}
+	return decimalVisibleDigits(r, d)
 }
 
 func (f *Formatter) Format(dst []byte, d *Decimal) []byte {
+	return f.Render(dst, FormatDigits(d, &f.RoundingContext))
+}
+
+func (f *Formatter) Render(dst []byte, d Digits) []byte {
 	var result []byte
 	var postPrefix, preSuffix int
-	if f.isScientific() {
-		result, postPrefix, preSuffix = appendScientific(dst, f, d)
+	if d.IsScientific {
+		result, postPrefix, preSuffix = appendScientific(dst, f, &d)
 	} else {
-		result, postPrefix, preSuffix = appendDecimal(dst, f, d)
+		result, postPrefix, preSuffix = appendDecimal(dst, f, &d)
 	}
 	if f.PadRune == 0 {
 		return result
@@ -130,12 +142,12 @@
 	return result
 }
 
-// TODO: just return visible digits.
-func decimalVisibleDigits(r *RoundingContext, d *Decimal) Decimal {
+func decimalVisibleDigits(r *RoundingContext, d *Decimal) Digits {
 	if d.NaN || d.Inf {
-		return *d
+		return Digits{digits: digits{Neg: d.Neg, NaN: d.NaN, Inf: d.Inf}}
 	}
-	n := d.normalize()
+	n := Digits{digits: d.normalize().digits}
+
 	if maxSig := int(r.MaxSignificantDigits); maxSig > 0 {
 		// TODO: really round to zero?
 		n.round(ToZero, maxSig)
@@ -199,11 +211,10 @@
 
 // appendDecimal appends a formatted number to dst. It returns two possible
 // insertion points for padding.
-func appendDecimal(dst []byte, f *Formatter, d *Decimal) (b []byte, postPre, preSuf int) {
-	if dst, ok := f.renderSpecial(dst, d); ok {
+func appendDecimal(dst []byte, f *Formatter, n *Digits) (b []byte, postPre, preSuf int) {
+	if dst, ok := f.renderSpecial(dst, n); ok {
 		return dst, 0, len(dst)
 	}
-	n := decimalVisibleDigits(&f.RoundingContext, d)
 	digits := n.Digits
 	exp := n.Exp
 
@@ -223,7 +234,7 @@
 		fracDigits = digits
 	}
 
-	neg := d.Neg
+	neg := n.Neg
 	affix, suffix := f.getAffixes(neg)
 	dst = appendAffix(dst, f, affix, neg)
 	savedLen := len(dst)
@@ -256,7 +267,7 @@
 	if numFrac > 0 || f.Flags&AlwaysDecimalSeparator != 0 {
 		dst = append(dst, f.Symbol(SymDecimal)...)
 	}
-	// Add leading zeros
+	// Add trailing zeros
 	i = 0
 	for n := -int(n.Exp); i < n; i++ {
 		dst = f.AppendDigit(dst, 0)
@@ -271,11 +282,11 @@
 	return appendAffix(dst, f, suffix, neg), savedLen, len(dst)
 }
 
-func scientificVisibleDigits(r *RoundingContext, d *Decimal) Decimal {
+func scientificVisibleDigits(r *RoundingContext, d *Decimal) Digits {
 	if d.NaN || d.Inf {
-		return *d
+		return Digits{digits: digits{Neg: d.Neg, NaN: d.NaN, Inf: d.Inf}}
 	}
-	n := d.normalize()
+	n := Digits{digits: d.normalize().digits, IsScientific: true}
 
 	// Significant digits are transformed by the parser for scientific notation
 	// and do not need to be handled here.
@@ -326,12 +337,10 @@
 
 // appendScientific appends a formatted number to dst. It returns two possible
 // insertion points for padding.
-func appendScientific(dst []byte, f *Formatter, d *Decimal) (b []byte, postPre, preSuf int) {
-	if dst, ok := f.renderSpecial(dst, d); ok {
+func appendScientific(dst []byte, f *Formatter, n *Digits) (b []byte, postPre, preSuf int) {
+	if dst, ok := f.renderSpecial(dst, n); ok {
 		return dst, 0, 0
 	}
-	// n := d.normalize()
-	n := scientificVisibleDigits(&f.RoundingContext, d)
 	digits := n.Digits
 	exp := n.Exp
 	numInt := int(n.Comma)
@@ -344,7 +353,7 @@
 	} else {
 		intDigits = digits
 	}
-	neg := d.Neg
+	neg := n.Neg
 	affix, suffix := f.getAffixes(neg)
 	dst = appendAffix(dst, f, affix, neg)
 	savedLen := len(dst)
@@ -462,7 +471,7 @@
 	return affix, suffix
 }
 
-func (f *Formatter) renderSpecial(dst []byte, d *Decimal) (b []byte, ok bool) {
+func (f *Formatter) renderSpecial(dst []byte, d *Digits) (b []byte, ok bool) {
 	if d.NaN {
 		return fmtNaN(dst, f), true
 	}
@@ -476,7 +485,7 @@
 	return append(dst, f.Symbol(SymNan)...)
 }
 
-func fmtInfinite(dst []byte, f *Formatter, d *Decimal) []byte {
+func fmtInfinite(dst []byte, f *Formatter, d *Digits) []byte {
 	affix, suffix := f.getAffixes(d.Neg)
 	dst = appendAffix(dst, f, affix, d.Neg)
 	dst = append(dst, f.Symbol(SymInfinity)...)
diff --git a/internal/number/format_test.go b/internal/number/format_test.go
index d7295e0..c56cbc1 100755
--- a/internal/number/format_test.go
+++ b/internal/number/format_test.go
@@ -449,11 +449,12 @@
 		}
 		var f Formatter
 		f.InitPattern(language.English, pat)
-		for dec, want := range tc.test {
+		for num, want := range tc.test {
 			buf := make([]byte, 100)
-			t.Run(tc.pattern+"/"+dec, func(t *testing.T) {
-				dec := mkdec(dec)
-				buf = f.Format(buf[:0], &dec)
+			t.Run(tc.pattern+"/"+num, func(t *testing.T) {
+				var d Decimal
+				d.Convert(&f.RoundingContext, dec(num))
+				buf = f.Format(buf[:0], &d)
 				if got := string(buf); got != want {
 					t.Errorf("\n got %[1]q (%[1]s)\nwant %[2]q (%[2]s)", got, want)
 				}
@@ -478,7 +479,8 @@
 		t.Run(fmt.Sprint(tc.tag, "/", tc.num), func(t *testing.T) {
 			var f Formatter
 			f.InitDecimal(tc.tag)
-			d := mkdec(tc.num)
+			var d Decimal
+			d.Convert(&f.RoundingContext, dec(tc.num))
 			b := f.Format(nil, &d)
 			if got := string(b); got != tc.want {
 				t.Errorf("got %[1]q (%[1]s); want %[2]q (%[2]s)", got, tc.want)
@@ -504,9 +506,9 @@
 	for i, tc := range testCases {
 		t.Run(fmt.Sprint(i, "/", tc.num), func(t *testing.T) {
 			tc.init(language.English)
-			f.Pattern.MinFractionDigits = 2
-			f.Pattern.MaxFractionDigits = 2
-			d := mkdec(tc.num)
+			f.SetScale(2)
+			var d Decimal
+			d.Convert(&f.RoundingContext, dec(tc.num))
 			b := f.Format(nil, &d)
 			if got := string(b); got != tc.want {
 				t.Errorf("got %[1]q (%[1]s); want %[2]q (%[2]s)", got, tc.want)
diff --git a/internal/number/pattern.go b/internal/number/pattern.go
index 8a3f595..881308e 100644
--- a/internal/number/pattern.go
+++ b/internal/number/pattern.go
@@ -60,14 +60,14 @@
 // It contains all information needed to determine the "visible digits" as
 // required by the pluralization rules.
 type RoundingContext struct {
-	Mode RoundingMode
-
 	Precision int32 // maximum number of significant digits.
 	Scale     int32 // maximum number of decimals after the dot.
 
 	// if > 0, round to Increment * 10^-Scale
 	Increment uint32 // Use Min*Digits to determine scale
 
+	Mode RoundingMode
+
 	DigitShift uint8 // Number of decimals to shift. Used for % and ‰.
 
 	// Number of digits.
@@ -82,6 +82,25 @@
 	MinExponentDigits uint8
 }
 
+func (r *RoundingContext) update() {
+	if r.Scale > 0 {
+		r.SetScale(int(r.Scale))
+	}
+	if r.Precision > 0 {
+		r.SetPrecision(int(r.Precision))
+	}
+}
+
+// SetScale fixes the RoundingContext to a fixed number of fraction digits.
+func (r *RoundingContext) SetScale(scale int) {
+	r.MinFractionDigits = uint8(scale)
+	r.MaxFractionDigits = uint8(scale)
+}
+
+func (r *RoundingContext) SetPrecision(prec int) {
+	r.MaxSignificantDigits = uint8(prec)
+}
+
 func (r *RoundingContext) isScientific() bool {
 	return r.MinExponentDigits > 0
 }