| // © 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 && !UPRV_INCOMPLETE_CPP11_SUPPORT |
| |
| #include "number_decimalquantity.h" |
| #include "math.h" |
| #include <cmath> |
| #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(testAppend); |
| TESTCASE_AUTO(testConvertToAccurateDouble); |
| TESTCASE_AUTO(testUseApproximateDoubleWhenAble); |
| 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(); |
| assertDoubleEquals( |
| UnicodeString(u"Initial construction from hard double: ") + baseStr, |
| d, fq.toDouble()); |
| 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"1234123412341234E0", fq.toNumberString()); |
| assertHealth(fq); |
| // Long -> Bytes |
| fq.appendDigit(5, 0, true); |
| assertTrue("Should be using byte array", fq.isUsingBytes()); |
| assertEquals("Failed on multiply", u"12341234123412345E0", fq.toNumberString()); |
| 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"123412341234E5", fq.toNumberString()); |
| assertHealth(fq); |
| } |
| |
| void DecimalQuantityTest::testAppend() { |
| DecimalQuantity fq; |
| fq.appendDigit(1, 0, true); |
| assertEquals("Failed on append", u"1E0", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(2, 0, true); |
| assertEquals("Failed on append", u"12E0", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(3, 1, true); |
| assertEquals("Failed on append", u"1203E0", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(0, 1, true); |
| assertEquals("Failed on append", u"1203E2", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(4, 0, true); |
| assertEquals("Failed on append", u"1203004E0", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(0, 0, true); |
| assertEquals("Failed on append", u"1203004E1", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(5, 0, false); |
| assertEquals("Failed on append", u"120300405E-1", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(6, 0, false); |
| assertEquals("Failed on append", u"1203004056E-2", fq.toNumberString()); |
| assertHealth(fq); |
| fq.appendDigit(7, 3, false); |
| assertEquals("Failed on append", u"12030040560007E-6", fq.toNumberString()); |
| assertHealth(fq); |
| UnicodeString baseExpected(u"12030040560007"); |
| for (int i = 0; i < 10; i++) { |
| fq.appendDigit(8, 0, false); |
| baseExpected.append(u'8'); |
| UnicodeString expected(baseExpected); |
| expected.append(u"E-"); |
| if (i >= 3) { |
| expected.append(u'1'); |
| } |
| expected.append(((7 + i) % 10) + u'0'); |
| assertEquals("Failed on append", expected, fq.toNumberString()); |
| assertHealth(fq); |
| } |
| fq.appendDigit(9, 2, false); |
| baseExpected.append(u"009"); |
| UnicodeString expected(baseExpected); |
| expected.append(u"E-19"); |
| assertEquals("Failed on append", expected, fq.toNumberString()); |
| 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() { |
| 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()); |
| } |
| } |
| } |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |