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