| // 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} | |
| incScaleEqualToScalesLen := RoundingContext{Increment: 1, IncrementScale: 0} | |
| if len(scales) <= math.MaxUint8 { | |
| incScaleEqualToScalesLen.IncrementScale = uint8(len(scales)) | |
| } | |
| 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"}, | |
| {0.0, incScaleEqualToScalesLen, "0"}, | |
| } | |
| 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 | |
| } |