• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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