From d6c6fa0404aa95257bbf6a01147953016257238e Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Tue, 17 Apr 2018 01:36:18 +0000 Subject: [PATCH] ICU-13634 DecimalQuantity work: Fixing integer overflow behavior in toLong and toFractionLong methods. Adding test for maxInt/maxFrac behavior in toScientificString and related methods. Updating a few test expectations in IntlTestDecimalFormatAPI::TestFixedDecimal, which is now passing. X-SVN-Rev: 41235 --- icu4c/source/i18n/number_decimalquantity.cpp | 77 ++++++++++--------- icu4c/source/i18n/number_decimalquantity.h | 9 ++- icu4c/source/i18n/plurrule.cpp | 10 ++- icu4c/source/i18n/plurrule_impl.h | 8 +- icu4c/source/test/intltest/dcfmapts.cpp | 10 +-- icu4c/source/test/intltest/numbertest.h | 1 + .../intltest/numbertest_decimalquantity.cpp | 27 ++++++- .../number/DecimalQuantity_AbstractBCD.java | 74 +++++++++--------- .../icu/impl/number/parse/ParsedNumber.java | 2 +- .../dev/test/number/DecimalQuantityTest.java | 16 ++++ .../dev/test/number/ExhaustiveNumberTest.java | 1 - .../src/com/ibm/icu/dev/test/TestFmwk.java | 3 + 12 files changed, 147 insertions(+), 91 deletions(-) diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index c61cd82e32f..0fa6dada975 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -168,8 +168,9 @@ uint64_t DecimalQuantity::getPositionFingerprint() const { void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode roundingMode, int32_t maxFrac, UErrorCode& status) { - // TODO: This is innefficient. Improve? - // TODO: Should we convert to decNumber instead? + // TODO(13701): This is innefficient. Improve? + // TODO(13701): Should we convert to decNumber instead? + roundToInfinity(); double temp = toDouble(); temp /= roundingIncrement; setToDouble(temp); @@ -246,7 +247,7 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { switch (operand) { case PLURAL_OPERAND_I: // Invert the negative sign if necessary - return static_cast(isNegative() ? -toLong() : toLong()); + return static_cast(isNegative() ? -toLong(true) : toLong(true)); case PLURAL_OPERAND_F: return static_cast(toFractionLong(true)); case PLURAL_OPERAND_T: @@ -260,6 +261,10 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { } } +bool DecimalQuantity::hasIntegerValue() const { + return scale >= 0; +} + int32_t DecimalQuantity::getUpperDisplayMagnitude() const { // If this assertion fails, you need to call roundToInfinity() or some other rounding method. // See the comment in the header file explaining the "isApproximate" field. @@ -489,11 +494,17 @@ void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { } } -int64_t DecimalQuantity::toLong() const { +int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const { // NOTE: Call sites should be guarded by fitsInLong(), like this: // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } + // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits. + U_ASSERT(truncateIfOverflow || fitsInLong()); int64_t result = 0L; - for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { + int32_t upperMagnitude = std::min(scale + precision, lOptPos) - 1; + if (truncateIfOverflow) { + upperMagnitude = std::min(upperMagnitude, 17); + } + for (int32_t magnitude = upperMagnitude; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } if (isNegative()) { @@ -502,13 +513,22 @@ int64_t DecimalQuantity::toLong() const { return result; } -int64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { - int64_t result = 0L; +uint64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { + uint64_t result = 0L; int32_t magnitude = -1; - for (; (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos)) && - magnitude >= rOptPos; magnitude--) { + int32_t lowerMagnitude = std::max(scale, rOptPos); + if (includeTrailingZeros) { + lowerMagnitude = std::min(lowerMagnitude, rReqPos); + } + for (; magnitude >= lowerMagnitude && result <= 1e18L; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + // Remove trailing zeros; this can happen during integer overflow cases. + if (!includeTrailingZeros) { + while (result > 0 && (result % 10) == 0) { + result /= 10; + } + } return result; } @@ -542,9 +562,9 @@ bool DecimalQuantity::fitsInLong() const { } double DecimalQuantity::toDouble() const { - if (isApproximate) { - return toDoubleFromOriginal(); - } + // If this assertion fails, you need to call roundToInfinity() or some other rounding method. + // See the comment in the header file explaining the "isApproximate" field. + U_ASSERT(!isApproximate); if (isNaN()) { return NAN; @@ -562,24 +582,6 @@ double DecimalQuantity::toDouble() const { &count); } -double DecimalQuantity::toDoubleFromOriginal() const { - double result = origDouble; - int32_t delta = origDelta; - if (delta >= 0) { - // 1e22 is the largest exact double. - for (; delta >= 22; delta -= 22) result *= 1e22; - result *= DOUBLE_MULTIPLIERS[delta]; - } else { - // 1e22 is the largest exact double. - for (; delta <= -22; delta += 22) result /= 1e22; - result /= DOUBLE_MULTIPLIERS[-delta]; - } - if (isNegative()) { - result = -result; - } - return result; -} - void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const { // Special handling for zero if (precision == 0) { @@ -795,15 +797,20 @@ UnicodeString DecimalQuantity::toScientificString() const { result.append(u"0E+0", -1); return result; } - result.append(u'0' + getDigitPos(precision - 1)); - if (precision > 1) { + // NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from + // rOptPos (aka -maxFrac) due to overflow. + int32_t upperPos = std::min(precision + scale, lOptPos) - scale - 1; + int32_t lowerPos = std::max(scale, rOptPos) - scale; + int32_t p = upperPos; + result.append(u'0' + getDigitPos(p)); + if ((--p) >= lowerPos) { result.append(u'.'); - for (int32_t i = 1; i < precision; i++) { - result.append(u'0' + getDigitPos(precision - i - 1)); + for (; p >= lowerPos; p--) { + result.append(u'0' + getDigitPos(p)); } } result.append(u'E'); - int32_t _scale = scale + precision - 1; + int32_t _scale = upperPos + scale; if (_scale < 0) { _scale *= -1; result.append(u'-'); diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 78bcdfc0b35..f440544a7b0 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -143,9 +143,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */ bool isNaN() const U_OVERRIDE; - int64_t toLong() const; + /** @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error. */ + int64_t toLong(bool truncateIfOverflow = false) const; - int64_t toFractionLong(bool includeTrailingZeros) const; + uint64_t toFractionLong(bool includeTrailingZeros) const; /** * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. @@ -200,6 +201,8 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { double getPluralOperand(PluralOperand operand) const U_OVERRIDE; + bool hasIntegerValue() const U_OVERRIDE; + /** * Gets the digit at the specified magnitude. For example, if the represented number is 12.3, * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1. @@ -466,8 +469,6 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void convertToAccurateDouble(); - double toDoubleFromOriginal() const; - /** Ensure that a byte array of at least 40 digits is allocated. */ void ensureCapacity(); diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 1b0089f9c9d..04e4d825f6a 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -1480,7 +1480,7 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) { decimalDigits = other.decimalDigits; decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros; intValue = other.intValue; - hasIntegerValue = other.hasIntegerValue; + _hasIntegerValue = other._hasIntegerValue; isNegative = other.isNegative; _isNaN = other._isNaN; _isInfinite = other._isInfinite; @@ -1504,10 +1504,10 @@ void FixedDecimal::init(double n, int32_t v, int64_t f) { v = 0; f = 0; intValue = 0; - hasIntegerValue = FALSE; + _hasIntegerValue = FALSE; } else { intValue = (int64_t)source; - hasIntegerValue = (source == intValue); + _hasIntegerValue = (source == intValue); } visibleDecimalDigitCount = v; @@ -1641,6 +1641,10 @@ bool FixedDecimal::isInfinite() const { return _isInfinite; } +bool FixedDecimal::hasIntegerValue() const { + return _hasIntegerValue; +} + bool FixedDecimal::isNanOrInfinity() const { return _isNaN || _isInfinite; } diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index c85c922f15c..a0d3f4a564a 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -249,9 +249,8 @@ class U_I18N_API IFixedDecimal { virtual bool isInfinite() const = 0; - virtual bool hasIntegerValue() { - return getPluralOperand(PLURAL_OPERAND_N) == getPluralOperand(PLURAL_OPERAND_I); - } + /** Whether the number has no nonzero fraction digits. */ + virtual bool hasIntegerValue() const = 0; }; /** @@ -279,6 +278,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { double getPluralOperand(PluralOperand operand) const U_OVERRIDE; bool isNaN() const U_OVERRIDE; bool isInfinite() const U_OVERRIDE; + bool hasIntegerValue() const U_OVERRIDE; bool isNanOrInfinity() const; // used in decimfmtimpl.cpp @@ -297,7 +297,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { int64_t decimalDigits; int64_t decimalDigitsWithoutTrailingZeros; int64_t intValue; - UBool hasIntegerValue; + UBool _hasIntegerValue; UBool isNegative; UBool _isNaN; UBool _isInfinite; diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index 48f6a9e2e90..96d297074f6 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -825,12 +825,12 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); ASSERT_EQUAL(FALSE, fd.isNegative()); - fable.setDecimalNumber("12.345678901234567890123456789", status); + fable.setDecimalNumber("12.3456789012345678900123456789", status); TEST_ASSERT_STATUS(status); df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); - ASSERT_EQUAL(345678901234567890LL, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(3456789012345678900LL, fd.getPluralOperand(PLURAL_OPERAND_F)); ASSERT_EQUAL(34567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_T)); ASSERT_EQUAL(12, fd.getPluralOperand(PLURAL_OPERAND_I)); ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); @@ -842,8 +842,8 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { df->formatToDecimalQuantity(fable, fd, status); TEST_ASSERT_STATUS(status); ASSERT_EQUAL(22, fd.getPluralOperand(PLURAL_OPERAND_V)); - ASSERT_EQUAL(123456789012345678LL, fd.getPluralOperand(PLURAL_OPERAND_F)); - ASSERT_EQUAL(123456789012345678LL, fd.getPluralOperand(PLURAL_OPERAND_T)); + ASSERT_EQUAL(1234567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_F)); + ASSERT_EQUAL(1234567890123456789LL, fd.getPluralOperand(PLURAL_OPERAND_T)); ASSERT_EQUAL(345678901234567890LL, fd.getPluralOperand(PLURAL_OPERAND_I)); ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); ASSERT_EQUAL(FALSE, fd.isNegative()); @@ -892,7 +892,7 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_EQUAL(2, fd.getPluralOperand(PLURAL_OPERAND_V)); ASSERT_EQUAL(30, fd.getPluralOperand(PLURAL_OPERAND_F)); ASSERT_EQUAL(3, fd.getPluralOperand(PLURAL_OPERAND_T)); - ASSERT_EQUAL(100000000000000000LL, fd.getPluralOperand(PLURAL_OPERAND_I)); + ASSERT_EQUAL(0, fd.getPluralOperand(PLURAL_OPERAND_I)); ASSERT_EQUAL(FALSE, fd.hasIntegerValue()); ASSERT_EQUAL(FALSE, fd.isNegative()); diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index fdb892a0fc9..594788287b8 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -122,6 +122,7 @@ class DecimalQuantityTest : public IntlTest { void testUseApproximateDoubleWhenAble(); void testHardDoubleConversion(); void testToDouble(); + void testMaxDigits(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp index 2a19b0b9087..fdd9124f0c3 100644 --- a/icu4c/source/test/intltest/numbertest_decimalquantity.cpp +++ b/icu4c/source/test/intltest/numbertest_decimalquantity.cpp @@ -8,6 +8,7 @@ #include "number_decimalquantity.h" #include "math.h" #include +#include "number_utils.h" #include "numbertest.h" void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { @@ -23,6 +24,7 @@ void DecimalQuantityTest::runIndexedTest(int32_t index, UBool exec, const char * TESTCASE_AUTO(testUseApproximateDoubleWhenAble); TESTCASE_AUTO(testHardDoubleConversion); TESTCASE_AUTO(testToDouble); + TESTCASE_AUTO(testMaxDigits); TESTCASE_AUTO_END; } @@ -60,9 +62,6 @@ void DecimalQuantityTest::checkDoubleBehavior(double d, bool 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) { @@ -358,4 +357,26 @@ void DecimalQuantityTest::testToDouble() { } } +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", 76L, dq.toLong()); + assertEquals("Should trim, toFractionLong", 54L, 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 */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index 7cb9bb1b15a..567a17ce0d5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -242,7 +242,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { switch (operand) { case i: // Invert the negative sign if necessary - return isNegative() ? -toLong() : toLong(); + return isNegative() ? -toLong(true) : toLong(true); case f: return toFractionLong(true); case t: @@ -572,11 +572,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { * Returns a long approximating the internal BCD. A long can only represent the integral part of the * number. * + * @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error. * @return A 64-bit integer representation of the internal BCD. */ - public long toLong() { + public long toLong(boolean truncateIfOverflow) { + // NOTE: Call sites should be guarded by fitsInLong(), like this: + // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } + // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits. + assert(truncateIfOverflow || fitsInLong()); long result = 0L; - for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { + int upperMagnitude = Math.min(scale + precision, lOptPos) - 1; + if (truncateIfOverflow) { + upperMagnitude = Math.min(upperMagnitude, 17); + } + for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } if (isNegative()) { @@ -593,10 +602,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { public long toFractionLong(boolean includeTrailingZeros) { long result = 0L; int magnitude = -1; - for (; (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos)) - && magnitude >= rOptPos; magnitude--) { + int lowerMagnitude = Math.max(scale, rOptPos); + if (includeTrailingZeros) { + lowerMagnitude = Math.min(lowerMagnitude, rReqPos); + } + // NOTE: Java has only signed longs, so we check result <= 1e17 instead of 1e18 + for (; magnitude >= lowerMagnitude && result <= 1e17; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + // Remove trailing zeros; this can happen during integer overflow cases. + if (!includeTrailingZeros) { + while (result > 0 && (result % 10) == 0) { + result /= 10; + } + } return result; } @@ -641,9 +660,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { */ @Override public double toDouble() { - if (isApproximate) { - return toDoubleFromOriginal(); - } + // If this assertion fails, you need to call roundToInfinity() or some other rounding method. + // See the comment at the top of this file explaining the "isApproximate" field. + assert !isApproximate; if (isNaN()) { return Double.NaN; @@ -652,7 +671,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { } // TODO: Do like in C++ and use a library function to perform this conversion? - // This code is not as not in Java because .parse() returns a BigDecimal, not a double. + // This code is not as hot in Java because .parse() returns a BigDecimal, not a double. long tempLong = 0L; int lostDigits = precision - Math.min(precision, 17); @@ -701,26 +720,6 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { return bcdToBigDecimal(); } - protected double toDoubleFromOriginal() { - double result = origDouble; - int delta = origDelta; - if (delta >= 0) { - // 1e22 is the largest exact double. - for (; delta >= 22; delta -= 22) - result *= 1e22; - result *= DOUBLE_MULTIPLIERS[delta]; - } else { - // 1e22 is the largest exact double. - for (; delta <= -22; delta += 22) - result /= 1e22; - result /= DOUBLE_MULTIPLIERS[-delta]; - } - if (isNegative()) { - result = -result; - } - return result; - } - private static int safeSubtract(int a, int b) { int diff = a - b; if (b < 0 && diff < a) @@ -952,7 +951,7 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { sb.append('0'); } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { - sb.append(getDigit(m)); + sb.append((char) ('0' + getDigit(m))); if (m == 0) sb.append('.'); } @@ -974,15 +973,20 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity { result.append("0E+0"); return; } - result.append((char) ('0' + getDigitPos(precision - 1))); - if (precision > 1) { + // NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from + // rOptPos (aka -maxFrac) due to overflow. + int upperPos = Math.min(precision + scale, lOptPos) - scale - 1; + int lowerPos = Math.max(scale, rOptPos) - scale; + int p = upperPos; + result.append((char) ('0' + getDigitPos(p))); + if ((--p) >= lowerPos) { result.append('.'); - for (int i = 1; i < precision; i++) { - result.append((char) ('0' + getDigitPos(precision - i - 1))); + for (; p >= lowerPos; p--) { + result.append((char) ('0' + getDigitPos(p))); } } result.append('E'); - int _scale = scale + precision - 1; + int _scale = upperPos + scale; if (_scale < 0) { _scale *= -1; result.append('-'); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index c6e686e64e2..7ad95317b62 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -155,7 +155,7 @@ public class ParsedNumber { } if (quantity.fitsInLong() && !forceBigDecimal) { - return quantity.toLong(); + return quantity.toLong(false); } else { return quantity.toBigDecimal(); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 256f548e846..18a5dc32a2b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -496,6 +496,22 @@ public class DecimalQuantityTest extends TestFmwk { } } + @Test + public void testMaxDigits() { + DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(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", 76, dq.toLong(true)); + assertEquals("Should trim, toFractionLong", 54, dq.toFractionLong(false)); + if (!logKnownIssue("13701", "consider cleaning up")) { + assertEquals("Should trim, toDouble", 76.54, dq.toDouble()); + assertEquals("Should trim, toBigDecimal", new BigDecimal("76.54"), dq.toBigDecimal()); + } + } + static void assertDoubleEquals(String message, double d1, double d2) { boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6); handleAssert(equal, message, d1, d2, null, false); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java index 51dbe11ab88..b924b0525b3 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ExhaustiveNumberTest.java @@ -170,7 +170,6 @@ public class ExhaustiveNumberTest extends TestFmwk { if (explicitRequired) { assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble); } - assertEquals(alert + "Initial construction from hard double", d, fq.toDouble()); fq.roundToInfinity(); if (explicitRequired) { assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble); diff --git a/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java b/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java index 13f4332ec5b..ce0082eddbd 100644 --- a/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java +++ b/icu4j/main/tests/framework/src/com/ibm/icu/dev/test/TestFmwk.java @@ -169,6 +169,9 @@ abstract public class TestFmwk extends AbstractTestLog { return false; } + // TODO: This method currently does not do very much. + // See http://bugs.icu-project.org/trac/ticket/12589 + StringBuffer descBuf = new StringBuffer(); // TODO(junit) : what to do about this? //getParams().stack.appendPath(descBuf);