]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/test/intltest/numbertest_decimalquantity.cpp
ICU-62107.0.1.tar.gz
[apple/icu.git] / icuSources / test / intltest / numbertest_decimalquantity.cpp
diff --git a/icuSources/test/intltest/numbertest_decimalquantity.cpp b/icuSources/test/intltest/numbertest_decimalquantity.cpp
new file mode 100644 (file)
index 0000000..48b9f91
--- /dev/null
@@ -0,0 +1,383 @@
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+#include "number_decimalquantity.h"
+#include "number_decnum.h"
+#include "math.h"
+#include <cmath>
+#include "number_utils.h"
+#include "numbertest.h"
+
+void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
+    if (exec) {
+        logln("TestSuite DecimalQuantityTest: ");
+    }
+    TESTCASE_AUTO_BEGIN;
+        TESTCASE_AUTO(testDecimalQuantityBehaviorStandalone);
+        TESTCASE_AUTO(testSwitchStorage);;
+        TESTCASE_AUTO(testCopyMove);
+        TESTCASE_AUTO(testAppend);
+        TESTCASE_AUTO(testConvertToAccurateDouble);
+        TESTCASE_AUTO(testUseApproximateDoubleWhenAble);
+        TESTCASE_AUTO(testHardDoubleConversion);
+        TESTCASE_AUTO(testToDouble);
+        TESTCASE_AUTO(testMaxDigits);
+    TESTCASE_AUTO_END;
+}
+
+void DecimalQuantityTest::assertDoubleEquals(UnicodeString message, double a, double b) {
+    if (a == b) {
+        return;
+    }
+
+    double diff = a - b;
+    diff = diff < 0 ? -diff : diff;
+    double bound = a < 0 ? -a * 1e-6 : a * 1e-6;
+    if (diff > bound) {
+        errln(message + u": " + DoubleToUnicodeString(a) + u" vs " + DoubleToUnicodeString(b) + u" differ by " + DoubleToUnicodeString(diff));
+    }
+}
+
+void DecimalQuantityTest::assertHealth(const DecimalQuantity &fq) {
+    const char16_t* health = fq.checkHealth();
+    if (health != nullptr) {
+        errln(UnicodeString(u"HEALTH FAILURE: ") + UnicodeString(health) + u": " + fq.toString());
+    }
+}
+
+void
+DecimalQuantityTest::assertToStringAndHealth(const DecimalQuantity &fq, const UnicodeString &expected) {
+    UnicodeString actual = fq.toString();
+    assertEquals("DecimalQuantity toString failed", expected, actual);
+    assertHealth(fq);
+}
+
+void DecimalQuantityTest::checkDoubleBehavior(double d, bool explicitRequired) {
+    DecimalQuantity fq;
+    fq.setToDouble(d);
+    if (explicitRequired) {
+        assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
+    }
+    UnicodeString baseStr = fq.toString();
+    fq.roundToInfinity();
+    UnicodeString newStr = fq.toString();
+    if (explicitRequired) {
+        assertTrue("Should not be using approximate double", fq.isExplicitExactDouble());
+    }
+    assertDoubleEquals(
+        UnicodeString(u"After conversion to exact BCD (double): ") + baseStr + u" vs " + newStr,
+        d, fq.toDouble());
+}
+
+void DecimalQuantityTest::testDecimalQuantityBehaviorStandalone() {
+    UErrorCode status = U_ZERO_ERROR;
+    DecimalQuantity fq;
+    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 0E0>");
+    fq.setToInt(51423);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E0>");
+    fq.adjustMagnitude(-3);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 51423E-3>");
+    fq.setToLong(999999999999000L);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
+    fq.setIntegerLength(2, 5);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
+    fq.setFractionLength(3, 6);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
+    fq.setToDouble(987.654321);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+    fq.roundToInfinity();
+    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+    fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, 3, status);
+    assertSuccess("Rounding to increment", status);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
+    fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status);
+    assertSuccess("Rounding to magnitude", status);
+    assertToStringAndHealth(fq, u"<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
+}
+
+void DecimalQuantityTest::testSwitchStorage() {
+    UErrorCode status = U_ZERO_ERROR;
+    DecimalQuantity fq;
+
+    fq.setToLong(1234123412341234L);
+    assertFalse("Should not be using byte array", fq.isUsingBytes());
+    assertEquals("Failed on initialize", u"1.234123412341234E+15", fq.toScientificString());
+    assertHealth(fq);
+    // Long -> Bytes
+    fq.appendDigit(5, 0, true);
+    assertTrue("Should be using byte array", fq.isUsingBytes());
+    assertEquals("Failed on multiply", u"1.2341234123412345E+16", fq.toScientificString());
+    assertHealth(fq);
+    // Bytes -> Long
+    fq.roundToMagnitude(5, RoundingMode::UNUM_ROUND_HALFEVEN, status);
+    assertSuccess("Rounding to magnitude", status);
+    assertFalse("Should not be using byte array", fq.isUsingBytes());
+    assertEquals("Failed on round", u"1.23412341234E+16", fq.toScientificString());
+    assertHealth(fq);
+}
+
+void DecimalQuantityTest::testCopyMove() {
+    // Small numbers (fits in BCD long)
+    {
+        DecimalQuantity a;
+        a.setToLong(1234123412341234L);
+        DecimalQuantity b = a; // copy constructor
+        assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
+        assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
+        DecimalQuantity c(std::move(a)); // move constructor
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
+        c.setToLong(54321L);
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 54321E0>");
+        c = b; // copy assignment
+        assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 1234123412341234E0>");
+        b.setToLong(45678);
+        c.setToLong(56789);
+        c = std::move(b); // move assignment
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 long 45678E0>");
+        a = std::move(c); // move assignment to a defunct object
+        assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 long 45678E0>");
+    }
+
+    // Large numbers (requires byte allocation)
+    {
+        IcuTestErrorCode status(*this, "testCopyMove");
+        DecimalQuantity a;
+        a.setToDecNumber({"1234567890123456789", -1}, status);
+        DecimalQuantity b = a; // copy constructor
+        assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
+        assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
+        DecimalQuantity c(std::move(a)); // move constructor
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
+        c.setToDecNumber({"9876543210987654321", -1}, status);
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 9876543210987654321E0>");
+        c = b; // copy assignment
+        assertToStringAndHealth(b, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 1234567890123456789E0>");
+        b.setToDecNumber({"876543210987654321", -1}, status);
+        c.setToDecNumber({"987654321098765432", -1}, status);
+        c = std::move(b); // move assignment
+        assertToStringAndHealth(c, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>");
+        a = std::move(c); // move assignment to a defunct object
+        assertToStringAndHealth(a, u"<DecimalQuantity 999:0:0:-999 bytes 876543210987654321E0>");
+    }
+}
+
+void DecimalQuantityTest::testAppend() {
+    DecimalQuantity fq;
+    fq.appendDigit(1, 0, true);
+    assertEquals("Failed on append", u"1E+0", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(2, 0, true);
+    assertEquals("Failed on append", u"1.2E+1", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(3, 1, true);
+    assertEquals("Failed on append", u"1.203E+3", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(0, 1, true);
+    assertEquals("Failed on append", u"1.203E+5", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(4, 0, true);
+    assertEquals("Failed on append", u"1.203004E+6", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(0, 0, true);
+    assertEquals("Failed on append", u"1.203004E+7", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(5, 0, false);
+    assertEquals("Failed on append", u"1.20300405E+7", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(6, 0, false);
+    assertEquals("Failed on append", u"1.203004056E+7", fq.toScientificString());
+    assertHealth(fq);
+    fq.appendDigit(7, 3, false);
+    assertEquals("Failed on append", u"1.2030040560007E+7", fq.toScientificString());
+    assertHealth(fq);
+    UnicodeString baseExpected(u"1.2030040560007");
+    for (int i = 0; i < 10; i++) {
+        fq.appendDigit(8, 0, false);
+        baseExpected.append(u'8');
+        UnicodeString expected(baseExpected);
+        expected.append(u"E+7");
+        assertEquals("Failed on append", expected, fq.toScientificString());
+        assertHealth(fq);
+    }
+    fq.appendDigit(9, 2, false);
+    baseExpected.append(u"009");
+    UnicodeString expected(baseExpected);
+    expected.append(u"E+7");
+    assertEquals("Failed on append", expected, fq.toScientificString());
+    assertHealth(fq);
+}
+
+void DecimalQuantityTest::testConvertToAccurateDouble() {
+    // based on https://github.com/google/double-conversion/issues/28
+    static double hardDoubles[] = {
+            1651087494906221570.0,
+            -5074790912492772E-327,
+            83602530019752571E-327,
+            2.207817077636718750000000000000,
+            1.818351745605468750000000000000,
+            3.941719055175781250000000000000,
+            3.738609313964843750000000000000,
+            3.967735290527343750000000000000,
+            1.328025817871093750000000000000,
+            3.920967102050781250000000000000,
+            1.015235900878906250000000000000,
+            1.335227966308593750000000000000,
+            1.344520568847656250000000000000,
+            2.879127502441406250000000000000,
+            3.695838928222656250000000000000,
+            1.845344543457031250000000000000,
+            3.793952941894531250000000000000,
+            3.211402893066406250000000000000,
+            2.565971374511718750000000000000,
+            0.965156555175781250000000000000,
+            2.700004577636718750000000000000,
+            0.767097473144531250000000000000,
+            1.780448913574218750000000000000,
+            2.624839782714843750000000000000,
+            1.305290222167968750000000000000,
+            3.834922790527343750000000000000,};
+
+    static double integerDoubles[] = {
+            51423,
+            51423e10,
+            4.503599627370496E15,
+            6.789512076111555E15,
+            9.007199254740991E15,
+            9.007199254740992E15};
+
+    for (double d : hardDoubles) {
+        checkDoubleBehavior(d, true);
+    }
+
+    for (double d : integerDoubles) {
+        checkDoubleBehavior(d, false);
+    }
+
+    assertDoubleEquals(u"NaN check failed", NAN, DecimalQuantity().setToDouble(NAN).toDouble());
+    assertDoubleEquals(
+            u"Inf check failed", INFINITY, DecimalQuantity().setToDouble(INFINITY).toDouble());
+    assertDoubleEquals(
+            u"-Inf check failed", -INFINITY, DecimalQuantity().setToDouble(-INFINITY).toDouble());
+
+    // Generate random doubles
+    for (int32_t i = 0; i < 10000; i++) {
+        uint8_t bytes[8];
+        for (int32_t j = 0; j < 8; j++) {
+            bytes[j] = static_cast<uint8_t>(rand() % 256);
+        }
+        double d;
+        uprv_memcpy(&d, bytes, 8);
+        if (std::isnan(d) || !std::isfinite(d)) { continue; }
+        checkDoubleBehavior(d, false);
+    }
+}
+
+void DecimalQuantityTest::testUseApproximateDoubleWhenAble() {
+    static const struct TestCase {
+        double d;
+        int32_t maxFrac;
+        RoundingMode roundingMode;
+        bool usesExact;
+    } cases[] = {{1.2345678, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
+                 {1.2345678, 7, RoundingMode::UNUM_ROUND_HALFEVEN, false},
+                 {1.2345678, 12, RoundingMode::UNUM_ROUND_HALFEVEN, false},
+                 {1.2345678, 13, RoundingMode::UNUM_ROUND_HALFEVEN, true},
+                 {1.235, 1, RoundingMode::UNUM_ROUND_HALFEVEN, false},
+                 {1.235, 2, RoundingMode::UNUM_ROUND_HALFEVEN, true},
+                 {1.235, 3, RoundingMode::UNUM_ROUND_HALFEVEN, false},
+                 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_HALFEVEN, false},
+                 {1.000000000000001, 0, RoundingMode::UNUM_ROUND_CEILING, true},
+                 {1.235, 1, RoundingMode::UNUM_ROUND_CEILING, false},
+                 {1.235, 2, RoundingMode::UNUM_ROUND_CEILING, false},
+                 {1.235, 3, RoundingMode::UNUM_ROUND_CEILING, true}};
+
+    UErrorCode status = U_ZERO_ERROR;
+    for (TestCase cas : cases) {
+        DecimalQuantity fq;
+        fq.setToDouble(cas.d);
+        assertTrue("Should be using approximate double", !fq.isExplicitExactDouble());
+        fq.roundToMagnitude(-cas.maxFrac, cas.roundingMode, status);
+        assertSuccess("Rounding to magnitude", status);
+        if (cas.usesExact != fq.isExplicitExactDouble()) {
+            errln(UnicodeString(u"Using approximate double after rounding: ") + fq.toString());
+        }
+    }
+}
+
+void DecimalQuantityTest::testHardDoubleConversion() {
+    static const struct TestCase {
+        double input;
+        const char16_t* expectedOutput;
+    } cases[] = {
+            { 512.0000000000017, u"512.0000000000017" },
+            { 4095.9999999999977, u"4095.9999999999977" },
+            { 4095.999999999998, u"4095.999999999998" },
+            { 4095.9999999999986, u"4095.9999999999986" },
+            { 4095.999999999999, u"4095.999999999999" },
+            { 4095.9999999999995, u"4095.9999999999995" },
+            { 4096.000000000001, u"4096.000000000001" },
+            { 4096.000000000002, u"4096.000000000002" },
+            { 4096.000000000003, u"4096.000000000003" },
+            { 4096.000000000004, u"4096.000000000004" },
+            { 4096.000000000005, u"4096.000000000005" },
+            { 4096.0000000000055, u"4096.0000000000055" },
+            { 4096.000000000006, u"4096.000000000006" },
+            { 4096.000000000007, u"4096.000000000007" } };
+
+    for (auto& cas : cases) {
+        DecimalQuantity q;
+        q.setToDouble(cas.input);
+        q.roundToInfinity();
+        UnicodeString actualOutput = q.toPlainString();
+        assertEquals("", cas.expectedOutput, actualOutput);
+    }
+}
+
+void DecimalQuantityTest::testToDouble() {
+    IcuTestErrorCode status(*this, "testToDouble");
+    static const struct TestCase {
+        const char* input; // char* for the decNumber constructor
+        double expected;
+    } cases[] = {
+            { "0", 0.0 },
+            { "514.23", 514.23 },
+            { "-3.142E-271", -3.142e-271 } };
+
+    for (auto& cas : cases) {
+        status.setScope(cas.input);
+        DecimalQuantity q;
+        q.setToDecNumber({cas.input, -1}, status);
+        double actual = q.toDouble();
+        assertEquals("Doubles should exactly equal", cas.expected, actual);
+    }
+}
+
+void DecimalQuantityTest::testMaxDigits() {
+    IcuTestErrorCode status(*this, "testMaxDigits");
+    DecimalQuantity dq;
+    dq.setToDouble(876.543);
+    dq.roundToInfinity();
+    dq.setIntegerLength(0, 2);
+    dq.setFractionLength(0, 2);
+    assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
+    assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
+    assertEquals("Should trim, toLong", 76LL, dq.toLong(true));
+    assertEquals("Should trim, toFractionLong", (int64_t) 54, (int64_t) dq.toFractionLong(false));
+    assertEquals("Should trim, toDouble", 76.54, dq.toDouble());
+    // To test DecNum output, check the round-trip.
+    DecNum dn;
+    dq.toDecNum(dn, status);
+    DecimalQuantity copy;
+    copy.setToDecNum(dn, status);
+    if (!logKnownIssue("13701")) {
+        assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString());
+    }
+}
+
+#endif /* #if !UCONFIG_NO_FORMATTING */