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