• 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         TESTCASE_AUTO(testConvertToAccurateDouble);
25         TESTCASE_AUTO(testUseApproximateDoubleWhenAble);
26         TESTCASE_AUTO(testHardDoubleConversion);
27         TESTCASE_AUTO(testToDouble);
28         TESTCASE_AUTO(testMaxDigits);
29     TESTCASE_AUTO_END;
30 }
31 
assertDoubleEquals(UnicodeString message,double a,double b)32 void DecimalQuantityTest::assertDoubleEquals(UnicodeString message, double a, double b) {
33     if (a == b) {
34         return;
35     }
36 
37     double diff = a - b;
38     diff = diff < 0 ? -diff : diff;
39     double bound = a < 0 ? -a * 1e-6 : a * 1e-6;
40     if (diff > bound) {
41         errln(message + u": " + DoubleToUnicodeString(a) + u" vs " + DoubleToUnicodeString(b) + u" differ by " + DoubleToUnicodeString(diff));
42     }
43 }
44 
assertHealth(const DecimalQuantity & fq)45 void DecimalQuantityTest::assertHealth(const DecimalQuantity &fq) {
46     const char16_t* health = fq.checkHealth();
47     if (health != nullptr) {
48         errln(UnicodeString(u"HEALTH FAILURE: ") + UnicodeString(health) + u": " + fq.toString());
49     }
50 }
51 
52 void
assertToStringAndHealth(const DecimalQuantity & fq,const UnicodeString & expected)53 DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected) {
54     UnicodeString actual = fq.toString();
55     assertEquals("DecimalQuantity toString failed", expected, actual);
56     assertHealth(fq);
57 }
58 
checkDoubleBehavior(double d,bool explicitRequired)59 void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) {
60     DecimalQuantity fq;
61     fq.setToDouble(d);
62     if (explicitRequired) {
63         assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
64     }
65     UnicodeString baseStr = fq.toString();
66     fq.roundToInfinity();
67     UnicodeString newStr = fq.toString();
68     if (explicitRequired) {
69         assertTrue("Should not be using approximate double", fq.isExplicitExactDouble());
70     }
71     assertDoubleEquals(
72         UnicodeString(u"After conversion to exact BCD (double): ") + baseStr + u" vs " + newStr,
73         d, fq.toDouble());
74 }
75 
testDecimalQuantityBehaviorStandalone()76 void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
77     UErrorCode status = U_ZERO_ERROR;
78     DecimalQuantity fq;
79     assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 0E0>");
80     fq.setToInt(51423);
81     assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E0>");
82     fq.adjustMagnitude(-3);
83     assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E-3>");
84     fq.setToLong(999999999999000L);
85     assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
86     fq.setIntegerLength(2, 5);
87     assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
88     fq.setFractionLength(3, 6);
89     assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
90     fq.setToDouble(987.654321);
91     assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
92     fq.roundToInfinity();
93     assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
94     fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, 3, status);
95     assertSuccess("Rounding to increment", status);
96     assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
97     fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status);
98     assertSuccess("Rounding to magnitude", status);
99     assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
100 }
101 
testSwitchStorage()102 void DecimalQuantityTest::testSwitchStorage() {
103     UErrorCode status = U_ZERO_ERROR;
104     DecimalQuantity fq;
105 
106     fq.setToLong(1234123412341234L);
107     assertFalse("Should not be using byte array", fq.isUsingBytes());
108     assertEquals("Failed on initialize", u"1.234123412341234E+15", fq.toScientificString());
109     assertHealth(fq);
110     // Long -> Bytes
111     fq.appendDigit(5, 0, true);
112     assertTrue("Should be using byte array", fq.isUsingBytes());
113     assertEquals("Failed on multiply", u"1.2341234123412345E+16", fq.toScientificString());
114     assertHealth(fq);
115     // Bytes -> Long
116     fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status);
117     assertSuccess("Rounding to magnitude", status);
118     assertFalse("Should not be using byte array", fq.isUsingBytes());
119     assertEquals("Failed on round", u"1.23412341234E+16", fq.toScientificString());
120     assertHealth(fq);
121 }
122 
testCopyMove()123 void DecimalQuantityTest::testCopyMove() {
124     // Small numbers (fits in BCD long)
125     {
126         DecimalQuantity a;
127         a.setToLong(1234123412341234L);
128         DecimalQuantity b = a; // copy constructor
129         assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
130         assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
131         DecimalQuantity c(std::move(a)); // move constructor
132         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
133         c.setToLong(54321L);
134         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 54321E0>");
135         c = b; // copy assignment
136         assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
137         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
138         b.setToLong(45678);
139         c.setToLong(56789);
140         c = std::move(b); // move assignment
141         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 45678E0>");
142         a = std::move(c); // move assignment to a defunct object
143         assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 45678E0>");
144     }
145 
146     // Large numbers (requires byte allocation)
147     {
148         IcuTestErrorCode status(*this, "testCopyMove");
149         DecimalQuantity a;
150         a.setToDecNumber({"1234567890123456789", -1}, status);
151         DecimalQuantity b = a; // copy constructor
152         assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
153         assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
154         DecimalQuantity c(std::move(a)); // move constructor
155         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
156         c.setToDecNumber({"9876543210987654321", -1}, status);
157         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 9876543210987654321E0>");
158         c = b; // copy assignment
159         assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
160         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
161         b.setToDecNumber({"876543210987654321", -1}, status);
162         c.setToDecNumber({"987654321098765432", -1}, status);
163         c = std::move(b); // move assignment
164         assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>");
165         a = std::move(c); // move assignment to a defunct object
166         assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>");
167     }
168 }
169 
testAppend()170 void DecimalQuantityTest::testAppend() {
171     DecimalQuantity fq;
172     fq.appendDigit(1, 0, true);
173     assertEquals("Failed on append", u"1E+0", fq.toScientificString());
174     assertHealth(fq);
175     fq.appendDigit(2, 0, true);
176     assertEquals("Failed on append", u"1.2E+1", fq.toScientificString());
177     assertHealth(fq);
178     fq.appendDigit(3, 1, true);
179     assertEquals("Failed on append", u"1.203E+3", fq.toScientificString());
180     assertHealth(fq);
181     fq.appendDigit(0, 1, true);
182     assertEquals("Failed on append", u"1.203E+5", fq.toScientificString());
183     assertHealth(fq);
184     fq.appendDigit(4, 0, true);
185     assertEquals("Failed on append", u"1.203004E+6", fq.toScientificString());
186     assertHealth(fq);
187     fq.appendDigit(0, 0, true);
188     assertEquals("Failed on append", u"1.203004E+7", fq.toScientificString());
189     assertHealth(fq);
190     fq.appendDigit(5, 0, false);
191     assertEquals("Failed on append", u"1.20300405E+7", fq.toScientificString());
192     assertHealth(fq);
193     fq.appendDigit(6, 0, false);
194     assertEquals("Failed on append", u"1.203004056E+7", fq.toScientificString());
195     assertHealth(fq);
196     fq.appendDigit(7, 3, false);
197     assertEquals("Failed on append", u"1.2030040560007E+7", fq.toScientificString());
198     assertHealth(fq);
199     UnicodeString baseExpected(u"1.2030040560007");
200     for (int i = 0; i < 10; i++) {
201         fq.appendDigit(8, 0, false);
202         baseExpected.append(u'8');
203         UnicodeString expected(baseExpected);
204         expected.append(u"E+7");
205         assertEquals("Failed on append", expected, fq.toScientificString());
206         assertHealth(fq);
207     }
208     fq.appendDigit(9, 2, false);
209     baseExpected.append(u"009");
210     UnicodeString expected(baseExpected);
211     expected.append(u"E+7");
212     assertEquals("Failed on append", expected, fq.toScientificString());
213     assertHealth(fq);
214 }
215 
testConvertToAccurateDouble()216 void DecimalQuantityTest::testConvertToAccurateDouble() {
217     // based on https://github.com/google/double-conversion/issues/28
218     static double hardDoubles[] = {
219             1651087494906221570.0,
220             -5074790912492772E-327,
221             83602530019752571E-327,
222             2.207817077636718750000000000000,
223             1.818351745605468750000000000000,
224             3.941719055175781250000000000000,
225             3.738609313964843750000000000000,
226             3.967735290527343750000000000000,
227             1.328025817871093750000000000000,
228             3.920967102050781250000000000000,
229             1.015235900878906250000000000000,
230             1.335227966308593750000000000000,
231             1.344520568847656250000000000000,
232             2.879127502441406250000000000000,
233             3.695838928222656250000000000000,
234             1.845344543457031250000000000000,
235             3.793952941894531250000000000000,
236             3.211402893066406250000000000000,
237             2.565971374511718750000000000000,
238             0.965156555175781250000000000000,
239             2.700004577636718750000000000000,
240             0.767097473144531250000000000000,
241             1.780448913574218750000000000000,
242             2.624839782714843750000000000000,
243             1.305290222167968750000000000000,
244             3.834922790527343750000000000000,};
245 
246     static double integerDoubles[] = {
247             51423,
248             51423e10,
249             4.503599627370496E15,
250             6.789512076111555E15,
251             9.007199254740991E15,
252             9.007199254740992E15};
253 
254     for (double d : hardDoubles) {
255         checkDoubleBehavior(d, true);
256     }
257 
258     for (double d : integerDoubles) {
259         checkDoubleBehavior(d, false);
260     }
261 
262     assertDoubleEquals(u"NaN check failed", NAN, DecimalQuantity().setToDouble(NAN).toDouble());
263     assertDoubleEquals(
264             u"Inf check failed", INFINITY, DecimalQuantity().setToDouble(INFINITY).toDouble());
265     assertDoubleEquals(
266             u"-Inf check failed", -INFINITY, DecimalQuantity().setToDouble(-INFINITY).toDouble());
267 
268     // Generate random doubles
269     for (int32_t i = 0; i < 10000; i++) {
270         uint8_t bytes[8];
271         for (int32_t j = 0; j < 8; j++) {
272             bytes[j] = static_cast<uint8_t>(rand() % 256);
273         }
274         double d;
275         uprv_memcpy(&d, bytes, 8);
276         if (std::isnan(d) || !std::isfinite(d)) { continue; }
277         checkDoubleBehavior(d, false);
278     }
279 }
280 
testUseApproximateDoubleWhenAble()281 void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
282     static const struct TestCase {
283         double d;
284         int32_t maxFrac;
285         RoundingMode roundingMode;
286         bool usesExact;
287     } cases[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
288                  {1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN, false},
289                  {1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN, false},
290                  {1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN, true},
291                  {1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
292                  {1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN, true},
293                  {1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN, false},
294                  {1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN, false},
295                  {1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING, true},
296                  {1.235, 1, RoundingMode::UNUM_ROUND_CEILING, false},
297                  {1.235, 2, RoundingMode::UNUM_ROUND_CEILING, false},
298                  {1.235, 3, RoundingMode::UNUM_ROUND_CEILING, true}};
299 
300     UErrorCode status = U_ZERO_ERROR;
301     for (TestCase cas : cases) {
302         DecimalQuantity fq;
303         fq.setToDouble(cas.d);
304         assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
305         fq.roundToMagnitude(-cas.maxFrac, cas.roundingMode, status);
306         assertSuccess("Rounding to magnitude", status);
307         if (cas.usesExact != fq.isExplicitExactDouble()) {
308             errln(UnicodeString(u"Using approximate double after rounding: ") + fq.toString());
309         }
310     }
311 }
312 
testHardDoubleConversion()313 void DecimalQuantityTest::testHardDoubleConversion() {
314     static const struct TestCase {
315         double input;
316         const char16_t* expectedOutput;
317     } cases[] = {
318             { 512.0000000000017, u"512.0000000000017" },
319             { 4095.9999999999977, u"4095.9999999999977" },
320             { 4095.999999999998, u"4095.999999999998" },
321             { 4095.9999999999986, u"4095.9999999999986" },
322             { 4095.999999999999, u"4095.999999999999" },
323             { 4095.9999999999995, u"4095.9999999999995" },
324             { 4096.000000000001, u"4096.000000000001" },
325             { 4096.000000000002, u"4096.000000000002" },
326             { 4096.000000000003, u"4096.000000000003" },
327             { 4096.000000000004, u"4096.000000000004" },
328             { 4096.000000000005, u"4096.000000000005" },
329             { 4096.0000000000055, u"4096.0000000000055" },
330             { 4096.000000000006, u"4096.000000000006" },
331             { 4096.000000000007, u"4096.000000000007" } };
332 
333     for (auto& cas : cases) {
334         DecimalQuantity q;
335         q.setToDouble(cas.input);
336         q.roundToInfinity();
337         UnicodeString actualOutput = q.toPlainString();
338         assertEquals("", cas.expectedOutput, actualOutput);
339     }
340 }
341 
testToDouble()342 void DecimalQuantityTest::testToDouble() {
343     IcuTestErrorCode status(*this, "testToDouble");
344     static const struct TestCase {
345         const char* input; // char* for the decNumber constructor
346         double expected;
347     } cases[] = {
348             { "0", 0.0 },
349             { "514.23", 514.23 },
350             { "-3.142E-271", -3.142e-271 } };
351 
352     for (auto& cas : cases) {
353         status.setScope(cas.input);
354         DecimalQuantity q;
355         q.setToDecNumber({cas.input, -1}, status);
356         double actual = q.toDouble();
357         assertEquals("Doubles should exactly equal", cas.expected, actual);
358     }
359 }
360 
testMaxDigits()361 void DecimalQuantityTest::testMaxDigits() {
362     IcuTestErrorCode status(*this, "testMaxDigits");
363     DecimalQuantity dq;
364     dq.setToDouble(876.543);
365     dq.roundToInfinity();
366     dq.setIntegerLength(0, 2);
367     dq.setFractionLength(0, 2);
368     assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
369     assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
370     assertEquals("Should trim, toLong", 76LL, dq.toLong(true));
371     assertEquals("Should trim, toFractionLong", (int64_t) 54, (int64_t) dq.toFractionLong(false));
372     assertEquals("Should trim, toDouble", 76.54, dq.toDouble());
373     // To test DecNum output, check the round-trip.
374     DecNum dn;
375     dq.toDecNum(dn, status);
376     DecimalQuantity copy;
377     copy.setToDecNum(dn, status);
378     if (!logKnownIssue("13701")) {
379         assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString());
380     }
381 }
382 
383 #endif /* #if !UCONFIG_NO_FORMATTING */
384