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
}