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