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 && !UPRV_INCOMPLETE_CPP11_SUPPORT
7
8#include "number_decimalquantity.h"
9#include "math.h"
10#include <cmath>
11#include "numbertest.h"
12
13void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
14    if (exec) {
15        logln("TestSuite DecimalQuantityTest: ");
16    }
17    TESTCASE_AUTO_BEGIN;
18        TESTCASE_AUTO(testDecimalQuantityBehaviorStandalone);
19        TESTCASE_AUTO(testSwitchStorage);
20        TESTCASE_AUTO(testAppend);
21        TESTCASE_AUTO(testConvertToAccurateDouble);
22        TESTCASE_AUTO(testUseApproximateDoubleWhenAble);
23    TESTCASE_AUTO_END;
24}
25
26void DecimalQuantityTest::assertDoubleEquals(UnicodeString message, double a, double b) {
27    if (a == b) {
28        return;
29    }
30
31    double diff = a - b;
32    diff = diff < 0 ? -diff : diff;
33    double bound = a < 0 ? -a * 1e-6 : a * 1e-6;
34    if (diff > bound) {
35        errln(message + u": " + DoubleToUnicodeString(a) + u" vs " + DoubleToUnicodeString(b) + u" differ by " + DoubleToUnicodeString(diff));
36    }
37}
38
39void DecimalQuantityTest::assertHealth(const DecimalQuantity &fq) {
40    const char16_t* health = fq.checkHealth();
41    if (health != nullptr) {
42        errln(UnicodeString(u"HEALTH FAILURE: ") + UnicodeString(health) + u": " + fq.toString());
43    }
44}
45
46void
47DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected) {
48    UnicodeString actual = fq.toString();
49    assertEquals("DecimalQuantity toString failed", expected, actual);
50    assertHealth(fq);
51}
52
53void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) {
54    DecimalQuantity fq;
55    fq.setToDouble(d);
56    if (explicitRequired) {
57        assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
58    }
59    UnicodeString baseStr = fq.toString();
60    assertDoubleEquals(
61        UnicodeString(u"Initial construction from hard double: ") + baseStr,
62        d, fq.toDouble());
63    fq.roundToInfinity();
64    UnicodeString newStr = fq.toString();
65    if (explicitRequired) {
66        assertTrue("Should not be using approximate double", fq.isExplicitExactDouble());
67    }
68    assertDoubleEquals(
69        UnicodeString(u"After conversion to exact BCD (double): ") + baseStr + u" vs " + newStr,
70        d, fq.toDouble());
71}
72
73void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
74    UErrorCode status = U_ZERO_ERROR;
75    DecimalQuantity fq;
76    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 0E0>");
77    fq.setToInt(51423);
78    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E0>");
79    fq.adjustMagnitude(-3);
80    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E-3>");
81    fq.setToLong(999999999999000L);
82    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
83    fq.setIntegerLength(2, 5);
84    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
85    fq.setFractionLength(3, 6);
86    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
87    fq.setToDouble(987.654321);
88    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
89    fq.roundToInfinity();
90    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
91    fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, 3, status);
92    assertSuccess("Rounding to increment", status);
93    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
94    fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status);
95    assertSuccess("Rounding to magnitude", status);
96    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
97}
98
99void DecimalQuantityTest::testSwitchStorage() {
100    UErrorCode status = U_ZERO_ERROR;
101    DecimalQuantity fq;
102
103    fq.setToLong(1234123412341234L);
104    assertFalse("Should not be using byte array", fq.isUsingBytes());
105    assertEquals("Failed on initialize", u"1234123412341234E0", fq.toNumberString());
106    assertHealth(fq);
107    // Long -> Bytes
108    fq.appendDigit(5, 0, true);
109    assertTrue("Should be using byte array", fq.isUsingBytes());
110    assertEquals("Failed on multiply", u"12341234123412345E0", fq.toNumberString());
111    assertHealth(fq);
112    // Bytes -> Long
113    fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status);
114    assertSuccess("Rounding to magnitude", status);
115    assertFalse("Should not be using byte array", fq.isUsingBytes());
116    assertEquals("Failed on round", u"123412341234E5", fq.toNumberString());
117    assertHealth(fq);
118}
119
120void DecimalQuantityTest::testAppend() {
121    DecimalQuantity fq;
122    fq.appendDigit(1, 0, true);
123    assertEquals("Failed on append", u"1E0", fq.toNumberString());
124    assertHealth(fq);
125    fq.appendDigit(2, 0, true);
126    assertEquals("Failed on append", u"12E0", fq.toNumberString());
127    assertHealth(fq);
128    fq.appendDigit(3, 1, true);
129    assertEquals("Failed on append", u"1203E0", fq.toNumberString());
130    assertHealth(fq);
131    fq.appendDigit(0, 1, true);
132    assertEquals("Failed on append", u"1203E2", fq.toNumberString());
133    assertHealth(fq);
134    fq.appendDigit(4, 0, true);
135    assertEquals("Failed on append", u"1203004E0", fq.toNumberString());
136    assertHealth(fq);
137    fq.appendDigit(0, 0, true);
138    assertEquals("Failed on append", u"1203004E1", fq.toNumberString());
139    assertHealth(fq);
140    fq.appendDigit(5, 0, false);
141    assertEquals("Failed on append", u"120300405E-1", fq.toNumberString());
142    assertHealth(fq);
143    fq.appendDigit(6, 0, false);
144    assertEquals("Failed on append", u"1203004056E-2", fq.toNumberString());
145    assertHealth(fq);
146    fq.appendDigit(7, 3, false);
147    assertEquals("Failed on append", u"12030040560007E-6", fq.toNumberString());
148    assertHealth(fq);
149    UnicodeString baseExpected(u"12030040560007");
150    for (int i = 0; i < 10; i++) {
151        fq.appendDigit(8, 0, false);
152        baseExpected.append(u'8');
153        UnicodeString expected(baseExpected);
154        expected.append(u"E-");
155        if (i >= 3) {
156            expected.append(u'1');
157        }
158        expected.append(((7 + i) % 10) + u'0');
159        assertEquals("Failed on append", expected, fq.toNumberString());
160        assertHealth(fq);
161    }
162    fq.appendDigit(9, 2, false);
163    baseExpected.append(u"009");
164    UnicodeString expected(baseExpected);
165    expected.append(u"E-19");
166    assertEquals("Failed on append", expected, fq.toNumberString());
167    assertHealth(fq);
168}
169
170void DecimalQuantityTest::testConvertToAccurateDouble() {
171    // based on https://github.com/google/double-conversion/issues/28
172    static double hardDoubles[] = {
173            1651087494906221570.0,
174            -5074790912492772E-327,
175            83602530019752571E-327,
176            2.207817077636718750000000000000,
177            1.818351745605468750000000000000,
178            3.941719055175781250000000000000,
179            3.738609313964843750000000000000,
180            3.967735290527343750000000000000,
181            1.328025817871093750000000000000,
182            3.920967102050781250000000000000,
183            1.015235900878906250000000000000,
184            1.335227966308593750000000000000,
185            1.344520568847656250000000000000,
186            2.879127502441406250000000000000,
187            3.695838928222656250000000000000,
188            1.845344543457031250000000000000,
189            3.793952941894531250000000000000,
190            3.211402893066406250000000000000,
191            2.565971374511718750000000000000,
192            0.965156555175781250000000000000,
193            2.700004577636718750000000000000,
194            0.767097473144531250000000000000,
195            1.780448913574218750000000000000,
196            2.624839782714843750000000000000,
197            1.305290222167968750000000000000,
198            3.834922790527343750000000000000,};
199
200    static double integerDoubles[] = {
201            51423,
202            51423e10,
203            4.503599627370496E15,
204            6.789512076111555E15,
205            9.007199254740991E15,
206            9.007199254740992E15};
207
208    for (double d : hardDoubles) {
209        checkDoubleBehavior(d, true);
210    }
211
212    for (double d : integerDoubles) {
213        checkDoubleBehavior(d, false);
214    }
215
216    assertDoubleEquals(u"NaN check failed", NAN, DecimalQuantity().setToDouble(NAN).toDouble());
217    assertDoubleEquals(
218            u"Inf check failed", INFINITY, DecimalQuantity().setToDouble(INFINITY).toDouble());
219    assertDoubleEquals(
220            u"-Inf check failed", -INFINITY, DecimalQuantity().setToDouble(-INFINITY).toDouble());
221
222    // Generate random doubles
223    for (int32_t i = 0; i < 10000; i++) {
224        uint8_t bytes[8];
225        for (int32_t j = 0; j < 8; j++) {
226            bytes[j] = static_cast<uint8_t>(rand() % 256);
227        }
228        double d;
229        uprv_memcpy(&d, bytes, 8);
230        if (std::isnan(d) || !std::isfinite(d)) { continue; }
231        checkDoubleBehavior(d, false);
232    }
233}
234
235void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
236    struct TestCase {
237        double d;
238        int32_t maxFrac;
239        RoundingMode roundingMode;
240        bool usesExact;
241    } cases[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
242                 {1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN, false},
243                 {1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN, false},
244                 {1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN, true},
245                 {1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
246                 {1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN, true},
247                 {1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN, false},
248                 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN, false},
249                 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING, true},
250                 {1.235, 1, RoundingMode::UNUM_ROUND_CEILING, false},
251                 {1.235, 2, RoundingMode::UNUM_ROUND_CEILING, false},
252                 {1.235, 3, RoundingMode::UNUM_ROUND_CEILING, true}};
253
254    UErrorCode status = U_ZERO_ERROR;
255    for (TestCase cas : cases) {
256        DecimalQuantity fq;
257        fq.setToDouble(cas.d);
258        assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
259        fq.roundToMagnitude(-cas.maxFrac, cas.roundingMode, status);
260        assertSuccess("Rounding to magnitude", status);
261        if (cas.usesExact != fq.isExplicitExactDouble()) {
262            errln(UnicodeString(u"Using approximate double after rounding: ") + fq.toString());
263        }
264    }
265}
266
267#endif /* #if !UCONFIG_NO_FORMATTING */
268