]> granicus.if.org Git - icu/blob - icu4c/source/test/intltest/numbertest_decimalquantity.cpp
ICU-21493 Add more rounding modes in ICU4C
[icu] / icu4c / source / 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         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
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
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
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
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
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
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
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
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
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
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
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
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
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
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_HALF_ODD, u"1.05"},
417         {1.025, -2, UNUM_ROUND_HALF_CEILING, u"1.05"},
418         {1.025, -2, UNUM_ROUND_HALF_FLOOR, u"1"},
419         {1.025, -2, UNUM_ROUND_HALFUP,   u"1.05"},
420         {1.026, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
421         {1.030, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
422         {1.040, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
423         {1.050, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
424         {1.060, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
425         {1.070, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
426         {1.074, -2, UNUM_ROUND_HALFEVEN, u"1.05"},
427         {1.075, -2, UNUM_ROUND_HALFDOWN, u"1.05"},
428         {1.075, -2, UNUM_ROUND_HALF_ODD, u"1.05"},
429         {1.075, -2, UNUM_ROUND_HALF_CEILING, u"1.1"},
430         {1.075, -2, UNUM_ROUND_HALF_FLOOR, u"1.05"},
431         {1.075, -2, UNUM_ROUND_HALFUP,   u"1.1"},
432         {1.075, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
433         {1.076, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
434         {1.080, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
435         {1.090, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
436         {1.099, -2, UNUM_ROUND_HALFEVEN, u"1.1"},
437         {1.999, -2, UNUM_ROUND_HALFEVEN, u"2"},
438         {2.25, -1, UNUM_ROUND_HALFEVEN, u"2"},
439         {2.25, -1, UNUM_ROUND_HALFUP,   u"2.5"},
440         {2.75, -1, UNUM_ROUND_HALFDOWN, u"2.5"},
441         {2.75, -1, UNUM_ROUND_HALF_ODD, u"2.5"},
442         {2.75, -1, UNUM_ROUND_HALF_CEILING, u"3"},
443         {2.75, -1, UNUM_ROUND_HALF_FLOOR, u"2.5"},
444         {2.75, -1, UNUM_ROUND_HALFEVEN, u"3"},
445         {3.00, -1, UNUM_ROUND_CEILING, u"3"},
446         {3.25, -1, UNUM_ROUND_CEILING, u"3.5"},
447         {3.50, -1, UNUM_ROUND_CEILING, u"3.5"},
448         {3.75, -1, UNUM_ROUND_CEILING, u"4"},
449         {4.00, -1, UNUM_ROUND_FLOOR, u"4"},
450         {4.25, -1, UNUM_ROUND_FLOOR, u"4"},
451         {4.50, -1, UNUM_ROUND_FLOOR, u"4.5"},
452         {4.75, -1, UNUM_ROUND_FLOOR, u"4.5"},
453         {5.00, -1, UNUM_ROUND_UP, u"5"},
454         {5.25, -1, UNUM_ROUND_UP, u"5.5"},
455         {5.50, -1, UNUM_ROUND_UP, u"5.5"},
456         {5.75, -1, UNUM_ROUND_UP, u"6"},
457         {6.00, -1, UNUM_ROUND_DOWN, u"6"},
458         {6.25, -1, UNUM_ROUND_DOWN, u"6"},
459         {6.50, -1, UNUM_ROUND_DOWN, u"6.5"},
460         {6.75, -1, UNUM_ROUND_DOWN, u"6.5"},
461         {7.00, -1, UNUM_ROUND_UNNECESSARY, u"7"},
462         {7.50, -1, UNUM_ROUND_UNNECESSARY, u"7.5"},
463     };
464     for (const auto& cas : cases) {
465         UnicodeString message = DoubleToUnicodeString(cas.input) + u" @ " + Int64ToUnicodeString(cas.magnitude) + u" / " + Int64ToUnicodeString(cas.roundingMode);
466         status.setScope(message);
467         DecimalQuantity dq;
468         dq.setToDouble(cas.input);
469         dq.roundToNickel(cas.magnitude, cas.roundingMode, status);
470         status.errIfFailureAndReset();
471         UnicodeString actual = dq.toPlainString();
472         assertEquals(message, cas.expected, actual);
473     }
474     status.setScope("");
475     DecimalQuantity dq;
476     dq.setToDouble(7.1);
477     dq.roundToNickel(-1, UNUM_ROUND_UNNECESSARY, status);
478     status.expectErrorAndReset(U_FORMAT_INEXACT_ERROR);
479 }
480
481 void DecimalQuantityTest::testCompactDecimalSuppressedExponent() {
482     IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent");
483     Locale ulocale("fr-FR");
484
485     struct TestCase {
486         UnicodeString skeleton;
487         double input;
488         const char16_t* expectedString;
489         int64_t expectedLong;
490         double expectedDouble;
491         const char16_t* expectedPlainString;
492         int32_t expectedSuppressedExponent;
493     } cases[] = {
494         // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent
495         {u"",              123456789, u"123 456 789",  123456789L, 123456789.0, u"123456789", 0},
496         {u"compact-long",  123456789, u"123 millions", 123000000L, 123000000.0, u"123000000", 6},
497         {u"compact-short", 123456789, u"123 M",        123000000L, 123000000.0, u"123000000", 6},
498         {u"scientific",    123456789, u"1,234568E8",   123456800L, 123456800.0, u"123456800", 8},
499
500         {u"",              1234567, u"1 234 567",   1234567L, 1234567.0, u"1234567", 0},
501         {u"compact-long",  1234567, u"1,2 million", 1200000L, 1200000.0, u"1200000", 6},
502         {u"compact-short", 1234567, u"1,2 M",       1200000L, 1200000.0, u"1200000", 6},
503         {u"scientific",    1234567, u"1,234567E6",  1234567L, 1234567.0, u"1234567", 6},
504
505         {u"",              123456, u"123 456",   123456L, 123456.0, u"123456", 0},
506         {u"compact-long",  123456, u"123 mille", 123000L, 123000.0, u"123000", 3},
507         {u"compact-short", 123456, u"123 k",     123000L, 123000.0, u"123000", 3},
508         {u"scientific",    123456, u"1,23456E5", 123456L, 123456.0, u"123456", 5},
509
510         {u"",              123, u"123",    123L, 123.0, u"123", 0},
511         {u"compact-long",  123, u"123",    123L, 123.0, u"123", 0},
512         {u"compact-short", 123, u"123",    123L, 123.0, u"123", 0},
513         {u"scientific",    123, u"1,23E2", 123L, 123.0, u"123", 2},
514
515         {u"",              1.2, u"1,2",   1L, 1.2, u"1.2", 0},
516         {u"compact-long",  1.2, u"1,2",   1L, 1.2, u"1.2", 0},
517         {u"compact-short", 1.2, u"1,2",   1L, 1.2, u"1.2", 0},
518         {u"scientific",    1.2, u"1,2E0", 1L, 1.2, u"1.2", 0},
519
520         {u"",              0.12, u"0,12",   0L, 0.12, u"0.12", 0},
521         {u"compact-long",  0.12, u"0,12",   0L, 0.12, u"0.12", 0},
522         {u"compact-short", 0.12, u"0,12",   0L, 0.12, u"0.12", 0},
523         {u"scientific",    0.12, u"1,2E-1", 0L, 0.12, u"0.12", -1},
524
525         {u"",              0.012, u"0,012",   0L, 0.012, u"0.012", 0},
526         {u"compact-long",  0.012, u"0,012",   0L, 0.012, u"0.012", 0},
527         {u"compact-short", 0.012, u"0,012",   0L, 0.012, u"0.012", 0},
528         {u"scientific",    0.012, u"1,2E-2",  0L, 0.012, u"0.012", -2},
529
530         {u"",              999.9, u"999,9",     999L,  999.9,  u"999.9", 0},
531         {u"compact-long",  999.9, u"1 millier", 1000L, 1000.0, u"1000",  3},
532         {u"compact-short", 999.9, u"1 k",       1000L, 1000.0, u"1000",  3},
533         {u"scientific",    999.9, u"9,999E2",   999L,  999.9,  u"999.9", 2},
534
535         {u"",              1000.0, u"1 000",     1000L, 1000.0, u"1000", 0},
536         {u"compact-long",  1000.0, u"1 millier", 1000L, 1000.0, u"1000", 3},
537         {u"compact-short", 1000.0, u"1 k",       1000L, 1000.0, u"1000", 3},
538         {u"scientific",    1000.0, u"1E3",       1000L, 1000.0, u"1000", 3},
539     };
540     for (const auto& cas : cases) {
541         // test the helper methods used to compute plural operand values
542
543         LocalizedNumberFormatter formatter =
544             NumberFormatter::forSkeleton(cas.skeleton, status)
545               .locale(ulocale);
546         FormattedNumber fn = formatter.formatDouble(cas.input, status);
547         DecimalQuantity dq;
548         fn.getDecimalQuantity(dq, status);
549         UnicodeString actualString = fn.toString(status);
550         int64_t actualLong = dq.toLong();
551         double actualDouble = dq.toDouble();
552         UnicodeString actualPlainString = dq.toPlainString();
553         int32_t actualSuppressedExponent = dq.getExponent();
554
555         assertEquals(
556                 u"formatted number " + cas.skeleton + u" toString: " + cas.input,
557                 cas.expectedString,
558                 actualString);
559         assertEquals(
560                 u"compact decimal " + cas.skeleton + u" toLong: " + cas.input,
561                 cas.expectedLong,
562                 actualLong);
563         assertDoubleEquals(
564                 u"compact decimal " + cas.skeleton + u" toDouble: " + cas.input,
565                 cas.expectedDouble,
566                 actualDouble);
567         assertEquals(
568                 u"formatted number " + cas.skeleton + u" toPlainString: " + cas.input,
569                 cas.expectedPlainString,
570                 actualPlainString);
571         assertEquals(
572                 u"compact decimal " + cas.skeleton + u" suppressed exponent: " + cas.input,
573                 cas.expectedSuppressedExponent,
574                 actualSuppressedExponent);
575
576         // test the actual computed values of the plural operands
577
578         double expectedNOperand = cas.expectedDouble;
579         double expectedIOperand = cas.expectedLong;
580         double expectedEOperand = cas.expectedSuppressedExponent;
581         double actualNOperand = dq.getPluralOperand(PLURAL_OPERAND_N);
582         double actualIOperand = dq.getPluralOperand(PLURAL_OPERAND_I);
583         double actualEOperand = dq.getPluralOperand(PLURAL_OPERAND_E);
584
585         assertDoubleEquals(
586                 u"compact decimal " + cas.skeleton + u" n operand: " + cas.input,
587                 expectedNOperand,
588                 actualNOperand);
589         assertDoubleEquals(
590                 u"compact decimal " + cas.skeleton + u" i operand: " + cas.input,
591                 expectedIOperand,
592                 actualIOperand);
593         assertDoubleEquals(
594                 u"compact decimal " + cas.skeleton + " e operand: " + cas.input,
595                 expectedEOperand,
596                 actualEOperand);
597     }
598 }
599
600 void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() {
601     IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent");
602     Locale ulocale("fr-FR");
603     LocalizedNumberFormatter withLocale = NumberFormatter::withLocale(ulocale);
604     LocalizedNumberFormatter compactLong =
605         withLocale.notation(Notation::compactLong());
606     LocalizedNumberFormatter compactScaled =
607         compactLong.scale(Scale::powerOfTen(3));
608     
609     struct TestCase {
610         int32_t input;
611         UnicodeString expectedString;
612         double expectedNOperand;
613         double expectedIOperand;
614         double expectedEOperand;
615     } cases[] = {
616         // input, compact long string output,
617         // compact n operand, compact i operand, compact e operand
618         {123456789, "123 millions", 123000000.0, 123000000.0, 6.0},
619         {1234567,   "1,2 million",  1200000.0,   1200000.0,   6.0},
620         {123456,    "123 mille",    123000.0,    123000.0,    3.0},
621         {123,       "123",          123.0,       123.0,       0.0},
622     };
623
624     for (const auto& cas : cases) {
625         FormattedNumber fnCompactScaled = compactScaled.formatInt(cas.input, status);
626         DecimalQuantity dqCompactScaled;
627         fnCompactScaled.getDecimalQuantity(dqCompactScaled, status);
628         double compactScaledEOperand = dqCompactScaled.getPluralOperand(PLURAL_OPERAND_E);
629
630         FormattedNumber fnCompact = compactLong.formatInt(cas.input, status);
631         DecimalQuantity dqCompact;
632         fnCompact.getDecimalQuantity(dqCompact, status);
633         UnicodeString actualString = fnCompact.toString(status);
634         double compactNOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_N);
635         double compactIOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_I);
636         double compactEOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_E);
637         assertEquals(
638                 u"formatted number " + Int64ToUnicodeString(cas.input) + " compactLong toString: ",
639                 cas.expectedString,
640                 actualString);
641         assertDoubleEquals(
642                 u"compact decimal " + DoubleToUnicodeString(cas.input) + ", n operand vs. expected",
643                 cas.expectedNOperand,
644                 compactNOperand);
645         assertDoubleEquals(
646                 u"compact decimal " + DoubleToUnicodeString(cas.input) + ", i operand vs. expected",
647                 cas.expectedIOperand,
648                 compactIOperand);
649         assertDoubleEquals(
650                 u"compact decimal " + DoubleToUnicodeString(cas.input) + ", e operand vs. expected",
651                 cas.expectedEOperand,
652                 compactEOperand);
653
654         // By scaling by 10^3 in a locale that has words / compact notation
655         // based on powers of 10^3, we guarantee that the suppressed
656         // exponent will differ by 3.
657         assertDoubleEquals(
658                 u"decimal " + DoubleToUnicodeString(cas.input) + ", e operand for compact vs. compact scaled",
659                 compactEOperand + 3,
660                 compactScaledEOperand);
661     }
662 }
663
664 #endif /* #if !UCONFIG_NO_FORMATTING */