123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- // Copyright 2017 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package number
- import (
- "fmt"
- "math"
- "strconv"
- "strings"
- "testing"
- )
- func mkfloat(num string) float64 {
- u, _ := strconv.ParseUint(num, 10, 32)
- return float64(u)
- }
- // mkdec creates a decimal from a string. All ASCII digits are converted to
- // digits in the decimal. The dot is used to indicate the scale by which the
- // digits are shifted. Numbers may have an additional exponent or be the special
- // value NaN, Inf, or -Inf.
- func mkdec(num string) (d Decimal) {
- var r RoundingContext
- d.Convert(r, dec(num))
- return
- }
- type dec string
- func (s dec) Convert(d *Decimal, _ RoundingContext) {
- num := string(s)
- if num[0] == '-' {
- d.Neg = true
- num = num[1:]
- }
- switch num {
- case "NaN":
- d.NaN = true
- return
- case "Inf":
- d.Inf = true
- return
- }
- if p := strings.IndexAny(num, "eE"); p != -1 {
- i64, err := strconv.ParseInt(num[p+1:], 10, 32)
- if err != nil {
- panic(err)
- }
- d.Exp = int32(i64)
- num = num[:p]
- }
- if p := strings.IndexByte(num, '.'); p != -1 {
- d.Exp += int32(p)
- num = num[:p] + num[p+1:]
- } else {
- d.Exp += int32(len(num))
- }
- d.Digits = []byte(num)
- for i := range d.Digits {
- d.Digits[i] -= '0'
- }
- *d = d.normalize()
- }
- func byteNum(s string) []byte {
- b := make([]byte, len(s))
- for i := 0; i < len(s); i++ {
- if c := s[i]; '0' <= c && c <= '9' {
- b[i] = s[i] - '0'
- } else {
- b[i] = s[i] - 'a' + 10
- }
- }
- return b
- }
- func strNum(s string) string {
- return string(byteNum(s))
- }
- func TestDecimalString(t *testing.T) {
- for _, test := range []struct {
- x Decimal
- want string
- }{
- {want: "0"},
- {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)
- }
- }
- }
- func TestRounding(t *testing.T) {
- testCases := []struct {
- x string
- n int
- // modes is the result for modes. Signs are left out of the result.
- // The results are stored in the following order:
- // zero, negInf
- // nearZero, nearEven, nearAway
- // away, posInf
- modes [numModes]string
- }{
- {"0", 1, [numModes]string{
- "0", "0",
- "0", "0", "0",
- "0", "0"}},
- {"1", 1, [numModes]string{
- "1", "1",
- "1", "1", "1",
- "1", "1"}},
- {"5", 1, [numModes]string{
- "5", "5",
- "5", "5", "5",
- "5", "5"}},
- {"15", 1, [numModes]string{
- "10", "10",
- "10", "20", "20",
- "20", "20"}},
- {"45", 1, [numModes]string{
- "40", "40",
- "40", "40", "50",
- "50", "50"}},
- {"95", 1, [numModes]string{
- "90", "90",
- "90", "100", "100",
- "100", "100"}},
- {"12344999", 4, [numModes]string{
- "12340000", "12340000",
- "12340000", "12340000", "12340000",
- "12350000", "12350000"}},
- {"12345000", 4, [numModes]string{
- "12340000", "12340000",
- "12340000", "12340000", "12350000",
- "12350000", "12350000"}},
- {"12345001", 4, [numModes]string{
- "12340000", "12340000",
- "12350000", "12350000", "12350000",
- "12350000", "12350000"}},
- {"12345100", 4, [numModes]string{
- "12340000", "12340000",
- "12350000", "12350000", "12350000",
- "12350000", "12350000"}},
- {"23454999", 4, [numModes]string{
- "23450000", "23450000",
- "23450000", "23450000", "23450000",
- "23460000", "23460000"}},
- {"23455000", 4, [numModes]string{
- "23450000", "23450000",
- "23450000", "23460000", "23460000",
- "23460000", "23460000"}},
- {"23455001", 4, [numModes]string{
- "23450000", "23450000",
- "23460000", "23460000", "23460000",
- "23460000", "23460000"}},
- {"23455100", 4, [numModes]string{
- "23450000", "23450000",
- "23460000", "23460000", "23460000",
- "23460000", "23460000"}},
- {"99994999", 4, [numModes]string{
- "99990000", "99990000",
- "99990000", "99990000", "99990000",
- "100000000", "100000000"}},
- {"99995000", 4, [numModes]string{
- "99990000", "99990000",
- "99990000", "100000000", "100000000",
- "100000000", "100000000"}},
- {"99999999", 4, [numModes]string{
- "99990000", "99990000",
- "100000000", "100000000", "100000000",
- "100000000", "100000000"}},
- {"12994999", 4, [numModes]string{
- "12990000", "12990000",
- "12990000", "12990000", "12990000",
- "13000000", "13000000"}},
- {"12995000", 4, [numModes]string{
- "12990000", "12990000",
- "12990000", "13000000", "13000000",
- "13000000", "13000000"}},
- {"12999999", 4, [numModes]string{
- "12990000", "12990000",
- "13000000", "13000000", "13000000",
- "13000000", "13000000"}},
- }
- modes := []RoundingMode{
- ToZero, ToNegativeInf,
- ToNearestZero, ToNearestEven, ToNearestAway,
- AwayFromZero, ToPositiveInf,
- }
- for _, tc := range testCases {
- // Create negative counterpart tests: the sign is reversed and
- // ToPositiveInf and ToNegativeInf swapped.
- negModes := tc.modes
- negModes[1], negModes[6] = negModes[6], negModes[1]
- for i, res := range negModes {
- negModes[i] = "-" + res
- }
- for i, m := range modes {
- t.Run(fmt.Sprintf("x:%s/n:%d/%s", tc.x, tc.n, m), func(t *testing.T) {
- d := mkdec(tc.x)
- d.round(m, tc.n)
- if got := d.String(); got != tc.modes[i] {
- t.Errorf("pos decimal: got %q; want %q", d.String(), tc.modes[i])
- }
- mult := math.Pow(10, float64(len(tc.x)-tc.n))
- f := mkfloat(tc.x)
- f = m.roundFloat(f/mult) * mult
- if got := fmt.Sprintf("%.0f", f); got != tc.modes[i] {
- t.Errorf("pos float: got %q; want %q", got, tc.modes[i])
- }
- // Test the negative case. This is the same as the positive
- // case, but with ToPositiveInf and ToNegativeInf swapped.
- d = mkdec(tc.x)
- d.Neg = true
- d.round(m, tc.n)
- if got, want := d.String(), negModes[i]; got != want {
- t.Errorf("neg decimal: got %q; want %q", d.String(), want)
- }
- f = -mkfloat(tc.x)
- f = m.roundFloat(f/mult) * mult
- if got := fmt.Sprintf("%.0f", f); got != negModes[i] {
- t.Errorf("neg float: got %q; want %q", got, negModes[i])
- }
- })
- }
- }
- }
- func TestConvert(t *testing.T) {
- scale2 := RoundingContext{}
- scale2.SetScale(2)
- scale2away := RoundingContext{Mode: AwayFromZero}
- scale2away.SetScale(2)
- inc0_05 := RoundingContext{Increment: 5, IncrementScale: 2}
- inc0_05.SetScale(2)
- inc50 := RoundingContext{Increment: 50}
- prec3 := RoundingContext{}
- prec3.SetPrecision(3)
- roundShift := RoundingContext{DigitShift: 2, MaxFractionDigits: 2}
- testCases := []struct {
- x interface{}
- rc RoundingContext
- out string
- }{
- {-0.001, scale2, "-0.00"},
- {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"},
- {int64(-234), scale2, "-234"},
- {int(-234), scale2, "-234"},
- {uint8(234), scale2, "234"},
- {uint16(234), scale2, "234"},
- {uint32(234), scale2, "234"},
- {uint64(234), scale2, "234"},
- {uint(234), scale2, "234"},
- {-1e9, scale2, "-1000000000.00"},
- // The following two causes this result to have a lot of digits:
- // 1) 0.234 cannot be accurately represented as a float64, and
- // 2) as strconv does not support the rounding AwayFromZero, Convert
- // leaves the rounding to caller.
- {0.234, scale2away,
- "0.2340000000000000135447209004269097931683063507080078125"},
- {0.0249, inc0_05, "0.00"},
- {0.025, inc0_05, "0.00"},
- {0.0251, inc0_05, "0.05"},
- {0.03, inc0_05, "0.05"},
- {0.049, inc0_05, "0.05"},
- {0.05, inc0_05, "0.05"},
- {0.051, inc0_05, "0.05"},
- {0.0749, inc0_05, "0.05"},
- {0.075, inc0_05, "0.10"},
- {0.0751, inc0_05, "0.10"},
- {324, inc50, "300"},
- {325, inc50, "300"},
- {326, inc50, "350"},
- {349, inc50, "350"},
- {350, inc50, "350"},
- {351, inc50, "350"},
- {374, inc50, "350"},
- {375, inc50, "400"},
- {376, 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"},
- {converter(3), scale2, "100"},
- {math.Inf(1), inc50, "Inf"},
- {math.Inf(-1), inc50, "-Inf"},
- {math.NaN(), inc50, "NaN"},
- {"clearly not a number", scale2, "NaN"},
- }
- for _, tc := range testCases {
- var d Decimal
- t.Run(fmt.Sprintf("%T:%v-%v", tc.x, tc.x, tc.rc), func(t *testing.T) {
- d.Convert(tc.rc, tc.x)
- if got := d.String(); got != tc.out {
- t.Errorf("got %q; want %q", got, tc.out)
- }
- })
- }
- }
- type converter int
- func (c converter) Convert(d *Decimal, r RoundingContext) {
- d.Digits = append(d.Digits, 1, 0, 0)
- d.Exp = 3
- }
|