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 }