blob: 48b9f91e27a34265a4bbe9be0fcc9b6db44136bf [file] [log] [blame]
// © 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 */