amount_test.go raw
1 package amt_test
2
3 import (
4 "github.com/p9c/p9/pkg/amt"
5 "math"
6 "testing"
7 )
8
9 func TestAmountCreation(t *testing.T) {
10 tests := []struct {
11 name string
12 amount float64
13 valid bool
14 expected amt.Amount
15 }{
16 // Positive tests.
17 {
18 name: "zero",
19 amount: 0,
20 valid: true,
21 expected: 0,
22 },
23 {
24 name: "max producible",
25 amount: 21e6,
26 valid: true,
27 expected: amt.MaxSatoshi,
28 },
29 {
30 name: "min producible",
31 amount: -21e6,
32 valid: true,
33 expected: -amt.MaxSatoshi,
34 },
35 {
36 name: "exceeds max producible",
37 amount: 21e6 + 1e-8,
38 valid: true,
39 expected: amt.MaxSatoshi + 1,
40 },
41 {
42 name: "exceeds min producible",
43 amount: -21e6 - 1e-8,
44 valid: true,
45 expected: -amt.MaxSatoshi - 1,
46 },
47 {
48 name: "one hundred",
49 amount: 100,
50 valid: true,
51 expected: 100 * amt.SatoshiPerBitcoin,
52 },
53 {
54 name: "fraction",
55 amount: 0.01234567,
56 valid: true,
57 expected: 1234567,
58 },
59 {
60 name: "rounding up",
61 amount: 54.999999999999943157,
62 valid: true,
63 expected: 55 * amt.SatoshiPerBitcoin,
64 },
65 {
66 name: "rounding down",
67 amount: 55.000000000000056843,
68 valid: true,
69 expected: 55 * amt.SatoshiPerBitcoin,
70 },
71 // Negative tests.
72 {
73 name: "not-a-number",
74 amount: math.NaN(),
75 valid: false,
76 },
77 {
78 name: "-infinity",
79 amount: math.Inf(-1),
80 valid: false,
81 },
82 {
83 name: "+infinity",
84 amount: math.Inf(1),
85 valid: false,
86 },
87 }
88 for _, test := range tests {
89 a, e := amt.NewAmount(test.amount)
90 switch {
91 case test.valid && e != nil:
92 t.Errorf("%v: Positive test Amount creation failed with: %v", test.name, e)
93 continue
94 case !test.valid && e == nil:
95 t.Errorf("%v: Negative test Amount creation succeeded (value %v) when should fail", test.name, a)
96 continue
97 }
98 if a != test.expected {
99 t.Errorf("%v: Created amount %v does not match expected %v", test.name, a, test.expected)
100 continue
101 }
102 }
103 }
104 func TestAmountUnitConversions(t *testing.T) {
105 tests := []struct {
106 name string
107 amount amt.Amount
108 unit amt.Unit
109 converted float64
110 s string
111 }{
112 {
113 name: "MDUO",
114 amount: amt.MaxSatoshi,
115 unit: amt.MegaDUO,
116 converted: 21,
117 s: "21 MDUO",
118 },
119 {
120 name: "kDUO",
121 amount: 44433322211100,
122 unit: amt.KiloDUO,
123 converted: 444.33322211100,
124 s: "444.333222111 kDUO",
125 },
126 {
127 name: "DUO",
128 amount: 44433322211100,
129 unit: amt.DUO,
130 converted: 444333.22211100,
131 s: "444333.222111 DUO",
132 },
133 {
134 name: "mDUO",
135 amount: 44433322211100,
136 unit: amt.MilliDUO,
137 converted: 444333222.11100,
138 s: "444333222.111 mDUO",
139 },
140 {
141 name: "μDUO",
142 amount: 44433322211100,
143 unit: amt.MicroDUO,
144 converted: 444333222111.00,
145 s: "444333222111 μDUO",
146 },
147 {
148 name: "satoshi",
149 amount: 44433322211100,
150 unit: amt.Satoshi,
151 converted: 44433322211100,
152 s: "44433322211100 Satoshi",
153 },
154 {
155 name: "non-standard unit",
156 amount: 44433322211100,
157 unit: amt.Unit(-1),
158 converted: 4443332.2211100,
159 s: "4443332.22111 1e-1 DUO",
160 },
161 }
162 for _, test := range tests {
163 f := test.amount.ToUnit(test.unit)
164 if f != test.converted {
165 t.Errorf("%v: converted value %v does not match expected %v", test.name, f, test.converted)
166 continue
167 }
168 s := test.amount.Format(test.unit)
169 if s != test.s {
170 t.Errorf("%v: format '%v' does not match expected '%v'", test.name, s, test.s)
171 continue
172 }
173 // Verify that Amount.ToDUO works as advertised.
174 f1 := test.amount.ToUnit(amt.DUO)
175 f2 := test.amount.ToDUO()
176 if f1 != f2 {
177 t.Errorf("%v: ToDUO does not match ToUnit(AmountDUO): %v != %v", test.name, f1, f2)
178 }
179 // Verify that Amount.String works as advertised.
180 s1 := test.amount.Format(amt.DUO)
181 s2 := test.amount.String()
182 if s1 != s2 {
183 t.Errorf("%v: String does not match Format(AmountBitcoin): %v != %v", test.name, s1, s2)
184 }
185 }
186 }
187 func TestAmountMulF64(t *testing.T) {
188 tests := []struct {
189 name string
190 amt amt.Amount
191 mul float64
192 res amt.Amount
193 }{
194 {
195 name: "Multiply 0.1 DUO by 2",
196 amt: 100e5, // 0.1 DUO
197 mul: 2,
198 res: 200e5, // 0.2 DUO
199 },
200 {
201 name: "Multiply 0.2 DUO by 0.02",
202 amt: 200e5, // 0.2 DUO
203 mul: 1.02,
204 res: 204e5, // 0.204 DUO
205 },
206 {
207 name: "Multiply 0.1 DUO by -2",
208 amt: 100e5, // 0.1 DUO
209 mul: -2,
210 res: -200e5, // -0.2 DUO
211 },
212 {
213 name: "Multiply 0.2 DUO by -0.02",
214 amt: 200e5, // 0.2 DUO
215 mul: -1.02,
216 res: -204e5, // -0.204 DUO
217 },
218 {
219 name: "Multiply -0.1 DUO by 2",
220 amt: -100e5, // -0.1 DUO
221 mul: 2,
222 res: -200e5, // -0.2 DUO
223 },
224 {
225 name: "Multiply -0.2 DUO by 0.02",
226 amt: -200e5, // -0.2 DUO
227 mul: 1.02,
228 res: -204e5, // -0.204 DUO
229 },
230 {
231 name: "Multiply -0.1 DUO by -2",
232 amt: -100e5, // -0.1 DUO
233 mul: -2,
234 res: 200e5, // 0.2 DUO
235 },
236 {
237 name: "Multiply -0.2 DUO by -0.02",
238 amt: -200e5, // -0.2 DUO
239 mul: -1.02,
240 res: 204e5, // 0.204 DUO
241 },
242 {
243 name: "Round down",
244 amt: 49, // 49 Satoshis
245 mul: 0.01,
246 res: 0,
247 },
248 {
249 name: "Round up",
250 amt: 50, // 50 Satoshis
251 mul: 0.01,
252 res: 1, // 1 Satoshi
253 },
254 {
255 name: "Multiply by 0.",
256 amt: 1e8, // 1 DUO
257 mul: 0,
258 res: 0, // 0 DUO
259 },
260 {
261 name: "Multiply 1 by 0.5.",
262 amt: 1, // 1 Satoshi
263 mul: 0.5,
264 res: 1, // 1 Satoshi
265 },
266 {
267 name: "Multiply 100 by 66%.",
268 amt: 100, // 100 Satoshis
269 mul: 0.66,
270 res: 66, // 66 Satoshis
271 },
272 {
273 name: "Multiply 100 by 66.6%.",
274 amt: 100, // 100 Satoshis
275 mul: 0.666,
276 res: 67, // 67 Satoshis
277 },
278 {
279 name: "Multiply 100 by 2/3.",
280 amt: 100, // 100 Satoshis
281 mul: 2.0 / 3,
282 res: 67, // 67 Satoshis
283 },
284 }
285 for _, test := range tests {
286 a := test.amt.MulF64(test.mul)
287 if a != test.res {
288 t.Errorf("%v: expected %v got %v", test.name, test.res, a)
289 }
290 }
291 }
292