]>
Commit | Line | Data |
---|---|---|
0f5d89e8 A |
1 | // © 2017 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html | |
3 | ||
4 | #include "unicode/utypes.h" | |
5 | ||
6 | #if !UCONFIG_NO_FORMATTING | |
7 | ||
8 | #include "number_decimalquantity.h" | |
9 | #include "number_decnum.h" | |
10 | #include "math.h" | |
11 | #include <cmath> | |
12 | #include "number_utils.h" | |
13 | #include "numbertest.h" | |
14 | ||
15 | void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { | |
16 | if (exec) { | |
17 | logln("TestSuite DecimalQuantityTest: "); | |
18 | } | |
19 | TESTCASE_AUTO_BEGIN; | |
20 | TESTCASE_AUTO(testDecimalQuantityBehaviorStandalone); | |
21 | TESTCASE_AUTO(testSwitchStorage);; | |
22 | TESTCASE_AUTO(testCopyMove); | |
23 | TESTCASE_AUTO(testAppend); | |
24 | TESTCASE_AUTO(testConvertToAccurateDouble); | |
25 | TESTCASE_AUTO(testUseApproximateDoubleWhenAble); | |
26 | TESTCASE_AUTO(testHardDoubleConversion); | |
27 | TESTCASE_AUTO(testToDouble); | |
28 | TESTCASE_AUTO(testMaxDigits); | |
29 | TESTCASE_AUTO_END; | |
30 | } | |
31 | ||
32 | void DecimalQuantityTest::assertDoubleEquals(UnicodeString message, double a, double b) { | |
33 | if (a == b) { | |
34 | return; | |
35 | } | |
36 | ||
37 | double diff = a - b; | |
38 | diff = diff < 0 ? -diff : diff; | |
39 | double bound = a < 0 ? -a * 1e-6 : a * 1e-6; | |
40 | if (diff > bound) { | |
41 | errln(message + u": " + DoubleToUnicodeString(a) + u" vs " + DoubleToUnicodeString(b) + u" differ by " + DoubleToUnicodeString(diff)); | |
42 | } | |
43 | } | |
44 | ||
45 | void DecimalQuantityTest::assertHealth(const DecimalQuantity &fq) { | |
46 | const char16_t* health = fq.checkHealth(); | |
47 | if (health != nullptr) { | |
48 | errln(UnicodeString(u"HEALTH FAILURE: ") + UnicodeString(health) + u": " + fq.toString()); | |
49 | } | |
50 | } | |
51 | ||
52 | void | |
53 | DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected) { | |
54 | UnicodeString actual = fq.toString(); | |
55 | assertEquals("DecimalQuantity toString failed", expected, actual); | |
56 | assertHealth(fq); | |
57 | } | |
58 | ||
59 | void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) { | |
60 | DecimalQuantity fq; | |
61 | fq.setToDouble(d); | |
62 | if (explicitRequired) { | |
63 | assertTrue("Should be using approximate double", !fq.isExplicitExactDouble()); | |
64 | } | |
65 | UnicodeString baseStr = fq.toString(); | |
66 | fq.roundToInfinity(); | |
67 | UnicodeString newStr = fq.toString(); | |
68 | if (explicitRequired) { | |
69 | assertTrue("Should not be using approximate double", fq.isExplicitExactDouble()); | |
70 | } | |
71 | assertDoubleEquals( | |
72 | UnicodeString(u"After conversion to exact BCD (double): ") + baseStr + u" vs " + newStr, | |
73 | d, fq.toDouble()); | |
74 | } | |
75 | ||
76 | void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() { | |
77 | UErrorCode status = U_ZERO_ERROR; | |
78 | DecimalQuantity fq; | |
79 | assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 0E0>"); | |
80 | fq.setToInt(51423); | |
81 | assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E0>"); | |
82 | fq.adjustMagnitude(-3); | |
83 | assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E-3>"); | |
84 | fq.setToLong(999999999999000L); | |
85 | assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 999999999999E3>"); | |
86 | fq.setIntegerLength(2, 5); | |
87 | assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:0:-999 long 999999999999E3>"); | |
88 | fq.setFractionLength(3, 6); | |
89 | assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 999999999999E3>"); | |
90 | fq.setToDouble(987.654321); | |
91 | assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>"); | |
92 | fq.roundToInfinity(); | |
93 | assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>"); | |
94 | fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, 3, status); | |
95 | assertSuccess("Rounding to increment", status); | |
96 | assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987655E-3>"); | |
97 | fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status); | |
98 | assertSuccess("Rounding to magnitude", status); | |
99 | assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 98766E-2>"); | |
100 | } | |
101 | ||
102 | void DecimalQuantityTest::testSwitchStorage() { | |
103 | UErrorCode status = U_ZERO_ERROR; | |
104 | DecimalQuantity fq; | |
105 | ||
106 | fq.setToLong(1234123412341234L); | |
107 | assertFalse("Should not be using byte array", fq.isUsingBytes()); | |
108 | assertEquals("Failed on initialize", u"1.234123412341234E+15", fq.toScientificString()); | |
109 | assertHealth(fq); | |
110 | // Long -> Bytes | |
111 | fq.appendDigit(5, 0, true); | |
112 | assertTrue("Should be using byte array", fq.isUsingBytes()); | |
113 | assertEquals("Failed on multiply", u"1.2341234123412345E+16", fq.toScientificString()); | |
114 | assertHealth(fq); | |
115 | // Bytes -> Long | |
116 | fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status); | |
117 | assertSuccess("Rounding to magnitude", status); | |
118 | assertFalse("Should not be using byte array", fq.isUsingBytes()); | |
119 | assertEquals("Failed on round", u"1.23412341234E+16", fq.toScientificString()); | |
120 | assertHealth(fq); | |
121 | } | |
122 | ||
123 | void DecimalQuantityTest::testCopyMove() { | |
124 | // Small numbers (fits in BCD long) | |
125 | { | |
126 | DecimalQuantity a; | |
127 | a.setToLong(1234123412341234L); | |
128 | DecimalQuantity b = a; // copy constructor | |
129 | assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>"); | |
130 | assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>"); | |
131 | DecimalQuantity c(std::move(a)); // move constructor | |
132 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>"); | |
133 | c.setToLong(54321L); | |
134 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 54321E0>"); | |
135 | c = b; // copy assignment | |
136 | assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>"); | |
137 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>"); | |
138 | b.setToLong(45678); | |
139 | c.setToLong(56789); | |
140 | c = std::move(b); // move assignment | |
141 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 45678E0>"); | |
142 | a = std::move(c); // move assignment to a defunct object | |
143 | assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 45678E0>"); | |
144 | } | |
145 | ||
146 | // Large numbers (requires byte allocation) | |
147 | { | |
148 | IcuTestErrorCode status(*this, "testCopyMove"); | |
149 | DecimalQuantity a; | |
150 | a.setToDecNumber({"1234567890123456789", -1}, status); | |
151 | DecimalQuantity b = a; // copy constructor | |
152 | assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>"); | |
153 | assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>"); | |
154 | DecimalQuantity c(std::move(a)); // move constructor | |
155 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>"); | |
156 | c.setToDecNumber({"9876543210987654321", -1}, status); | |
157 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 9876543210987654321E0>"); | |
158 | c = b; // copy assignment | |
159 | assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>"); | |
160 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>"); | |
161 | b.setToDecNumber({"876543210987654321", -1}, status); | |
162 | c.setToDecNumber({"987654321098765432", -1}, status); | |
163 | c = std::move(b); // move assignment | |
164 | assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>"); | |
165 | a = std::move(c); // move assignment to a defunct object | |
166 | assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>"); | |
167 | } | |
168 | } | |
169 | ||
170 | void DecimalQuantityTest::testAppend() { | |
171 | DecimalQuantity fq; | |
172 | fq.appendDigit(1, 0, true); | |
173 | assertEquals("Failed on append", u"1E+0", fq.toScientificString()); | |
174 | assertHealth(fq); | |
175 | fq.appendDigit(2, 0, true); | |
176 | assertEquals("Failed on append", u"1.2E+1", fq.toScientificString()); | |
177 | assertHealth(fq); | |
178 | fq.appendDigit(3, 1, true); | |
179 | assertEquals("Failed on append", u"1.203E+3", fq.toScientificString()); | |
180 | assertHealth(fq); | |
181 | fq.appendDigit(0, 1, true); | |
182 | assertEquals("Failed on append", u"1.203E+5", fq.toScientificString()); | |
183 | assertHealth(fq); | |
184 | fq.appendDigit(4, 0, true); | |
185 | assertEquals("Failed on append", u"1.203004E+6", fq.toScientificString()); | |
186 | assertHealth(fq); | |
187 | fq.appendDigit(0, 0, true); | |
188 | assertEquals("Failed on append", u"1.203004E+7", fq.toScientificString()); | |
189 | assertHealth(fq); | |
190 | fq.appendDigit(5, 0, false); | |
191 | assertEquals("Failed on append", u"1.20300405E+7", fq.toScientificString()); | |
192 | assertHealth(fq); | |
193 | fq.appendDigit(6, 0, false); | |
194 | assertEquals("Failed on append", u"1.203004056E+7", fq.toScientificString()); | |
195 | assertHealth(fq); | |
196 | fq.appendDigit(7, 3, false); | |
197 | assertEquals("Failed on append", u"1.2030040560007E+7", fq.toScientificString()); | |
198 | assertHealth(fq); | |
199 | UnicodeString baseExpected(u"1.2030040560007"); | |
200 | for (int i = 0; i < 10; i++) { | |
201 | fq.appendDigit(8, 0, false); | |
202 | baseExpected.append(u'8'); | |
203 | UnicodeString expected(baseExpected); | |
204 | expected.append(u"E+7"); | |
205 | assertEquals("Failed on append", expected, fq.toScientificString()); | |
206 | assertHealth(fq); | |
207 | } | |
208 | fq.appendDigit(9, 2, false); | |
209 | baseExpected.append(u"009"); | |
210 | UnicodeString expected(baseExpected); | |
211 | expected.append(u"E+7"); | |
212 | assertEquals("Failed on append", expected, fq.toScientificString()); | |
213 | assertHealth(fq); | |
214 | } | |
215 | ||
216 | void DecimalQuantityTest::testConvertToAccurateDouble() { | |
217 | // based on https://github.com/google/double-conversion/issues/28 | |
218 | static double hardDoubles[] = { | |
219 | 1651087494906221570.0, | |
220 | -5074790912492772E-327, | |
221 | 83602530019752571E-327, | |
222 | 2.207817077636718750000000000000, | |
223 | 1.818351745605468750000000000000, | |
224 | 3.941719055175781250000000000000, | |
225 | 3.738609313964843750000000000000, | |
226 | 3.967735290527343750000000000000, | |
227 | 1.328025817871093750000000000000, | |
228 | 3.920967102050781250000000000000, | |
229 | 1.015235900878906250000000000000, | |
230 | 1.335227966308593750000000000000, | |
231 | 1.344520568847656250000000000000, | |
232 | 2.879127502441406250000000000000, | |
233 | 3.695838928222656250000000000000, | |
234 | 1.845344543457031250000000000000, | |
235 | 3.793952941894531250000000000000, | |
236 | 3.211402893066406250000000000000, | |
237 | 2.565971374511718750000000000000, | |
238 | 0.965156555175781250000000000000, | |
239 | 2.700004577636718750000000000000, | |
240 | 0.767097473144531250000000000000, | |
241 | 1.780448913574218750000000000000, | |
242 | 2.624839782714843750000000000000, | |
243 | 1.305290222167968750000000000000, | |
244 | 3.834922790527343750000000000000,}; | |
245 | ||
246 | static double integerDoubles[] = { | |
247 | 51423, | |
248 | 51423e10, | |
249 | 4.503599627370496E15, | |
250 | 6.789512076111555E15, | |
251 | 9.007199254740991E15, | |
252 | 9.007199254740992E15}; | |
253 | ||
254 | for (double d : hardDoubles) { | |
255 | checkDoubleBehavior(d, true); | |
256 | } | |
257 | ||
258 | for (double d : integerDoubles) { | |
259 | checkDoubleBehavior(d, false); | |
260 | } | |
261 | ||
262 | assertDoubleEquals(u"NaN check failed", NAN, DecimalQuantity().setToDouble(NAN).toDouble()); | |
263 | assertDoubleEquals( | |
264 | u"Inf check failed", INFINITY, DecimalQuantity().setToDouble(INFINITY).toDouble()); | |
265 | assertDoubleEquals( | |
266 | u"-Inf check failed", -INFINITY, DecimalQuantity().setToDouble(-INFINITY).toDouble()); | |
267 | ||
268 | // Generate random doubles | |
269 | for (int32_t i = 0; i < 10000; i++) { | |
270 | uint8_t bytes[8]; | |
271 | for (int32_t j = 0; j < 8; j++) { | |
272 | bytes[j] = static_cast<uint8_t>(rand() % 256); | |
273 | } | |
274 | double d; | |
275 | uprv_memcpy(&d, bytes, 8); | |
276 | if (std::isnan(d) || !std::isfinite(d)) { continue; } | |
277 | checkDoubleBehavior(d, false); | |
278 | } | |
279 | } | |
280 | ||
281 | void DecimalQuantityTest::testUseApproximateDoubleWhenAble() { | |
282 | static const struct TestCase { | |
283 | double d; | |
284 | int32_t maxFrac; | |
285 | RoundingMode roundingMode; | |
286 | bool usesExact; | |
287 | } cases[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false}, | |
288 | {1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN, false}, | |
289 | {1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN, false}, | |
290 | {1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN, true}, | |
291 | {1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false}, | |
292 | {1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN, true}, | |
293 | {1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN, false}, | |
294 | {1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN, false}, | |
295 | {1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING, true}, | |
296 | {1.235, 1, RoundingMode::UNUM_ROUND_CEILING, false}, | |
297 | {1.235, 2, RoundingMode::UNUM_ROUND_CEILING, false}, | |
298 | {1.235, 3, RoundingMode::UNUM_ROUND_CEILING, true}}; | |
299 | ||
300 | UErrorCode status = U_ZERO_ERROR; | |
301 | for (TestCase cas : cases) { | |
302 | DecimalQuantity fq; | |
303 | fq.setToDouble(cas.d); | |
304 | assertTrue("Should be using approximate double", !fq.isExplicitExactDouble()); | |
305 | fq.roundToMagnitude(-cas.maxFrac, cas.roundingMode, status); | |
306 | assertSuccess("Rounding to magnitude", status); | |
307 | if (cas.usesExact != fq.isExplicitExactDouble()) { | |
308 | errln(UnicodeString(u"Using approximate double after rounding: ") + fq.toString()); | |
309 | } | |
310 | } | |
311 | } | |
312 | ||
313 | void DecimalQuantityTest::testHardDoubleConversion() { | |
314 | static const struct TestCase { | |
315 | double input; | |
316 | const char16_t* expectedOutput; | |
317 | } cases[] = { | |
318 | { 512.0000000000017, u"512.0000000000017" }, | |
319 | { 4095.9999999999977, u"4095.9999999999977" }, | |
320 | { 4095.999999999998, u"4095.999999999998" }, | |
321 | { 4095.9999999999986, u"4095.9999999999986" }, | |
322 | { 4095.999999999999, u"4095.999999999999" }, | |
323 | { 4095.9999999999995, u"4095.9999999999995" }, | |
324 | { 4096.000000000001, u"4096.000000000001" }, | |
325 | { 4096.000000000002, u"4096.000000000002" }, | |
326 | { 4096.000000000003, u"4096.000000000003" }, | |
327 | { 4096.000000000004, u"4096.000000000004" }, | |
328 | { 4096.000000000005, u"4096.000000000005" }, | |
329 | { 4096.0000000000055, u"4096.0000000000055" }, | |
330 | { 4096.000000000006, u"4096.000000000006" }, | |
331 | { 4096.000000000007, u"4096.000000000007" } }; | |
332 | ||
333 | for (auto& cas : cases) { | |
334 | DecimalQuantity q; | |
335 | q.setToDouble(cas.input); | |
336 | q.roundToInfinity(); | |
337 | UnicodeString actualOutput = q.toPlainString(); | |
338 | assertEquals("", cas.expectedOutput, actualOutput); | |
339 | } | |
340 | } | |
341 | ||
342 | void DecimalQuantityTest::testToDouble() { | |
343 | IcuTestErrorCode status(*this, "testToDouble"); | |
344 | static const struct TestCase { | |
345 | const char* input; // char* for the decNumber constructor | |
346 | double expected; | |
347 | } cases[] = { | |
348 | { "0", 0.0 }, | |
349 | { "514.23", 514.23 }, | |
350 | { "-3.142E-271", -3.142e-271 } }; | |
351 | ||
352 | for (auto& cas : cases) { | |
353 | status.setScope(cas.input); | |
354 | DecimalQuantity q; | |
355 | q.setToDecNumber({cas.input, -1}, status); | |
356 | double actual = q.toDouble(); | |
357 | assertEquals("Doubles should exactly equal", cas.expected, actual); | |
358 | } | |
359 | } | |
360 | ||
361 | void DecimalQuantityTest::testMaxDigits() { | |
362 | IcuTestErrorCode status(*this, "testMaxDigits"); | |
363 | DecimalQuantity dq; | |
364 | dq.setToDouble(876.543); | |
365 | dq.roundToInfinity(); | |
366 | dq.setIntegerLength(0, 2); | |
367 | dq.setFractionLength(0, 2); | |
368 | assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString()); | |
369 | assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString()); | |
370 | assertEquals("Should trim, toLong", 76LL, dq.toLong(true)); | |
371 | assertEquals("Should trim, toFractionLong", (int64_t) 54, (int64_t) dq.toFractionLong(false)); | |
372 | assertEquals("Should trim, toDouble", 76.54, dq.toDouble()); | |
373 | // To test DecNum output, check the round-trip. | |
374 | DecNum dn; | |
375 | dq.toDecNum(dn, status); | |
376 | DecimalQuantity copy; | |
377 | copy.setToDecNum(dn, status); | |
378 | if (!logKnownIssue("13701")) { | |
379 | assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString()); | |
380 | } | |
381 | } | |
382 | ||
383 | #endif /* #if !UCONFIG_NO_FORMATTING */ |