internal/number: fix more rounding issues
Removed AppendFloat-based rounding to
not hit bug golang/go#21714.
Fixed several edge cases and implemented some
missing features that were exposed as a result
of having to fall back on our own rounding.
This also fixed some previously unnoticed bugs.
Change-Id: Id675bdc9a9099eda0b9c31e5166b510b714887b5
Reviewed-on: https://go-review.googlesource.com/60730
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 ac2098d..62074e7 100644
--- a/internal/number/decimal.go
+++ b/internal/number/decimal.go
@@ -411,14 +411,18 @@
// ToNearestEven.
// Something like this would work:
// AppendDigits(dst []byte, x float64, base, size, prec int) (digits []byte, exp, accuracy int)
- if r.Mode == ToNearestEven {
- if n := r.RoundSignificantDigits(); n >= 0 {
- prec = n
- } else if n = r.RoundFractionDigits(); n >= 0 {
- prec = n
- verb = 'f'
- }
- }
+ //
+ // 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'
+ // }
+ // }
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 f13b7c1..04aa8b2 100644
--- a/internal/number/decimal_test.go
+++ b/internal/number/decimal_test.go
@@ -256,6 +256,13 @@
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"},
+
{int8(-34), scale2, "-34"},
{int16(-234), scale2, "-234"},
{int32(-234), scale2, "-234"},
@@ -266,22 +273,18 @@
{uint32(234), scale2, "234"},
{uint64(234), scale2, "234"},
{uint(234), scale2, "234"},
- {-0.001, scale2, "-0.00"}, // not normalized
- {-1e9, scale2, "-1000000000.00"},
+ {-1e9, scale2, "-1000000000"},
{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.00"}, // not normalized
- {0.075, inc0_05, "0.10"},
+ {0.025, inc0_05, "0"},
+ {0.075, inc0_05, "0.1"},
{325, inc50, "300"},
{375, 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.1230"},
+ {0.123, roundShift, "0.123"},
{converter(3), scale2, "100"},
diff --git a/internal/number/format.go b/internal/number/format.go
index 4104789..0a5ffb5 100755
--- a/internal/number/format.go
+++ b/internal/number/format.go
@@ -154,48 +154,47 @@
}
n := Digits{digits: d.normalize().digits}
- if maxSig := int(r.MaxSignificantDigits); maxSig > 0 {
- // TODO: really round to zero?
- n.round(ToZero, maxSig)
- }
- digits := n.Digits
exp := n.Exp
exp += int32(r.DigitShift)
// Cap integer digits. Remove *most-significant* digits.
if r.MaxIntegerDigits > 0 {
if p := int(exp) - int(r.MaxIntegerDigits); p > 0 {
- if p > len(digits) {
- p = len(digits)
+ if p > len(n.Digits) {
+ p = len(n.Digits)
}
- if digits = digits[p:]; len(digits) == 0 {
+ if n.Digits = n.Digits[p:]; len(n.Digits) == 0 {
exp = 0
} else {
exp -= int32(p)
}
// Strip leading zeros.
- for len(digits) > 0 && digits[0] == 0 {
- digits = digits[1:]
+ for len(n.Digits) > 0 && n.Digits[0] == 0 {
+ n.Digits = n.Digits[1:]
exp--
}
}
}
- // Rounding usually is done by convert, but we don't rely on it.
- numFrac := len(digits) - int(exp)
- if r.MaxSignificantDigits == 0 && int(r.MaxFractionDigits) < numFrac {
- p := int(exp) + int(r.MaxFractionDigits)
- if p <= 0 {
- p = 0
- } else if p >= len(digits) {
- p = len(digits)
- }
- digits = digits[:p] // TODO: round
+ // Rounding if not already done by Convert.
+ p := len(n.Digits)
+ if maxSig := int(r.MaxSignificantDigits); maxSig > 0 {
+ p = maxSig
}
+ if maxFrac := int(r.MaxFractionDigits); maxFrac >= 0 {
+ if cap := int(exp) + maxFrac; cap < p {
+ p = int(exp) + maxFrac
+ }
+ if p < 0 {
+ p = 0
+ }
+ }
+ n.round(r.Mode, p)
// set End (trailing zeros)
- n.End = int32(len(digits))
- if len(digits) == 0 {
+ n.End = int32(len(n.Digits))
+ if n.End == 0 {
+ exp = 0
if r.MinFractionDigits > 0 {
n.End = int32(r.MinFractionDigits)
}
@@ -210,7 +209,6 @@
n.End = int32(r.MinSignificantDigits)
}
}
- n.Digits = digits
n.Exp = exp
return n
}
@@ -323,9 +321,14 @@
numInt += d
}
- if maxSig := int(r.MaxFractionDigits); maxSig >= 0 {
- n.round(r.Mode, maxSig+numInt)
+ p := len(n.Digits)
+ if maxSig := int(r.MaxSignificantDigits); maxSig > 0 {
+ p = maxSig
}
+ if maxFrac := int(r.MaxFractionDigits); maxFrac >= 0 && numInt+maxFrac < p {
+ p = numInt + maxFrac
+ }
+ n.round(r.Mode, p)
n.Comma = uint8(numInt)
n.End = int32(len(n.Digits))
diff --git a/internal/number/format_test.go b/internal/number/format_test.go
index 1273818..01a0894 100755
--- a/internal/number/format_test.go
+++ b/internal/number/format_test.go
@@ -112,7 +112,7 @@
test: pairs{
"0": "0",
"1234.5678": "1234.5678",
- "0.123456789": "0.123456",
+ "0.123456789": "0.123457",
"NaN": "NaN",
"Inf": "∞",
},
@@ -142,7 +142,7 @@
pattern: "#,##0.###",
test: pairs{
"0": "0",
- "1234.5678": "1,234.567",
+ "1234.5678": "1,234.568",
"0.123456789": "0.123",
},
}, {
@@ -157,7 +157,7 @@
test: pairs{
"0": "0,00,000",
"123456789012": "1,23,45,67,89,012",
- "12.3456789": "0,00,012.345",
+ "12.3456789": "0,00,012.346",
"0.123456789": "0,00,000.123",
},
@@ -245,7 +245,7 @@
pattern: "@@##",
test: pairs{
"1": "1.0",
- "0.1": "0.10",
+ "0.1": "0.10", // leading zero does not count as significant digit
"123": "123",
"1234": "1234",
"12345": "12340",
diff --git a/internal/number/pattern.go b/internal/number/pattern.go
index eebaafd..b95ca40 100644
--- a/internal/number/pattern.go
+++ b/internal/number/pattern.go
@@ -358,6 +358,7 @@
case '@':
p.groupingCount++
p.leadingSharps = 0
+ p.MaxFractionDigits = -1
return p.sigDigits(r)
case ',':
if p.leadingSharps == 0 { // no leading commas
diff --git a/internal/number/pattern_test.go b/internal/number/pattern_test.go
index 3a0b2be..a7517d0 100644
--- a/internal/number/pattern_test.go
+++ b/internal/number/pattern_test.go
@@ -203,6 +203,7 @@
RoundingContext: RoundingContext{
MinSignificantDigits: 1,
MaxSignificantDigits: 1,
+ MaxFractionDigits: -1,
},
},
}, {
@@ -213,6 +214,7 @@
RoundingContext: RoundingContext{
MinSignificantDigits: 4,
MaxSignificantDigits: 4,
+ MaxFractionDigits: -1,
},
},
}, {
@@ -222,6 +224,7 @@
RoundingContext: RoundingContext{
MinSignificantDigits: 1,
MaxSignificantDigits: 4,
+ MaxFractionDigits: -1,
},
},
}, {