]> git.saurik.com Git - apple/icu.git/blob - icuSources/test/intltest/numbertest_decimalquantity.cpp
ICU-62107.0.1.tar.gz
[apple/icu.git] / icuSources / test / intltest / numbertest_decimalquantity.cpp
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 */