1
2
3
4
5 package number
6
7 import (
8 "fmt"
9 "math"
10 "strconv"
11 "strings"
12 "testing"
13 )
14
15 func mkfloat(num string) float64 {
16 u, _ := strconv.ParseUint(num, 10, 32)
17 return float64(u)
18 }
19
20
21
22
23
24 func mkdec(num string) (d Decimal) {
25 var r RoundingContext
26 d.Convert(r, dec(num))
27 return
28 }
29
30 type dec string
31
32 func (s dec) Convert(d *Decimal, _ RoundingContext) {
33 num := string(s)
34 if num[0] == '-' {
35 d.Neg = true
36 num = num[1:]
37 }
38 switch num {
39 case "NaN":
40 d.NaN = true
41 return
42 case "Inf":
43 d.Inf = true
44 return
45 }
46 if p := strings.IndexAny(num, "eE"); p != -1 {
47 i64, err := strconv.ParseInt(num[p+1:], 10, 32)
48 if err != nil {
49 panic(err)
50 }
51 d.Exp = int32(i64)
52 num = num[:p]
53 }
54 if p := strings.IndexByte(num, '.'); p != -1 {
55 d.Exp += int32(p)
56 num = num[:p] + num[p+1:]
57 } else {
58 d.Exp += int32(len(num))
59 }
60 d.Digits = []byte(num)
61 for i := range d.Digits {
62 d.Digits[i] -= '0'
63 }
64 *d = d.normalize()
65 }
66
67 func byteNum(s string) []byte {
68 b := make([]byte, len(s))
69 for i := 0; i < len(s); i++ {
70 if c := s[i]; '0' <= c && c <= '9' {
71 b[i] = s[i] - '0'
72 } else {
73 b[i] = s[i] - 'a' + 10
74 }
75 }
76 return b
77 }
78
79 func strNum(s string) string {
80 return string(byteNum(s))
81 }
82
83 func TestDecimalString(t *testing.T) {
84 for _, test := range []struct {
85 x Decimal
86 want string
87 }{
88 {want: "0"},
89 {Decimal{digits: digits{Digits: nil, Exp: 1000}}, "0"},
90 {Decimal{digits: digits{Digits: byteNum("12345"), Exp: 0}}, "0.12345"},
91 {Decimal{digits: digits{Digits: byteNum("12345"), Exp: -3}}, "0.00012345"},
92 {Decimal{digits: digits{Digits: byteNum("12345"), Exp: +3}}, "123.45"},
93 {Decimal{digits: digits{Digits: byteNum("12345"), Exp: +10}}, "1234500000"},
94 } {
95 if got := test.x.String(); got != test.want {
96 t.Errorf("%v == %q; want %q", test.x, got, test.want)
97 }
98 }
99 }
100
101 func TestRounding(t *testing.T) {
102 testCases := []struct {
103 x string
104 n int
105
106
107
108
109
110 modes [numModes]string
111 }{
112 {"0", 1, [numModes]string{
113 "0", "0",
114 "0", "0", "0",
115 "0", "0"}},
116 {"1", 1, [numModes]string{
117 "1", "1",
118 "1", "1", "1",
119 "1", "1"}},
120 {"5", 1, [numModes]string{
121 "5", "5",
122 "5", "5", "5",
123 "5", "5"}},
124 {"15", 1, [numModes]string{
125 "10", "10",
126 "10", "20", "20",
127 "20", "20"}},
128 {"45", 1, [numModes]string{
129 "40", "40",
130 "40", "40", "50",
131 "50", "50"}},
132 {"95", 1, [numModes]string{
133 "90", "90",
134 "90", "100", "100",
135 "100", "100"}},
136
137 {"12344999", 4, [numModes]string{
138 "12340000", "12340000",
139 "12340000", "12340000", "12340000",
140 "12350000", "12350000"}},
141 {"12345000", 4, [numModes]string{
142 "12340000", "12340000",
143 "12340000", "12340000", "12350000",
144 "12350000", "12350000"}},
145 {"12345001", 4, [numModes]string{
146 "12340000", "12340000",
147 "12350000", "12350000", "12350000",
148 "12350000", "12350000"}},
149 {"12345100", 4, [numModes]string{
150 "12340000", "12340000",
151 "12350000", "12350000", "12350000",
152 "12350000", "12350000"}},
153 {"23454999", 4, [numModes]string{
154 "23450000", "23450000",
155 "23450000", "23450000", "23450000",
156 "23460000", "23460000"}},
157 {"23455000", 4, [numModes]string{
158 "23450000", "23450000",
159 "23450000", "23460000", "23460000",
160 "23460000", "23460000"}},
161 {"23455001", 4, [numModes]string{
162 "23450000", "23450000",
163 "23460000", "23460000", "23460000",
164 "23460000", "23460000"}},
165 {"23455100", 4, [numModes]string{
166 "23450000", "23450000",
167 "23460000", "23460000", "23460000",
168 "23460000", "23460000"}},
169
170 {"99994999", 4, [numModes]string{
171 "99990000", "99990000",
172 "99990000", "99990000", "99990000",
173 "100000000", "100000000"}},
174 {"99995000", 4, [numModes]string{
175 "99990000", "99990000",
176 "99990000", "100000000", "100000000",
177 "100000000", "100000000"}},
178 {"99999999", 4, [numModes]string{
179 "99990000", "99990000",
180 "100000000", "100000000", "100000000",
181 "100000000", "100000000"}},
182
183 {"12994999", 4, [numModes]string{
184 "12990000", "12990000",
185 "12990000", "12990000", "12990000",
186 "13000000", "13000000"}},
187 {"12995000", 4, [numModes]string{
188 "12990000", "12990000",
189 "12990000", "13000000", "13000000",
190 "13000000", "13000000"}},
191 {"12999999", 4, [numModes]string{
192 "12990000", "12990000",
193 "13000000", "13000000", "13000000",
194 "13000000", "13000000"}},
195 }
196 modes := []RoundingMode{
197 ToZero, ToNegativeInf,
198 ToNearestZero, ToNearestEven, ToNearestAway,
199 AwayFromZero, ToPositiveInf,
200 }
201 for _, tc := range testCases {
202
203
204 negModes := tc.modes
205 negModes[1], negModes[6] = negModes[6], negModes[1]
206 for i, res := range negModes {
207 negModes[i] = "-" + res
208 }
209 for i, m := range modes {
210 t.Run(fmt.Sprintf("x:%s/n:%d/%s", tc.x, tc.n, m), func(t *testing.T) {
211 d := mkdec(tc.x)
212 d.round(m, tc.n)
213 if got := d.String(); got != tc.modes[i] {
214 t.Errorf("pos decimal: got %q; want %q", d.String(), tc.modes[i])
215 }
216
217 mult := math.Pow(10, float64(len(tc.x)-tc.n))
218 f := mkfloat(tc.x)
219 f = m.roundFloat(f/mult) * mult
220 if got := fmt.Sprintf("%.0f", f); got != tc.modes[i] {
221 t.Errorf("pos float: got %q; want %q", got, tc.modes[i])
222 }
223
224
225
226 d = mkdec(tc.x)
227 d.Neg = true
228 d.round(m, tc.n)
229 if got, want := d.String(), negModes[i]; got != want {
230 t.Errorf("neg decimal: got %q; want %q", d.String(), want)
231 }
232
233 f = -mkfloat(tc.x)
234 f = m.roundFloat(f/mult) * mult
235 if got := fmt.Sprintf("%.0f", f); got != negModes[i] {
236 t.Errorf("neg float: got %q; want %q", got, negModes[i])
237 }
238 })
239 }
240 }
241 }
242
243 func TestConvert(t *testing.T) {
244 scale2 := RoundingContext{}
245 scale2.SetScale(2)
246 scale2away := RoundingContext{Mode: AwayFromZero}
247 scale2away.SetScale(2)
248 inc0_05 := RoundingContext{Increment: 5, IncrementScale: 2}
249 inc0_05.SetScale(2)
250 inc50 := RoundingContext{Increment: 50}
251 incScaleEqualToScalesLen := RoundingContext{Increment: 1, IncrementScale: 0}
252 if len(scales) <= math.MaxUint8 {
253 incScaleEqualToScalesLen.IncrementScale = uint8(len(scales))
254 }
255 prec3 := RoundingContext{}
256 prec3.SetPrecision(3)
257 roundShift := RoundingContext{DigitShift: 2, MaxFractionDigits: 2}
258 testCases := []struct {
259 x interface{}
260 rc RoundingContext
261 out string
262 }{
263 {-0.001, scale2, "-0.00"},
264 {0.1234, prec3, "0.123"},
265 {1234.0, prec3, "1230"},
266 {1.2345e10, prec3, "12300000000"},
267
268 {int8(-34), scale2, "-34"},
269 {int16(-234), scale2, "-234"},
270 {int32(-234), scale2, "-234"},
271 {int64(-234), scale2, "-234"},
272 {int(-234), scale2, "-234"},
273 {uint8(234), scale2, "234"},
274 {uint16(234), scale2, "234"},
275 {uint32(234), scale2, "234"},
276 {uint64(234), scale2, "234"},
277 {uint(234), scale2, "234"},
278 {-1e9, scale2, "-1000000000.00"},
279
280
281
282
283 {0.234, scale2away,
284 "0.2340000000000000135447209004269097931683063507080078125"},
285
286 {0.0249, inc0_05, "0.00"},
287 {0.025, inc0_05, "0.00"},
288 {0.0251, inc0_05, "0.05"},
289 {0.03, inc0_05, "0.05"},
290 {0.049, inc0_05, "0.05"},
291 {0.05, inc0_05, "0.05"},
292 {0.051, inc0_05, "0.05"},
293 {0.0749, inc0_05, "0.05"},
294 {0.075, inc0_05, "0.10"},
295 {0.0751, inc0_05, "0.10"},
296 {324, inc50, "300"},
297 {325, inc50, "300"},
298 {326, inc50, "350"},
299 {349, inc50, "350"},
300 {350, inc50, "350"},
301 {351, inc50, "350"},
302 {374, inc50, "350"},
303 {375, inc50, "400"},
304 {376, inc50, "400"},
305
306
307
308 {0.123, roundShift, "0.1230"},
309
310 {converter(3), scale2, "100"},
311
312 {math.Inf(1), inc50, "Inf"},
313 {math.Inf(-1), inc50, "-Inf"},
314 {math.NaN(), inc50, "NaN"},
315 {"clearly not a number", scale2, "NaN"},
316 {0.0, incScaleEqualToScalesLen, "0"},
317 }
318 for _, tc := range testCases {
319 var d Decimal
320 t.Run(fmt.Sprintf("%T:%v-%v", tc.x, tc.x, tc.rc), func(t *testing.T) {
321 d.Convert(tc.rc, tc.x)
322 if got := d.String(); got != tc.out {
323 t.Errorf("got %q; want %q", got, tc.out)
324 }
325 })
326 }
327 }
328
329 type converter int
330
331 func (c converter) Convert(d *Decimal, r RoundingContext) {
332 d.Digits = append(d.Digits, 1, 0, 0)
333 d.Exp = 3
334 }
335
View as plain text