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