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