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
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)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 if (!quick) {
25 // Slow test: run in exhaustive mode only
26 TESTCASE_AUTO(testConvertToAccurateDouble);
27 }
28 TESTCASE_AUTO(testUseApproximateDoubleWhenAble);
29 TESTCASE_AUTO(testHardDoubleConversion);
30 TESTCASE_AUTO(testToDouble);
31 TESTCASE_AUTO(testMaxDigits);
32 TESTCASE_AUTO(testNickelRounding);
33 TESTCASE_AUTO(testCompactDecimalSuppressedExponent);
34 TESTCASE_AUTO(testSuppressedExponentUnchangedByInitialScaling);
35 TESTCASE_AUTO_END;
36 }
37
assertDoubleEquals(UnicodeString message,double a,double b)38 void DecimalQuantityTest::assertDoubleEquals(UnicodeString message, double a, double b) {
39 if (a == b) {
40 return;
41 }
42
43 double diff = a - b;
44 diff = diff < 0 ? -diff : diff;
45 double bound = a < 0 ? -a * 1e-6 : a * 1e-6;
46 if (diff > bound) {
47 errln(message + u": " + DoubleToUnicodeString(a) + u" vs " + DoubleToUnicodeString(b) + u" differ by " + DoubleToUnicodeString(diff));
48 }
49 }
50
assertHealth(const DecimalQuantity & fq)51 void DecimalQuantityTest::assertHealth(const DecimalQuantity &fq) {
52 const char16_t* health = fq.checkHealth();
53 if (health != nullptr) {
54 errln(UnicodeString(u"HEALTH FAILURE: ") + UnicodeString(health) + u": " + fq.toString());
55 }
56 }
57
58 void
assertToStringAndHealth(const DecimalQuantity & fq,const UnicodeString & expected)59 DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected) {
60 UnicodeString actual = fq.toString();
61 assertEquals("DecimalQuantity toString failed", expected, actual);
62 assertHealth(fq);
63 }
64
checkDoubleBehavior(double d,bool explicitRequired)65 void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) {
66 DecimalQuantity fq;
67 fq.setToDouble(d);
68 if (explicitRequired) {
69 assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
70 }
71 UnicodeString baseStr = fq.toString();
72 fq.roundToInfinity();
73 UnicodeString newStr = fq.toString();
74 if (explicitRequired) {
75 assertTrue("Should not be using approximate double", fq.isExplicitExactDouble());
76 }
77 assertDoubleEquals(
78 UnicodeString(u"After conversion to exact BCD (double): ") + baseStr + u" vs " + newStr,
79 d, fq.toDouble());
80 }
81
testDecimalQuantityBehaviorStandalone()82 void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
83 UErrorCode status = U_ZERO_ERROR;
84 DecimalQuantity fq;
85 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 0E0>");
86 fq.setToInt(51423);
87 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 51423E0>");
88 fq.adjustMagnitude(-3);
89 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 51423E-3>");
90
91 fq.setToLong(90909090909000L);
92 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 90909090909E3>");
93 fq.setMinInteger(2);
94 fq.applyMaxInteger(5);
95 assertToStringAndHealth(fq, u"<DecimalQuantity 2:0 long 9E3>");
96 fq.setMinFraction(3);
97 assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 9E3>");
98
99 fq.setToDouble(987.654321);
100 assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987654321E-6>");
101 fq.roundToInfinity();
102 assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987654321E-6>");
103 fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, status);
104 assertSuccess("Rounding to increment", status);
105 assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987655E-3>");
106 fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status);
107 assertSuccess("Rounding to magnitude", status);
108 assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 98766E-2>");
109 }
110
testSwitchStorage()111 void DecimalQuantityTest::testSwitchStorage() {
112 UErrorCode status = U_ZERO_ERROR;
113 DecimalQuantity fq;
114
115 fq.setToLong(1234123412341234L);
116 assertFalse("Should not be using byte array", fq.isUsingBytes());
117 assertEquals("Failed on initialize", u"1.234123412341234E+15", fq.toScientificString());
118 assertHealth(fq);
119 // Long -> Bytes
120 fq.appendDigit(5, 0, true);
121 assertTrue("Should be using byte array", fq.isUsingBytes());
122 assertEquals("Failed on multiply", u"1.2341234123412345E+16", fq.toScientificString());
123 assertHealth(fq);
124 // Bytes -> Long
125 fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status);
126 assertSuccess("Rounding to magnitude", status);
127 assertFalse("Should not be using byte array", fq.isUsingBytes());
128 assertEquals("Failed on round", u"1.23412341234E+16", fq.toScientificString());
129 assertHealth(fq);
130 // Bytes with popFromLeft
131 fq.setToDecNumber({"999999999999999999"}, status);
132 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 bytes 999999999999999999E0>");
133 fq.applyMaxInteger(17);
134 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 bytes 99999999999999999E0>");
135 fq.applyMaxInteger(16);
136 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 9999999999999999E0>");
137 fq.applyMaxInteger(15);
138 assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 999999999999999E0>");
139 }
140
testCopyMove()141 void DecimalQuantityTest::testCopyMove() {
142 // Small numbers (fits in BCD long)
143 {
144 DecimalQuantity a;
145 a.setToLong(1234123412341234L);
146 DecimalQuantity b = a; // copy constructor
147 assertToStringAndHealth(a, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
148 assertToStringAndHealth(b, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
149 DecimalQuantity c(std::move(a)); // move constructor
150 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
151 c.setToLong(54321L);
152 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 54321E0>");
153 c = b; // copy assignment
154 assertToStringAndHealth(b, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
155 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 1234123412341234E0>");
156 b.setToLong(45678);
157 c.setToLong(56789);
158 c = std::move(b); // move assignment
159 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 45678E0>");
160 a = std::move(c); // move assignment to a defunct object
161 assertToStringAndHealth(a, u"<DecimalQuantity 0:0 long 45678E0>");
162 }
163
164 // Large numbers (requires byte allocation)
165 {
166 IcuTestErrorCode status(*this, "testCopyMove");
167 DecimalQuantity a;
168 a.setToDecNumber({"1234567890123456789", -1}, status);
169 DecimalQuantity b = a; // copy constructor
170 assertToStringAndHealth(a, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
171 assertToStringAndHealth(b, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
172 DecimalQuantity c(std::move(a)); // move constructor
173 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
174 c.setToDecNumber({"9876543210987654321", -1}, status);
175 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 9876543210987654321E0>");
176 c = b; // copy assignment
177 assertToStringAndHealth(b, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
178 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>");
179 b.setToDecNumber({"876543210987654321", -1}, status);
180 c.setToDecNumber({"987654321098765432", -1}, status);
181 c = std::move(b); // move assignment
182 assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 876543210987654321E0>");
183 a = std::move(c); // move assignment to a defunct object
184 assertToStringAndHealth(a, u"<DecimalQuantity 0:0 bytes 876543210987654321E0>");
185 }
186 }
187
testAppend()188 void DecimalQuantityTest::testAppend() {
189 DecimalQuantity fq;
190 fq.appendDigit(1, 0, true);
191 assertEquals("Failed on append", u"1E+0", fq.toScientificString());
192 assertHealth(fq);
193 fq.appendDigit(2, 0, true);
194 assertEquals("Failed on append", u"1.2E+1", fq.toScientificString());
195 assertHealth(fq);
196 fq.appendDigit(3, 1, true);
197 assertEquals("Failed on append", u"1.203E+3", fq.toScientificString());
198 assertHealth(fq);
199 fq.appendDigit(0, 1, true);
200 assertEquals("Failed on append", u"1.203E+5", fq.toScientificString());
201 assertHealth(fq);
202 fq.appendDigit(4, 0, true);
203 assertEquals("Failed on append", u"1.203004E+6", fq.toScientificString());
204 assertHealth(fq);
205 fq.appendDigit(0, 0, true);
206 assertEquals("Failed on append", u"1.203004E+7", fq.toScientificString());
207 assertHealth(fq);
208 fq.appendDigit(5, 0, false);
209 assertEquals("Failed on append", u"1.20300405E+7", fq.toScientificString());
210 assertHealth(fq);
211 fq.appendDigit(6, 0, false);
212 assertEquals("Failed on append", u"1.203004056E+7", fq.toScientificString());
213 assertHealth(fq);
214 fq.appendDigit(7, 3, false);
215 assertEquals("Failed on append", u"1.2030040560007E+7", fq.toScientificString());
216 assertHealth(fq);
217 UnicodeString baseExpected(u"1.2030040560007");
218 for (int i = 0; i < 10; i++) {
219 fq.appendDigit(8, 0, false);
220 baseExpected.append(u'8');
221 UnicodeString expected(baseExpected);
222 expected.append(u"E+7");
223 assertEquals("Failed on append", expected, fq.toScientificString());
224 assertHealth(fq);
225 }
226 fq.appendDigit(9, 2, false);
227 baseExpected.append(u"009");
228 UnicodeString expected(baseExpected);
229 expected.append(u"E+7");
230 assertEquals("Failed on append", expected, fq.toScientificString());
231 assertHealth(fq);
232 }
233
testConvertToAccurateDouble()234 void DecimalQuantityTest::testConvertToAccurateDouble() {
235 // based on https://github.com/google/double-conversion/issues/28
236 static double hardDoubles[] = {
237 1651087494906221570.0,
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,};
261
262 static double exactDoubles[] = {
263 51423,
264 51423e10,
265 -5074790912492772E-327,
266 83602530019752571E-327,
267 4.503599627370496E15,
268 6.789512076111555E15,
269 9.007199254740991E15,
270 9.007199254740992E15};
271
272 for (double d : hardDoubles) {
273 checkDoubleBehavior(d, true);
274 }
275
276 for (double d : exactDoubles) {
277 checkDoubleBehavior(d, false);
278 }
279
280 assertDoubleEquals(u"NaN check failed", NAN, DecimalQuantity().setToDouble(NAN).toDouble());
281 assertDoubleEquals(
282 u"Inf check failed", INFINITY, DecimalQuantity().setToDouble(INFINITY).toDouble());
283 assertDoubleEquals(
284 u"-Inf check failed", -INFINITY, DecimalQuantity().setToDouble(-INFINITY).toDouble());
285
286 // Generate random doubles
287 for (int32_t i = 0; i < 10000; i++) {
288 uint8_t bytes[8];
289 for (int32_t j = 0; j < 8; j++) {
290 bytes[j] = static_cast<uint8_t>(rand() % 256);
291 }
292 double d;
293 uprv_memcpy(&d, bytes, 8);
294 if (std::isnan(d) || !std::isfinite(d)) { continue; }
295 checkDoubleBehavior(d, false);
296 }
297 }
298
testUseApproximateDoubleWhenAble()299 void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
300 static const struct TestCase {
301 double d;
302 int32_t maxFrac;
303 RoundingMode roundingMode;
304 bool usesExact;
305 } cases[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
306 {1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN, false},
307 {1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN, false},
308 {1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN, true},
309 {1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
310 {1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN, true},
311 {1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN, false},
312 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN, false},
313 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING, true},
314 {1.235, 1, RoundingMode::UNUM_ROUND_CEILING, false},
315 {1.235, 2, RoundingMode::UNUM_ROUND_CEILING, false},
316 {1.235, 3, RoundingMode::UNUM_ROUND_CEILING, true}};
317
318 UErrorCode status = U_ZERO_ERROR;
319 for (TestCase cas : cases) {
320 DecimalQuantity fq;
321 fq.setToDouble(cas.d);
322 assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
323 fq.roundToMagnitude(-cas.maxFrac, cas.roundingMode, status);
324 assertSuccess("Rounding to magnitude", status);
325 if (cas.usesExact != fq.isExplicitExactDouble()) {
326 errln(UnicodeString(u"Using approximate double after rounding: ") + fq.toString());
327 }
328 }
329 }
330
testHardDoubleConversion()331 void DecimalQuantityTest::testHardDoubleConversion() {
332 static const struct TestCase {
333 double input;
334 const char16_t* expectedOutput;
335 } cases[] = {
336 { 512.0000000000017, u"512.0000000000017" },
337 { 4095.9999999999977, u"4095.9999999999977" },
338 { 4095.999999999998, u"4095.999999999998" },
339 { 4095.9999999999986, u"4095.9999999999986" },
340 { 4095.999999999999, u"4095.999999999999" },
341 { 4095.9999999999995, u"4095.9999999999995" },
342 { 4096.000000000001, u"4096.000000000001" },
343 { 4096.000000000002, u"4096.000000000002" },
344 { 4096.000000000003, u"4096.000000000003" },
345 { 4096.000000000004, u"4096.000000000004" },
346 { 4096.000000000005, u"4096.000000000005" },
347 { 4096.0000000000055, u"4096.0000000000055" },
348 { 4096.000000000006, u"4096.000000000006" },
349 { 4096.000000000007, u"4096.000000000007" } };
350
351 for (auto& cas : cases) {
352 DecimalQuantity q;
353 q.setToDouble(cas.input);
354 q.roundToInfinity();
355 UnicodeString actualOutput = q.toPlainString();
356 assertEquals("", cas.expectedOutput, actualOutput);
357 }
358 }
359
testToDouble()360 void DecimalQuantityTest::testToDouble() {
361 IcuTestErrorCode status(*this, "testToDouble");
362 static const struct TestCase {
363 const char* input; // char* for the decNumber constructor
364 double expected;
365 } cases[] = {
366 { "0", 0.0 },
367 { "514.23", 514.23 },
368 { "-3.142E-271", -3.142e-271 } };
369
370 for (auto& cas : cases) {
371 status.setScope(cas.input);
372 DecimalQuantity q;
373 q.setToDecNumber({cas.input, -1}, status);
374 double actual = q.toDouble();
375 assertEquals("Doubles should exactly equal", cas.expected, actual);
376 }
377 }
378
testMaxDigits()379 void DecimalQuantityTest::testMaxDigits() {
380 IcuTestErrorCode status(*this, "testMaxDigits");
381 DecimalQuantity dq;
382 dq.setToDouble(876.543);
383 dq.roundToInfinity();
384 dq.setMinInteger(0);
385 dq.applyMaxInteger(2);
386 dq.setMinFraction(0);
387 dq.roundToMagnitude(-2, UNUM_ROUND_FLOOR, status);
388 assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
389 assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
390 assertEquals("Should trim, toLong", 76LL, dq.toLong(true));
391 assertEquals("Should trim, toFractionLong", (int64_t) 54, (int64_t) dq.toFractionLong(false));
392 assertEquals("Should trim, toDouble", 76.54, dq.toDouble());
393 // To test DecNum output, check the round-trip.
394 DecNum dn;
395 dq.toDecNum(dn, status);
396 DecimalQuantity copy;
397 copy.setToDecNum(dn, status);
398 assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString());
399 }
400
testNickelRounding()401 void DecimalQuantityTest::testNickelRounding() {
402 IcuTestErrorCode status(*this, "testNickelRounding");
403 struct TestCase {
404 double input;
405 int32_t magnitude;
406 UNumberFormatRoundingMode roundingMode;
407 const char16_t* expected;
408 } cases[] = {
409 {1.000, -2, UNUM_ROUND_HALFEVEN, u"1"},
410 {1.001, -2, UNUM_ROUND_HALFEVEN, u"1"},
411 {1.010, -2, UNUM_ROUND_HALFEVEN, u"1"},
412 {1.020, -2, UNUM_ROUND_HALFEVEN, u"1"},
413 {1.024, -2, UNUM_ROUND_HALFEVEN, u"1"},
414 {1.025, -2, UNUM_ROUND_HALFEVEN, u"1"},
415 {1.025, -2, UNUM_ROUND_HALFDOWN, u"1"},
416 {1.025, -2, UNUM_ROUND_HALFUP, u"1.05"},
417 {1.026, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
418 {1.030, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
419 {1.040, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
420 {1.050, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
421 {1.060, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
422 {1.070, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
423 {1.074, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
424 {1.075, -2, UNUM_ROUND_HALFDOWN, u"1.05"},
425 {1.075, -2, UNUM_ROUND_HALFUP, u"1.1"},
426 {1.075, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
427 {1.076, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
428 {1.080, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
429 {1.090, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
430 {1.099, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
431 {1.999, -2, UNUM_ROUND_HALFEVEN, u"2"},
432 {2.25, -1, UNUM_ROUND_HALFEVEN, u"2"},
433 {2.25, -1, UNUM_ROUND_HALFUP, u"2.5"},
434 {2.75, -1, UNUM_ROUND_HALFDOWN, u"2.5"},
435 {2.75, -1, UNUM_ROUND_HALFEVEN, u"3"},
436 {3.00, -1, UNUM_ROUND_CEILING, u"3"},
437 {3.25, -1, UNUM_ROUND_CEILING, u"3.5"},
438 {3.50, -1, UNUM_ROUND_CEILING, u"3.5"},
439 {3.75, -1, UNUM_ROUND_CEILING, u"4"},
440 {4.00, -1, UNUM_ROUND_FLOOR, u"4"},
441 {4.25, -1, UNUM_ROUND_FLOOR, u"4"},
442 {4.50, -1, UNUM_ROUND_FLOOR, u"4.5"},
443 {4.75, -1, UNUM_ROUND_FLOOR, u"4.5"},
444 {5.00, -1, UNUM_ROUND_UP, u"5"},
445 {5.25, -1, UNUM_ROUND_UP, u"5.5"},
446 {5.50, -1, UNUM_ROUND_UP, u"5.5"},
447 {5.75, -1, UNUM_ROUND_UP, u"6"},
448 {6.00, -1, UNUM_ROUND_DOWN, u"6"},
449 {6.25, -1, UNUM_ROUND_DOWN, u"6"},
450 {6.50, -1, UNUM_ROUND_DOWN, u"6.5"},
451 {6.75, -1, UNUM_ROUND_DOWN, u"6.5"},
452 {7.00, -1, UNUM_ROUND_UNNECESSARY, u"7"},
453 {7.50, -1, UNUM_ROUND_UNNECESSARY, u"7.5"},
454 };
455 for (const auto& cas : cases) {
456 UnicodeString message = DoubleToUnicodeString(cas.input) + u" @ " + Int64ToUnicodeString(cas.magnitude) + u" / " + Int64ToUnicodeString(cas.roundingMode);
457 status.setScope(message);
458 DecimalQuantity dq;
459 dq.setToDouble(cas.input);
460 dq.roundToNickel(cas.magnitude, cas.roundingMode, status);
461 status.errIfFailureAndReset();
462 UnicodeString actual = dq.toPlainString();
463 assertEquals(message, cas.expected, actual);
464 }
465 status.setScope("");
466 DecimalQuantity dq;
467 dq.setToDouble(7.1);
468 dq.roundToNickel(-1, UNUM_ROUND_UNNECESSARY, status);
469 status.expectErrorAndReset(U_FORMAT_INEXACT_ERROR);
470 }
471
testCompactDecimalSuppressedExponent()472 void DecimalQuantityTest::testCompactDecimalSuppressedExponent() {
473 IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent");
474 Locale ulocale("fr-FR");
475
476 struct TestCase {
477 UnicodeString skeleton;
478 double input;
479 const char16_t* expectedString;
480 int64_t expectedLong;
481 double expectedDouble;
482 const char16_t* expectedPlainString;
483 int32_t expectedSuppressedExponent;
484 } cases[] = {
485 // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent
486 {u"", 123456789, u"123 456 789", 123456789L, 123456789.0, u"123456789", 0},
487 {u"compact-long", 123456789, u"123 millions", 123000000L, 123000000.0, u"123000000", 6},
488 {u"compact-short", 123456789, u"123 M", 123000000L, 123000000.0, u"123000000", 6},
489 {u"scientific", 123456789, u"1,234568E8", 123456800L, 123456800.0, u"123456800", 8},
490
491 {u"", 1234567, u"1 234 567", 1234567L, 1234567.0, u"1234567", 0},
492 {u"compact-long", 1234567, u"1,2 million", 1200000L, 1200000.0, u"1200000", 6},
493 {u"compact-short", 1234567, u"1,2 M", 1200000L, 1200000.0, u"1200000", 6},
494 {u"scientific", 1234567, u"1,234567E6", 1234567L, 1234567.0, u"1234567", 6},
495
496 {u"", 123456, u"123 456", 123456L, 123456.0, u"123456", 0},
497 {u"compact-long", 123456, u"123 mille", 123000L, 123000.0, u"123000", 3},
498 {u"compact-short", 123456, u"123 k", 123000L, 123000.0, u"123000", 3},
499 {u"scientific", 123456, u"1,23456E5", 123456L, 123456.0, u"123456", 5},
500
501 {u"", 123, u"123", 123L, 123.0, u"123", 0},
502 {u"compact-long", 123, u"123", 123L, 123.0, u"123", 0},
503 {u"compact-short", 123, u"123", 123L, 123.0, u"123", 0},
504 {u"scientific", 123, u"1,23E2", 123L, 123.0, u"123", 2},
505
506 {u"", 1.2, u"1,2", 1L, 1.2, u"1.2", 0},
507 {u"compact-long", 1.2, u"1,2", 1L, 1.2, u"1.2", 0},
508 {u"compact-short", 1.2, u"1,2", 1L, 1.2, u"1.2", 0},
509 {u"scientific", 1.2, u"1,2E0", 1L, 1.2, u"1.2", 0},
510
511 {u"", 0.12, u"0,12", 0L, 0.12, u"0.12", 0},
512 {u"compact-long", 0.12, u"0,12", 0L, 0.12, u"0.12", 0},
513 {u"compact-short", 0.12, u"0,12", 0L, 0.12, u"0.12", 0},
514 {u"scientific", 0.12, u"1,2E-1", 0L, 0.12, u"0.12", -1},
515
516 {u"", 0.012, u"0,012", 0L, 0.012, u"0.012", 0},
517 {u"compact-long", 0.012, u"0,012", 0L, 0.012, u"0.012", 0},
518 {u"compact-short", 0.012, u"0,012", 0L, 0.012, u"0.012", 0},
519 {u"scientific", 0.012, u"1,2E-2", 0L, 0.012, u"0.012", -2},
520
521 {u"", 999.9, u"999,9", 999L, 999.9, u"999.9", 0},
522 {u"compact-long", 999.9, u"1 millier", 1000L, 1000.0, u"1000", 3},
523 {u"compact-short", 999.9, u"1 k", 1000L, 1000.0, u"1000", 3},
524 {u"scientific", 999.9, u"9,999E2", 999L, 999.9, u"999.9", 2},
525
526 {u"", 1000.0, u"1 000", 1000L, 1000.0, u"1000", 0},
527 {u"compact-long", 1000.0, u"1 millier", 1000L, 1000.0, u"1000", 3},
528 {u"compact-short", 1000.0, u"1 k", 1000L, 1000.0, u"1000", 3},
529 {u"scientific", 1000.0, u"1E3", 1000L, 1000.0, u"1000", 3},
530 };
531 for (const auto& cas : cases) {
532 // test the helper methods used to compute plural operand values
533
534 LocalizedNumberFormatter formatter =
535 NumberFormatter::forSkeleton(cas.skeleton, status)
536 .locale(ulocale);
537 FormattedNumber fn = formatter.formatDouble(cas.input, status);
538 DecimalQuantity dq;
539 fn.getDecimalQuantity(dq, status);
540 UnicodeString actualString = fn.toString(status);
541 int64_t actualLong = dq.toLong();
542 double actualDouble = dq.toDouble();
543 UnicodeString actualPlainString = dq.toPlainString();
544 int32_t actualSuppressedExponent = dq.getExponent();
545
546 assertEquals(
547 u"formatted number " + cas.skeleton + u" toString: " + cas.input,
548 cas.expectedString,
549 actualString);
550 assertEquals(
551 u"compact decimal " + cas.skeleton + u" toLong: " + cas.input,
552 cas.expectedLong,
553 actualLong);
554 assertDoubleEquals(
555 u"compact decimal " + cas.skeleton + u" toDouble: " + cas.input,
556 cas.expectedDouble,
557 actualDouble);
558 assertEquals(
559 u"formatted number " + cas.skeleton + u" toPlainString: " + cas.input,
560 cas.expectedPlainString,
561 actualPlainString);
562 assertEquals(
563 u"compact decimal " + cas.skeleton + u" suppressed exponent: " + cas.input,
564 cas.expectedSuppressedExponent,
565 actualSuppressedExponent);
566
567 // test the actual computed values of the plural operands
568
569 double expectedNOperand = cas.expectedDouble;
570 double expectedIOperand = cas.expectedLong;
571 double expectedEOperand = cas.expectedSuppressedExponent;
572 double actualNOperand = dq.getPluralOperand(PLURAL_OPERAND_N);
573 double actualIOperand = dq.getPluralOperand(PLURAL_OPERAND_I);
574 double actualEOperand = dq.getPluralOperand(PLURAL_OPERAND_E);
575
576 assertEquals(
577 u"formatted number " + cas.skeleton + u" toString: " + cas.input,
578 cas.expectedString,
579 actualString);
580 assertDoubleEquals(
581 u"compact decimal " + cas.skeleton + u" n operand: " + cas.input,
582 expectedNOperand,
583 actualNOperand);
584 assertDoubleEquals(
585 u"compact decimal " + cas.skeleton + u" i operand: " + cas.input,
586 expectedIOperand,
587 actualIOperand);
588 assertDoubleEquals(
589 u"compact decimal " + cas.skeleton + " e operand: " + cas.input,
590 expectedEOperand,
591 actualEOperand);
592 }
593 }
594
testSuppressedExponentUnchangedByInitialScaling()595 void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() {
596 IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent");
597 Locale ulocale("fr-FR");
598 LocalizedNumberFormatter withLocale = NumberFormatter::withLocale(ulocale);
599 LocalizedNumberFormatter compactLong =
600 withLocale.notation(Notation::compactLong());
601 LocalizedNumberFormatter compactScaled =
602 compactLong.scale(Scale::powerOfTen(3));
603
604 struct TestCase {
605 int32_t input;
606 UnicodeString expectedString;
607 double expectedNOperand;
608 double expectedIOperand;
609 double expectedEOperand;
610 } cases[] = {
611 // input, compact long string output,
612 // compact n operand, compact i operand, compact e operand
613 {123456789, "123 millions", 123000000.0, 123000000.0, 6.0},
614 {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0},
615 {123456, "123 mille", 123000.0, 123000.0, 3.0},
616 {123, "123", 123.0, 123.0, 0.0},
617 };
618
619 for (const auto& cas : cases) {
620 FormattedNumber fnCompactScaled = compactScaled.formatInt(cas.input, status);
621 DecimalQuantity dqCompactScaled;
622 fnCompactScaled.getDecimalQuantity(dqCompactScaled, status);
623 double compactScaledEOperand = dqCompactScaled.getPluralOperand(PLURAL_OPERAND_E);
624
625 FormattedNumber fnCompact = compactLong.formatInt(cas.input, status);
626 DecimalQuantity dqCompact;
627 fnCompact.getDecimalQuantity(dqCompact, status);
628 UnicodeString actualString = fnCompact.toString(status);
629 double compactNOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_N);
630 double compactIOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_I);
631 double compactEOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_E);
632 assertEquals(
633 u"formatted number " + Int64ToUnicodeString(cas.input) + " compactLong toString: ",
634 cas.expectedString,
635 actualString);
636 assertDoubleEquals(
637 u"compact decimal " + DoubleToUnicodeString(cas.input) + ", n operand vs. expected",
638 cas.expectedNOperand,
639 compactNOperand);
640 assertDoubleEquals(
641 u"compact decimal " + DoubleToUnicodeString(cas.input) + ", i operand vs. expected",
642 cas.expectedIOperand,
643 compactIOperand);
644 assertDoubleEquals(
645 u"compact decimal " + DoubleToUnicodeString(cas.input) + ", e operand vs. expected",
646 cas.expectedEOperand,
647 compactEOperand);
648
649 // By scaling by 10^3 in a locale that has words / compact notation
650 // based on powers of 10^3, we guarantee that the suppressed
651 // exponent will differ by 3.
652 assertDoubleEquals(
653 u"decimal " + DoubleToUnicodeString(cas.input) + ", e operand for compact vs. compact scaled",
654 compactEOperand + 3,
655 compactScaledEOperand);
656 }
657 }
658
659 #endif /* #if !UCONFIG_NO_FORMATTING */
660