1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 #include "unicode/utypes.h"
6 #if !UCONFIG_NO_FORMATTING
8 #include "number_decimalquantity.h"
9 #include "number_decnum.h"
12 #include "number_utils.h"
13 #include "numbertest.h"
15 void DecimalQuantityTest::runIndexedTest(int32_t index
, UBool exec
, const char *&name
, char *) {
17 logln("TestSuite DecimalQuantityTest: ");
20 TESTCASE_AUTO(testDecimalQuantityBehaviorStandalone
);
21 TESTCASE_AUTO(testSwitchStorage
);;
22 TESTCASE_AUTO(testCopyMove
);
23 TESTCASE_AUTO(testAppend
);
25 // Slow test: run in exhaustive mode only
26 TESTCASE_AUTO(testConvertToAccurateDouble
);
28 TESTCASE_AUTO(testUseApproximateDoubleWhenAble
);
29 TESTCASE_AUTO(testHardDoubleConversion
);
30 TESTCASE_AUTO(testToDouble
);
31 TESTCASE_AUTO(testMaxDigits
);
32 TESTCASE_AUTO(testNickelRounding
);
36 void DecimalQuantityTest::assertDoubleEquals(UnicodeString message
, double a
, double b
) {
42 diff
= diff
< 0 ? -diff
: diff
;
43 double bound
= a
< 0 ? -a
* 1e-6 : a
* 1e-6;
45 errln(message
+ u
": " + DoubleToUnicodeString(a
) + u
" vs " + DoubleToUnicodeString(b
) + u
" differ by " + DoubleToUnicodeString(diff
));
49 void DecimalQuantityTest::assertHealth(const DecimalQuantity
&fq
) {
50 const char16_t* health
= fq
.checkHealth();
51 if (health
!= nullptr) {
52 errln(UnicodeString(u
"HEALTH FAILURE: ") + UnicodeString(health
) + u
": " + fq
.toString());
57 DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity
&fq
, const UnicodeString
&expected
) {
58 UnicodeString actual
= fq
.toString();
59 assertEquals("DecimalQuantity toString failed", expected
, actual
);
63 void DecimalQuantityTest::checkDoubleBehavior(double d
, bool explicitRequired
) {
66 if (explicitRequired
) {
67 assertTrue("Should be using approximate double", !fq
.isExplicitExactDouble());
69 UnicodeString baseStr
= fq
.toString();
71 UnicodeString newStr
= fq
.toString();
72 if (explicitRequired
) {
73 assertTrue("Should not be using approximate double", fq
.isExplicitExactDouble());
76 UnicodeString(u
"After conversion to exact BCD (double): ") + baseStr
+ u
" vs " + newStr
,
80 void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
81 UErrorCode status
= U_ZERO_ERROR
;
83 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 long 0E0>");
85 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 long 51423E0>");
86 fq
.adjustMagnitude(-3);
87 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 long 51423E-3>");
89 fq
.setToLong(90909090909000L);
90 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 long 90909090909E3>");
92 fq
.applyMaxInteger(5);
93 assertToStringAndHealth(fq
, u
"<DecimalQuantity 2:0 long 9E3>");
95 assertToStringAndHealth(fq
, u
"<DecimalQuantity 2:-3 long 9E3>");
97 fq
.setToDouble(987.654321);
98 assertToStringAndHealth(fq
, u
"<DecimalQuantity 2:-3 long 987654321E-6>");
100 assertToStringAndHealth(fq
, u
"<DecimalQuantity 2:-3 long 987654321E-6>");
101 fq
.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN
, status
);
102 assertSuccess("Rounding to increment", status
);
103 assertToStringAndHealth(fq
, u
"<DecimalQuantity 2:-3 long 987655E-3>");
104 fq
.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN
, status
);
105 assertSuccess("Rounding to magnitude", status
);
106 assertToStringAndHealth(fq
, u
"<DecimalQuantity 2:-3 long 98766E-2>");
109 void DecimalQuantityTest::testSwitchStorage() {
110 UErrorCode status
= U_ZERO_ERROR
;
113 fq
.setToLong(1234123412341234L);
114 assertFalse("Should not be using byte array", fq
.isUsingBytes());
115 assertEquals("Failed on initialize", u
"1.234123412341234E+15", fq
.toScientificString());
118 fq
.appendDigit(5, 0, true);
119 assertTrue("Should be using byte array", fq
.isUsingBytes());
120 assertEquals("Failed on multiply", u
"1.2341234123412345E+16", fq
.toScientificString());
123 fq
.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN
, status
);
124 assertSuccess("Rounding to magnitude", status
);
125 assertFalse("Should not be using byte array", fq
.isUsingBytes());
126 assertEquals("Failed on round", u
"1.23412341234E+16", fq
.toScientificString());
128 // Bytes with popFromLeft
129 fq
.setToDecNumber({"999999999999999999"}, status
);
130 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 bytes 999999999999999999E0>");
131 fq
.applyMaxInteger(17);
132 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 bytes 99999999999999999E0>");
133 fq
.applyMaxInteger(16);
134 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 long 9999999999999999E0>");
135 fq
.applyMaxInteger(15);
136 assertToStringAndHealth(fq
, u
"<DecimalQuantity 0:0 long 999999999999999E0>");
139 void DecimalQuantityTest::testCopyMove() {
140 // Small numbers (fits in BCD long)
143 a
.setToLong(1234123412341234L);
144 DecimalQuantity b
= a
; // copy constructor
145 assertToStringAndHealth(a
, u
"<DecimalQuantity 0:0 long 1234123412341234E0>");
146 assertToStringAndHealth(b
, u
"<DecimalQuantity 0:0 long 1234123412341234E0>");
147 DecimalQuantity
c(std::move(a
)); // move constructor
148 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 long 1234123412341234E0>");
150 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 long 54321E0>");
151 c
= b
; // copy assignment
152 assertToStringAndHealth(b
, u
"<DecimalQuantity 0:0 long 1234123412341234E0>");
153 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 long 1234123412341234E0>");
156 c
= std::move(b
); // move assignment
157 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 long 45678E0>");
158 a
= std::move(c
); // move assignment to a defunct object
159 assertToStringAndHealth(a
, u
"<DecimalQuantity 0:0 long 45678E0>");
162 // Large numbers (requires byte allocation)
164 IcuTestErrorCode
status(*this, "testCopyMove");
166 a
.setToDecNumber({"1234567890123456789", -1}, status
);
167 DecimalQuantity b
= a
; // copy constructor
168 assertToStringAndHealth(a
, u
"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
169 assertToStringAndHealth(b
, u
"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
170 DecimalQuantity
c(std::move(a
)); // move constructor
171 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
172 c
.setToDecNumber({"9876543210987654321", -1}, status
);
173 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 bytes 9876543210987654321E0>");
174 c
= b
; // copy assignment
175 assertToStringAndHealth(b
, u
"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
176 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
177 b
.setToDecNumber({"876543210987654321", -1}, status
);
178 c
.setToDecNumber({"987654321098765432", -1}, status
);
179 c
= std::move(b
); // move assignment
180 assertToStringAndHealth(c
, u
"<DecimalQuantity 0:0 bytes 876543210987654321E0>");
181 a
= std::move(c
); // move assignment to a defunct object
182 assertToStringAndHealth(a
, u
"<DecimalQuantity 0:0 bytes 876543210987654321E0>");
186 void DecimalQuantityTest::testAppend() {
188 fq
.appendDigit(1, 0, true);
189 assertEquals("Failed on append", u
"1E+0", fq
.toScientificString());
191 fq
.appendDigit(2, 0, true);
192 assertEquals("Failed on append", u
"1.2E+1", fq
.toScientificString());
194 fq
.appendDigit(3, 1, true);
195 assertEquals("Failed on append", u
"1.203E+3", fq
.toScientificString());
197 fq
.appendDigit(0, 1, true);
198 assertEquals("Failed on append", u
"1.203E+5", fq
.toScientificString());
200 fq
.appendDigit(4, 0, true);
201 assertEquals("Failed on append", u
"1.203004E+6", fq
.toScientificString());
203 fq
.appendDigit(0, 0, true);
204 assertEquals("Failed on append", u
"1.203004E+7", fq
.toScientificString());
206 fq
.appendDigit(5, 0, false);
207 assertEquals("Failed on append", u
"1.20300405E+7", fq
.toScientificString());
209 fq
.appendDigit(6, 0, false);
210 assertEquals("Failed on append", u
"1.203004056E+7", fq
.toScientificString());
212 fq
.appendDigit(7, 3, false);
213 assertEquals("Failed on append", u
"1.2030040560007E+7", fq
.toScientificString());
215 UnicodeString
baseExpected(u
"1.2030040560007");
216 for (int i
= 0; i
< 10; i
++) {
217 fq
.appendDigit(8, 0, false);
218 baseExpected
.append(u
'8');
219 UnicodeString
expected(baseExpected
);
220 expected
.append(u
"E+7");
221 assertEquals("Failed on append", expected
, fq
.toScientificString());
224 fq
.appendDigit(9, 2, false);
225 baseExpected
.append(u
"009");
226 UnicodeString
expected(baseExpected
);
227 expected
.append(u
"E+7");
228 assertEquals("Failed on append", expected
, fq
.toScientificString());
232 void DecimalQuantityTest::testConvertToAccurateDouble() {
233 // based on https://github.com/google/double-conversion/issues/28
234 static double hardDoubles
[] = {
235 1651087494906221570.0,
236 -5074790912492772E-327,
237 83602530019752571E-327,
238 2.207817077636718750000000000000,
239 1.818351745605468750000000000000,
240 3.941719055175781250000000000000,
241 3.738609313964843750000000000000,
242 3.967735290527343750000000000000,
243 1.328025817871093750000000000000,
244 3.920967102050781250000000000000,
245 1.015235900878906250000000000000,
246 1.335227966308593750000000000000,
247 1.344520568847656250000000000000,
248 2.879127502441406250000000000000,
249 3.695838928222656250000000000000,
250 1.845344543457031250000000000000,
251 3.793952941894531250000000000000,
252 3.211402893066406250000000000000,
253 2.565971374511718750000000000000,
254 0.965156555175781250000000000000,
255 2.700004577636718750000000000000,
256 0.767097473144531250000000000000,
257 1.780448913574218750000000000000,
258 2.624839782714843750000000000000,
259 1.305290222167968750000000000000,
260 3.834922790527343750000000000000,};
262 static double integerDoubles
[] = {
265 4.503599627370496E15
,
266 6.789512076111555E15
,
267 9.007199254740991E15
,
268 9.007199254740992E15
};
270 for (double d
: hardDoubles
) {
271 checkDoubleBehavior(d
, true);
274 for (double d
: integerDoubles
) {
275 checkDoubleBehavior(d
, false);
278 assertDoubleEquals(u
"NaN check failed", NAN
, DecimalQuantity().setToDouble(NAN
).toDouble());
280 u
"Inf check failed", INFINITY
, DecimalQuantity().setToDouble(INFINITY
).toDouble());
282 u
"-Inf check failed", -INFINITY
, DecimalQuantity().setToDouble(-INFINITY
).toDouble());
284 // Generate random doubles
285 for (int32_t i
= 0; i
< 10000; i
++) {
287 for (int32_t j
= 0; j
< 8; j
++) {
288 bytes
[j
] = static_cast<uint8_t>(rand() % 256);
291 uprv_memcpy(&d
, bytes
, 8);
292 if (std::isnan(d
) || !std::isfinite(d
)) { continue; }
293 checkDoubleBehavior(d
, false);
297 void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
298 static const struct TestCase
{
301 RoundingMode roundingMode
;
303 } cases
[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN
, false},
304 {1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN
, false},
305 {1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN
, false},
306 {1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN
, true},
307 {1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN
, false},
308 {1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN
, true},
309 {1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN
, false},
310 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN
, false},
311 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING
, true},
312 {1.235, 1, RoundingMode::UNUM_ROUND_CEILING
, false},
313 {1.235, 2, RoundingMode::UNUM_ROUND_CEILING
, false},
314 {1.235, 3, RoundingMode::UNUM_ROUND_CEILING
, true}};
316 UErrorCode status
= U_ZERO_ERROR
;
317 for (TestCase cas
: cases
) {
319 fq
.setToDouble(cas
.d
);
320 assertTrue("Should be using approximate double", !fq
.isExplicitExactDouble());
321 fq
.roundToMagnitude(-cas
.maxFrac
, cas
.roundingMode
, status
);
322 assertSuccess("Rounding to magnitude", status
);
323 if (cas
.usesExact
!= fq
.isExplicitExactDouble()) {
324 errln(UnicodeString(u
"Using approximate double after rounding: ") + fq
.toString());
329 void DecimalQuantityTest::testHardDoubleConversion() {
330 static const struct TestCase
{
332 const char16_t* expectedOutput
;
334 { 512.0000000000017, u
"512.0000000000017" },
335 { 4095.9999999999977, u
"4095.9999999999977" },
336 { 4095.999999999998, u
"4095.999999999998" },
337 { 4095.9999999999986, u
"4095.9999999999986" },
338 { 4095.999999999999, u
"4095.999999999999" },
339 { 4095.9999999999995, u
"4095.9999999999995" },
340 { 4096.000000000001, u
"4096.000000000001" },
341 { 4096.000000000002, u
"4096.000000000002" },
342 { 4096.000000000003, u
"4096.000000000003" },
343 { 4096.000000000004, u
"4096.000000000004" },
344 { 4096.000000000005, u
"4096.000000000005" },
345 { 4096.0000000000055, u
"4096.0000000000055" },
346 { 4096.000000000006, u
"4096.000000000006" },
347 { 4096.000000000007, u
"4096.000000000007" } };
349 for (auto& cas
: cases
) {
351 q
.setToDouble(cas
.input
);
353 UnicodeString actualOutput
= q
.toPlainString();
354 assertEquals("", cas
.expectedOutput
, actualOutput
);
358 void DecimalQuantityTest::testToDouble() {
359 IcuTestErrorCode
status(*this, "testToDouble");
360 static const struct TestCase
{
361 const char* input
; // char* for the decNumber constructor
365 { "514.23", 514.23 },
366 { "-3.142E-271", -3.142e-271 } };
368 for (auto& cas
: cases
) {
369 status
.setScope(cas
.input
);
371 q
.setToDecNumber({cas
.input
, -1}, status
);
372 double actual
= q
.toDouble();
373 assertEquals("Doubles should exactly equal", cas
.expected
, actual
);
377 void DecimalQuantityTest::testMaxDigits() {
378 IcuTestErrorCode
status(*this, "testMaxDigits");
380 dq
.setToDouble(876.543);
381 dq
.roundToInfinity();
383 dq
.applyMaxInteger(2);
384 dq
.setMinFraction(0);
385 dq
.roundToMagnitude(-2, UNUM_ROUND_FLOOR
, status
);
386 assertEquals("Should trim, toPlainString", "76.54", dq
.toPlainString());
387 assertEquals("Should trim, toScientificString", "7.654E+1", dq
.toScientificString());
388 assertEquals("Should trim, toLong", 76LL, dq
.toLong(true));
389 assertEquals("Should trim, toFractionLong", (int64_t) 54, (int64_t) dq
.toFractionLong(false));
390 assertEquals("Should trim, toDouble", 76.54, dq
.toDouble());
391 // To test DecNum output, check the round-trip.
393 dq
.toDecNum(dn
, status
);
394 DecimalQuantity copy
;
395 copy
.setToDecNum(dn
, status
);
396 assertEquals("Should trim, toDecNum", "76.54", copy
.toPlainString());
399 void DecimalQuantityTest::testNickelRounding() {
400 IcuTestErrorCode
status(*this, "testNickelRounding");
404 UNumberFormatRoundingMode roundingMode
;
405 const char16_t* expected
;
407 {1.000, -2, UNUM_ROUND_HALFEVEN
, u
"1"},
408 {1.001, -2, UNUM_ROUND_HALFEVEN
, u
"1"},
409 {1.010, -2, UNUM_ROUND_HALFEVEN
, u
"1"},
410 {1.020, -2, UNUM_ROUND_HALFEVEN
, u
"1"},
411 {1.024, -2, UNUM_ROUND_HALFEVEN
, u
"1"},
412 {1.025, -2, UNUM_ROUND_HALFEVEN
, u
"1"},
413 {1.025, -2, UNUM_ROUND_HALFDOWN
, u
"1"},
414 {1.025, -2, UNUM_ROUND_HALFUP
, u
"1.05"},
415 {1.026, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
416 {1.030, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
417 {1.040, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
418 {1.050, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
419 {1.060, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
420 {1.070, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
421 {1.074, -2, UNUM_ROUND_HALFEVEN
, u
"1.05"},
422 {1.075, -2, UNUM_ROUND_HALFDOWN
, u
"1.05"},
423 {1.075, -2, UNUM_ROUND_HALFUP
, u
"1.1"},
424 {1.075, -2, UNUM_ROUND_HALFEVEN
, u
"1.1"},
425 {1.076, -2, UNUM_ROUND_HALFEVEN
, u
"1.1"},
426 {1.080, -2, UNUM_ROUND_HALFEVEN
, u
"1.1"},
427 {1.090, -2, UNUM_ROUND_HALFEVEN
, u
"1.1"},
428 {1.099, -2, UNUM_ROUND_HALFEVEN
, u
"1.1"},
429 {1.999, -2, UNUM_ROUND_HALFEVEN
, u
"2"},
430 {2.25, -1, UNUM_ROUND_HALFEVEN
, u
"2"},
431 {2.25, -1, UNUM_ROUND_HALFUP
, u
"2.5"},
432 {2.75, -1, UNUM_ROUND_HALFDOWN
, u
"2.5"},
433 {2.75, -1, UNUM_ROUND_HALFEVEN
, u
"3"},
434 {3.00, -1, UNUM_ROUND_CEILING
, u
"3"},
435 {3.25, -1, UNUM_ROUND_CEILING
, u
"3.5"},
436 {3.50, -1, UNUM_ROUND_CEILING
, u
"3.5"},
437 {3.75, -1, UNUM_ROUND_CEILING
, u
"4"},
438 {4.00, -1, UNUM_ROUND_FLOOR
, u
"4"},
439 {4.25, -1, UNUM_ROUND_FLOOR
, u
"4"},
440 {4.50, -1, UNUM_ROUND_FLOOR
, u
"4.5"},
441 {4.75, -1, UNUM_ROUND_FLOOR
, u
"4.5"},
442 {5.00, -1, UNUM_ROUND_UP
, u
"5"},
443 {5.25, -1, UNUM_ROUND_UP
, u
"5.5"},
444 {5.50, -1, UNUM_ROUND_UP
, u
"5.5"},
445 {5.75, -1, UNUM_ROUND_UP
, u
"6"},
446 {6.00, -1, UNUM_ROUND_DOWN
, u
"6"},
447 {6.25, -1, UNUM_ROUND_DOWN
, u
"6"},
448 {6.50, -1, UNUM_ROUND_DOWN
, u
"6.5"},
449 {6.75, -1, UNUM_ROUND_DOWN
, u
"6.5"},
450 {7.00, -1, UNUM_ROUND_UNNECESSARY
, u
"7"},
451 {7.50, -1, UNUM_ROUND_UNNECESSARY
, u
"7.5"},
453 for (const auto& cas
: cases
) {
454 UnicodeString message
= DoubleToUnicodeString(cas
.input
) + u
" @ " + Int64ToUnicodeString(cas
.magnitude
) + u
" / " + Int64ToUnicodeString(cas
.roundingMode
);
455 status
.setScope(message
);
457 dq
.setToDouble(cas
.input
);
458 dq
.roundToNickel(cas
.magnitude
, cas
.roundingMode
, status
);
459 status
.errIfFailureAndReset();
460 UnicodeString actual
= dq
.toPlainString();
461 assertEquals(message
, cas
.expected
, actual
);
466 dq
.roundToNickel(-1, UNUM_ROUND_UNNECESSARY
, status
);
467 status
.expectErrorAndReset(U_FORMAT_INEXACT_ERROR
);
470 #endif /* #if !UCONFIG_NO_FORMATTING */