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
);
24 TESTCASE_AUTO(testConvertToAccurateDouble
);
25 TESTCASE_AUTO(testUseApproximateDoubleWhenAble
);
26 TESTCASE_AUTO(testHardDoubleConversion
);
27 TESTCASE_AUTO(testToDouble
);
28 TESTCASE_AUTO(testMaxDigits
);
32 void DecimalQuantityTest::assertDoubleEquals(UnicodeString message
, double a
, double b
) {
38 diff
= diff
< 0 ? -diff
: diff
;
39 double bound
= a
< 0 ? -a
* 1e-6 : a
* 1e-6;
41 errln(message
+ u
": " + DoubleToUnicodeString(a
) + u
" vs " + DoubleToUnicodeString(b
) + u
" differ by " + DoubleToUnicodeString(diff
));
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());
53 DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity
&fq
, const UnicodeString
&expected
) {
54 UnicodeString actual
= fq
.toString();
55 assertEquals("DecimalQuantity toString failed", expected
, actual
);
59 void DecimalQuantityTest::checkDoubleBehavior(double d
, bool explicitRequired
) {
62 if (explicitRequired
) {
63 assertTrue("Should be using approximate double", !fq
.isExplicitExactDouble());
65 UnicodeString baseStr
= fq
.toString();
67 UnicodeString newStr
= fq
.toString();
68 if (explicitRequired
) {
69 assertTrue("Should not be using approximate double", fq
.isExplicitExactDouble());
72 UnicodeString(u
"After conversion to exact BCD (double): ") + baseStr
+ u
" vs " + newStr
,
76 void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
77 UErrorCode status
= U_ZERO_ERROR
;
79 assertToStringAndHealth(fq
, u
"<DecimalQuantity 999:0:0:-999 long 0E0>");
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>");
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>");
102 void DecimalQuantityTest::testSwitchStorage() {
103 UErrorCode status
= U_ZERO_ERROR
;
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());
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());
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());
123 void DecimalQuantityTest::testCopyMove() {
124 // Small numbers (fits in BCD long)
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>");
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>");
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>");
146 // Large numbers (requires byte allocation)
148 IcuTestErrorCode
status(*this, "testCopyMove");
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>");
170 void DecimalQuantityTest::testAppend() {
172 fq
.appendDigit(1, 0, true);
173 assertEquals("Failed on append", u
"1E+0", fq
.toScientificString());
175 fq
.appendDigit(2, 0, true);
176 assertEquals("Failed on append", u
"1.2E+1", fq
.toScientificString());
178 fq
.appendDigit(3, 1, true);
179 assertEquals("Failed on append", u
"1.203E+3", fq
.toScientificString());
181 fq
.appendDigit(0, 1, true);
182 assertEquals("Failed on append", u
"1.203E+5", fq
.toScientificString());
184 fq
.appendDigit(4, 0, true);
185 assertEquals("Failed on append", u
"1.203004E+6", fq
.toScientificString());
187 fq
.appendDigit(0, 0, true);
188 assertEquals("Failed on append", u
"1.203004E+7", fq
.toScientificString());
190 fq
.appendDigit(5, 0, false);
191 assertEquals("Failed on append", u
"1.20300405E+7", fq
.toScientificString());
193 fq
.appendDigit(6, 0, false);
194 assertEquals("Failed on append", u
"1.203004056E+7", fq
.toScientificString());
196 fq
.appendDigit(7, 3, false);
197 assertEquals("Failed on append", u
"1.2030040560007E+7", fq
.toScientificString());
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());
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());
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,};
246 static double integerDoubles
[] = {
249 4.503599627370496E15
,
250 6.789512076111555E15
,
251 9.007199254740991E15
,
252 9.007199254740992E15
};
254 for (double d
: hardDoubles
) {
255 checkDoubleBehavior(d
, true);
258 for (double d
: integerDoubles
) {
259 checkDoubleBehavior(d
, false);
262 assertDoubleEquals(u
"NaN check failed", NAN
, DecimalQuantity().setToDouble(NAN
).toDouble());
264 u
"Inf check failed", INFINITY
, DecimalQuantity().setToDouble(INFINITY
).toDouble());
266 u
"-Inf check failed", -INFINITY
, DecimalQuantity().setToDouble(-INFINITY
).toDouble());
268 // Generate random doubles
269 for (int32_t i
= 0; i
< 10000; i
++) {
271 for (int32_t j
= 0; j
< 8; j
++) {
272 bytes
[j
] = static_cast<uint8_t>(rand() % 256);
275 uprv_memcpy(&d
, bytes
, 8);
276 if (std::isnan(d
) || !std::isfinite(d
)) { continue; }
277 checkDoubleBehavior(d
, false);
281 void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
282 static const struct TestCase
{
285 RoundingMode roundingMode
;
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}};
300 UErrorCode status
= U_ZERO_ERROR
;
301 for (TestCase cas
: cases
) {
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());
313 void DecimalQuantityTest::testHardDoubleConversion() {
314 static const struct TestCase
{
316 const char16_t* expectedOutput
;
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" } };
333 for (auto& cas
: cases
) {
335 q
.setToDouble(cas
.input
);
337 UnicodeString actualOutput
= q
.toPlainString();
338 assertEquals("", cas
.expectedOutput
, actualOutput
);
342 void DecimalQuantityTest::testToDouble() {
343 IcuTestErrorCode
status(*this, "testToDouble");
344 static const struct TestCase
{
345 const char* input
; // char* for the decNumber constructor
349 { "514.23", 514.23 },
350 { "-3.142E-271", -3.142e-271 } };
352 for (auto& cas
: cases
) {
353 status
.setScope(cas
.input
);
355 q
.setToDecNumber({cas
.input
, -1}, status
);
356 double actual
= q
.toDouble();
357 assertEquals("Doubles should exactly equal", cas
.expected
, actual
);
361 void DecimalQuantityTest::testMaxDigits() {
362 IcuTestErrorCode
status(*this, "testMaxDigits");
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.
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());
383 #endif /* #if !UCONFIG_NO_FORMATTING */