| // © 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); |
| if (!quick) { |
| // Slow test: run in exhaustive mode only |
| TESTCASE_AUTO(testConvertToAccurateDouble); |
| } |
| TESTCASE_AUTO(testUseApproximateDoubleWhenAble); |
| TESTCASE_AUTO(testHardDoubleConversion); |
| TESTCASE_AUTO(testToDouble); |
| TESTCASE_AUTO(testMaxDigits); |
| TESTCASE_AUTO(testNickelRounding); |
| TESTCASE_AUTO(testCompactDecimalSuppressedExponent); |
| TESTCASE_AUTO(testSuppressedExponentUnchangedByInitialScaling); |
| 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 0:0 long 0E0>"); |
| fq.setToInt(51423); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 51423E0>"); |
| fq.adjustMagnitude(-3); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 51423E-3>"); |
| |
| fq.setToLong(90909090909000L); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 90909090909E3>"); |
| fq.setMinInteger(2); |
| fq.applyMaxInteger(5); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 2:0 long 9E3>"); |
| fq.setMinFraction(3); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 9E3>"); |
| |
| fq.setToDouble(987.654321); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987654321E-6>"); |
| fq.roundToInfinity(); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987654321E-6>"); |
| fq.roundToIncrement(0.005, RoundingMode::UNUM_ROUND_HALFEVEN, status); |
| assertSuccess("Rounding to increment", status); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 long 987655E-3>"); |
| fq.roundToMagnitude(-2, RoundingMode::UNUM_ROUND_HALFEVEN, status); |
| assertSuccess("Rounding to magnitude", status); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 2:-3 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); |
| // Bytes with popFromLeft |
| fq.setToDecNumber({"999999999999999999"}, status); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 bytes 999999999999999999E0>"); |
| fq.applyMaxInteger(17); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 bytes 99999999999999999E0>"); |
| fq.applyMaxInteger(16); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 9999999999999999E0>"); |
| fq.applyMaxInteger(15); |
| assertToStringAndHealth(fq, u"<DecimalQuantity 0:0 long 999999999999999E0>"); |
| } |
| |
| void DecimalQuantityTest::testCopyMove() { |
| // Small numbers (fits in BCD long) |
| { |
| DecimalQuantity a; |
| a.setToLong(1234123412341234L); |
| DecimalQuantity b = a; // copy constructor |
| assertToStringAndHealth(a, u"<DecimalQuantity 0:0 long 1234123412341234E0>"); |
| assertToStringAndHealth(b, u"<DecimalQuantity 0:0 long 1234123412341234E0>"); |
| DecimalQuantity c(std::move(a)); // move constructor |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 1234123412341234E0>"); |
| c.setToLong(54321L); |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 54321E0>"); |
| c = b; // copy assignment |
| assertToStringAndHealth(b, u"<DecimalQuantity 0:0 long 1234123412341234E0>"); |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 1234123412341234E0>"); |
| b.setToLong(45678); |
| c.setToLong(56789); |
| c = std::move(b); // move assignment |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 long 45678E0>"); |
| a = std::move(c); // move assignment to a defunct object |
| assertToStringAndHealth(a, u"<DecimalQuantity 0:0 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 0:0 bytes 1234567890123456789E0>"); |
| assertToStringAndHealth(b, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>"); |
| DecimalQuantity c(std::move(a)); // move constructor |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>"); |
| c.setToDecNumber({"9876543210987654321", -1}, status); |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 9876543210987654321E0>"); |
| c = b; // copy assignment |
| assertToStringAndHealth(b, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>"); |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 1234567890123456789E0>"); |
| b.setToDecNumber({"876543210987654321", -1}, status); |
| c.setToDecNumber({"987654321098765432", -1}, status); |
| c = std::move(b); // move assignment |
| assertToStringAndHealth(c, u"<DecimalQuantity 0:0 bytes 876543210987654321E0>"); |
| a = std::move(c); // move assignment to a defunct object |
| assertToStringAndHealth(a, u"<DecimalQuantity 0:0 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, |
| 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 exactDoubles[] = { |
| 51423, |
| 51423e10, |
| -5074790912492772E-327, |
| 83602530019752571E-327, |
| 4.503599627370496E15, |
| 6.789512076111555E15, |
| 9.007199254740991E15, |
| 9.007199254740992E15}; |
| |
| for (double d : hardDoubles) { |
| checkDoubleBehavior(d, true); |
| } |
| |
| for (double d : exactDoubles) { |
| 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.setMinInteger(0); |
| dq.applyMaxInteger(2); |
| dq.setMinFraction(0); |
| dq.roundToMagnitude(-2, UNUM_ROUND_FLOOR, status); |
| 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); |
| assertEquals("Should trim, toDecNum", "76.54", copy.toPlainString()); |
| } |
| |
| void DecimalQuantityTest::testNickelRounding() { |
| IcuTestErrorCode status(*this, "testNickelRounding"); |
| struct TestCase { |
| double input; |
| int32_t magnitude; |
| UNumberFormatRoundingMode roundingMode; |
| const char16_t* expected; |
| } cases[] = { |
| {1.000, -2, UNUM_ROUND_HALFEVEN, u"1"}, |
| {1.001, -2, UNUM_ROUND_HALFEVEN, u"1"}, |
| {1.010, -2, UNUM_ROUND_HALFEVEN, u"1"}, |
| {1.020, -2, UNUM_ROUND_HALFEVEN, u"1"}, |
| {1.024, -2, UNUM_ROUND_HALFEVEN, u"1"}, |
| {1.025, -2, UNUM_ROUND_HALFEVEN, u"1"}, |
| {1.025, -2, UNUM_ROUND_HALFDOWN, u"1"}, |
| {1.025, -2, UNUM_ROUND_HALFUP, u"1.05"}, |
| {1.026, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.030, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.040, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.050, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.060, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.070, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.074, -2, UNUM_ROUND_HALFEVEN, u"1.05"}, |
| {1.075, -2, UNUM_ROUND_HALFDOWN, u"1.05"}, |
| {1.075, -2, UNUM_ROUND_HALFUP, u"1.1"}, |
| {1.075, -2, UNUM_ROUND_HALFEVEN, u"1.1"}, |
| {1.076, -2, UNUM_ROUND_HALFEVEN, u"1.1"}, |
| {1.080, -2, UNUM_ROUND_HALFEVEN, u"1.1"}, |
| {1.090, -2, UNUM_ROUND_HALFEVEN, u"1.1"}, |
| {1.099, -2, UNUM_ROUND_HALFEVEN, u"1.1"}, |
| {1.999, -2, UNUM_ROUND_HALFEVEN, u"2"}, |
| {2.25, -1, UNUM_ROUND_HALFEVEN, u"2"}, |
| {2.25, -1, UNUM_ROUND_HALFUP, u"2.5"}, |
| {2.75, -1, UNUM_ROUND_HALFDOWN, u"2.5"}, |
| {2.75, -1, UNUM_ROUND_HALFEVEN, u"3"}, |
| {3.00, -1, UNUM_ROUND_CEILING, u"3"}, |
| {3.25, -1, UNUM_ROUND_CEILING, u"3.5"}, |
| {3.50, -1, UNUM_ROUND_CEILING, u"3.5"}, |
| {3.75, -1, UNUM_ROUND_CEILING, u"4"}, |
| {4.00, -1, UNUM_ROUND_FLOOR, u"4"}, |
| {4.25, -1, UNUM_ROUND_FLOOR, u"4"}, |
| {4.50, -1, UNUM_ROUND_FLOOR, u"4.5"}, |
| {4.75, -1, UNUM_ROUND_FLOOR, u"4.5"}, |
| {5.00, -1, UNUM_ROUND_UP, u"5"}, |
| {5.25, -1, UNUM_ROUND_UP, u"5.5"}, |
| {5.50, -1, UNUM_ROUND_UP, u"5.5"}, |
| {5.75, -1, UNUM_ROUND_UP, u"6"}, |
| {6.00, -1, UNUM_ROUND_DOWN, u"6"}, |
| {6.25, -1, UNUM_ROUND_DOWN, u"6"}, |
| {6.50, -1, UNUM_ROUND_DOWN, u"6.5"}, |
| {6.75, -1, UNUM_ROUND_DOWN, u"6.5"}, |
| {7.00, -1, UNUM_ROUND_UNNECESSARY, u"7"}, |
| {7.50, -1, UNUM_ROUND_UNNECESSARY, u"7.5"}, |
| }; |
| for (const auto& cas : cases) { |
| UnicodeString message = DoubleToUnicodeString(cas.input) + u" @ " + Int64ToUnicodeString(cas.magnitude) + u" / " + Int64ToUnicodeString(cas.roundingMode); |
| status.setScope(message); |
| DecimalQuantity dq; |
| dq.setToDouble(cas.input); |
| dq.roundToNickel(cas.magnitude, cas.roundingMode, status); |
| status.errIfFailureAndReset(); |
| UnicodeString actual = dq.toPlainString(); |
| assertEquals(message, cas.expected, actual); |
| } |
| status.setScope(""); |
| DecimalQuantity dq; |
| dq.setToDouble(7.1); |
| dq.roundToNickel(-1, UNUM_ROUND_UNNECESSARY, status); |
| status.expectErrorAndReset(U_FORMAT_INEXACT_ERROR); |
| } |
| |
| void DecimalQuantityTest::testCompactDecimalSuppressedExponent() { |
| IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent"); |
| Locale ulocale("fr-FR"); |
| |
| struct TestCase { |
| UnicodeString skeleton; |
| double input; |
| const char16_t* expectedString; |
| int64_t expectedLong; |
| double expectedDouble; |
| const char16_t* expectedPlainString; |
| int32_t expectedSuppressedExponent; |
| } cases[] = { |
| // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent |
| {u"", 123456789, u"123 456 789", 123456789L, 123456789.0, u"123456789", 0}, |
| {u"compact-long", 123456789, u"123 millions", 123000000L, 123000000.0, u"123000000", 6}, |
| {u"compact-short", 123456789, u"123 M", 123000000L, 123000000.0, u"123000000", 6}, |
| {u"scientific", 123456789, u"1,234568E8", 123456800L, 123456800.0, u"123456800", 8}, |
| |
| {u"", 1234567, u"1 234 567", 1234567L, 1234567.0, u"1234567", 0}, |
| {u"compact-long", 1234567, u"1,2 million", 1200000L, 1200000.0, u"1200000", 6}, |
| {u"compact-short", 1234567, u"1,2 M", 1200000L, 1200000.0, u"1200000", 6}, |
| {u"scientific", 1234567, u"1,234567E6", 1234567L, 1234567.0, u"1234567", 6}, |
| |
| {u"", 123456, u"123 456", 123456L, 123456.0, u"123456", 0}, |
| {u"compact-long", 123456, u"123 mille", 123000L, 123000.0, u"123000", 3}, |
| {u"compact-short", 123456, u"123 k", 123000L, 123000.0, u"123000", 3}, |
| {u"scientific", 123456, u"1,23456E5", 123456L, 123456.0, u"123456", 5}, |
| |
| {u"", 123, u"123", 123L, 123.0, u"123", 0}, |
| {u"compact-long", 123, u"123", 123L, 123.0, u"123", 0}, |
| {u"compact-short", 123, u"123", 123L, 123.0, u"123", 0}, |
| {u"scientific", 123, u"1,23E2", 123L, 123.0, u"123", 2}, |
| |
| {u"", 1.2, u"1,2", 1L, 1.2, u"1.2", 0}, |
| {u"compact-long", 1.2, u"1,2", 1L, 1.2, u"1.2", 0}, |
| {u"compact-short", 1.2, u"1,2", 1L, 1.2, u"1.2", 0}, |
| {u"scientific", 1.2, u"1,2E0", 1L, 1.2, u"1.2", 0}, |
| |
| {u"", 0.12, u"0,12", 0L, 0.12, u"0.12", 0}, |
| {u"compact-long", 0.12, u"0,12", 0L, 0.12, u"0.12", 0}, |
| {u"compact-short", 0.12, u"0,12", 0L, 0.12, u"0.12", 0}, |
| {u"scientific", 0.12, u"1,2E-1", 0L, 0.12, u"0.12", -1}, |
| |
| {u"", 0.012, u"0,012", 0L, 0.012, u"0.012", 0}, |
| {u"compact-long", 0.012, u"0,012", 0L, 0.012, u"0.012", 0}, |
| {u"compact-short", 0.012, u"0,012", 0L, 0.012, u"0.012", 0}, |
| {u"scientific", 0.012, u"1,2E-2", 0L, 0.012, u"0.012", -2}, |
| |
| {u"", 999.9, u"999,9", 999L, 999.9, u"999.9", 0}, |
| {u"compact-long", 999.9, u"1 millier", 1000L, 1000.0, u"1000", 3}, |
| {u"compact-short", 999.9, u"1 k", 1000L, 1000.0, u"1000", 3}, |
| {u"scientific", 999.9, u"9,999E2", 999L, 999.9, u"999.9", 2}, |
| |
| {u"", 1000.0, u"1 000", 1000L, 1000.0, u"1000", 0}, |
| {u"compact-long", 1000.0, u"1 millier", 1000L, 1000.0, u"1000", 3}, |
| {u"compact-short", 1000.0, u"1 k", 1000L, 1000.0, u"1000", 3}, |
| {u"scientific", 1000.0, u"1E3", 1000L, 1000.0, u"1000", 3}, |
| }; |
| for (const auto& cas : cases) { |
| // test the helper methods used to compute plural operand values |
| |
| LocalizedNumberFormatter formatter = |
| NumberFormatter::forSkeleton(cas.skeleton, status) |
| .locale(ulocale); |
| FormattedNumber fn = formatter.formatDouble(cas.input, status); |
| DecimalQuantity dq; |
| fn.getDecimalQuantity(dq, status); |
| UnicodeString actualString = fn.toString(status); |
| int64_t actualLong = dq.toLong(); |
| double actualDouble = dq.toDouble(); |
| UnicodeString actualPlainString = dq.toPlainString(); |
| int32_t actualSuppressedExponent = dq.getExponent(); |
| |
| assertEquals( |
| u"formatted number " + cas.skeleton + u" toString: " + cas.input, |
| cas.expectedString, |
| actualString); |
| assertEquals( |
| u"compact decimal " + cas.skeleton + u" toLong: " + cas.input, |
| cas.expectedLong, |
| actualLong); |
| assertDoubleEquals( |
| u"compact decimal " + cas.skeleton + u" toDouble: " + cas.input, |
| cas.expectedDouble, |
| actualDouble); |
| assertEquals( |
| u"formatted number " + cas.skeleton + u" toPlainString: " + cas.input, |
| cas.expectedPlainString, |
| actualPlainString); |
| assertEquals( |
| u"compact decimal " + cas.skeleton + u" suppressed exponent: " + cas.input, |
| cas.expectedSuppressedExponent, |
| actualSuppressedExponent); |
| |
| // test the actual computed values of the plural operands |
| |
| double expectedNOperand = cas.expectedDouble; |
| double expectedIOperand = cas.expectedLong; |
| double expectedEOperand = cas.expectedSuppressedExponent; |
| double actualNOperand = dq.getPluralOperand(PLURAL_OPERAND_N); |
| double actualIOperand = dq.getPluralOperand(PLURAL_OPERAND_I); |
| double actualEOperand = dq.getPluralOperand(PLURAL_OPERAND_E); |
| |
| assertDoubleEquals( |
| u"compact decimal " + cas.skeleton + u" n operand: " + cas.input, |
| expectedNOperand, |
| actualNOperand); |
| assertDoubleEquals( |
| u"compact decimal " + cas.skeleton + u" i operand: " + cas.input, |
| expectedIOperand, |
| actualIOperand); |
| assertDoubleEquals( |
| u"compact decimal " + cas.skeleton + " e operand: " + cas.input, |
| expectedEOperand, |
| actualEOperand); |
| } |
| } |
| |
| void DecimalQuantityTest::testSuppressedExponentUnchangedByInitialScaling() { |
| IcuTestErrorCode status(*this, "testCompactDecimalSuppressedExponent"); |
| Locale ulocale("fr-FR"); |
| LocalizedNumberFormatter withLocale = NumberFormatter::withLocale(ulocale); |
| LocalizedNumberFormatter compactLong = |
| withLocale.notation(Notation::compactLong()); |
| LocalizedNumberFormatter compactScaled = |
| compactLong.scale(Scale::powerOfTen(3)); |
| |
| struct TestCase { |
| int32_t input; |
| UnicodeString expectedString; |
| double expectedNOperand; |
| double expectedIOperand; |
| double expectedEOperand; |
| } cases[] = { |
| // input, compact long string output, |
| // compact n operand, compact i operand, compact e operand |
| {123456789, "123 millions", 123000000.0, 123000000.0, 6.0}, |
| {1234567, "1,2 million", 1200000.0, 1200000.0, 6.0}, |
| {123456, "123 mille", 123000.0, 123000.0, 3.0}, |
| {123, "123", 123.0, 123.0, 0.0}, |
| }; |
| |
| for (const auto& cas : cases) { |
| FormattedNumber fnCompactScaled = compactScaled.formatInt(cas.input, status); |
| DecimalQuantity dqCompactScaled; |
| fnCompactScaled.getDecimalQuantity(dqCompactScaled, status); |
| double compactScaledEOperand = dqCompactScaled.getPluralOperand(PLURAL_OPERAND_E); |
| |
| FormattedNumber fnCompact = compactLong.formatInt(cas.input, status); |
| DecimalQuantity dqCompact; |
| fnCompact.getDecimalQuantity(dqCompact, status); |
| UnicodeString actualString = fnCompact.toString(status); |
| double compactNOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_N); |
| double compactIOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_I); |
| double compactEOperand = dqCompact.getPluralOperand(PLURAL_OPERAND_E); |
| assertEquals( |
| u"formatted number " + Int64ToUnicodeString(cas.input) + " compactLong toString: ", |
| cas.expectedString, |
| actualString); |
| assertDoubleEquals( |
| u"compact decimal " + DoubleToUnicodeString(cas.input) + ", n operand vs. expected", |
| cas.expectedNOperand, |
| compactNOperand); |
| assertDoubleEquals( |
| u"compact decimal " + DoubleToUnicodeString(cas.input) + ", i operand vs. expected", |
| cas.expectedIOperand, |
| compactIOperand); |
| assertDoubleEquals( |
| u"compact decimal " + DoubleToUnicodeString(cas.input) + ", e operand vs. expected", |
| cas.expectedEOperand, |
| compactEOperand); |
| |
| // By scaling by 10^3 in a locale that has words / compact notation |
| // based on powers of 10^3, we guarantee that the suppressed |
| // exponent will differ by 3. |
| assertDoubleEquals( |
| u"decimal " + DoubleToUnicodeString(cas.input) + ", e operand for compact vs. compact scaled", |
| compactEOperand + 3, |
| compactScaledEOperand); |
| } |
| } |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |