From 8abaebe06e04bb30a4987792781fa24741bdc18c Mon Sep 17 00:00:00 2001 From: Andy Heninger Date: Thu, 12 Sep 2013 01:00:04 +0000 Subject: [PATCH] ICU-10273 DecimalFormat::getFixedDecimal(), improved handling of rounding, overflow. X-SVN-Rev: 34280 --- icu4c/source/i18n/decimfmt.cpp | 104 ++++++++++++++---------- icu4c/source/i18n/plurrule.cpp | 16 ++++ icu4c/source/i18n/plurrule_impl.h | 1 + icu4c/source/i18n/unicode/decimfmt.h | 8 ++ icu4c/source/test/intltest/dcfmapts.cpp | 50 +++++++++++- 5 files changed, 134 insertions(+), 45 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index e8ceb601cae..c2ccb60edf5 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -1,7 +1,7 @@ /* ******************************************************************************* -* Copyright (C) 1997-2013, International Business Machines Corporation and -* others. All Rights Reserved. +* Copyright (C) 1997-2013, International Business Machines Corporation and * +* others. All Rights Reserved. * ******************************************************************************* * * File DECIMFMT.CPP @@ -1035,41 +1035,19 @@ DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const { return result; } - result.source = number; - int32_t minFractionDigits = getMinimumFractionDigits(); - if (fMultiplier == NULL && fScale == 0 && fRoundingIncrement == 0 && areSignificantDigitsUsed() == FALSE && result.quickInit(number) && result.visibleDecimalDigitCount <= getMaximumFractionDigits()) { // Fast Path. Construction of an exact FixedDecimal directly from the double, without passing // through a DigitList, was successful, and the formatter is doing nothing tricky with rounding. // printf("getFixedDecimal(%g): taking fast path.\n", number); + result.adjustForMinFractionDigits(getMinimumFractionDigits()); } else { // Slow path. Create a DigitList, and have this formatter round it according to the // requirements of the format, and fill the fixedDecimal from that. DigitList digits; digits.set(number); - UBool isNegative; - _round(digits, digits, isNegative, status); - double roundedNum = digits.getDouble(); - result.init(roundedNum); - - if (areSignificantDigitsUsed()) { - minFractionDigits = getMinimumSignificantDigits() - digits.getDecimalAt(); - if (minFractionDigits < 0) { - minFractionDigits = 0; - } - } + result = getFixedDecimal(digits, status); } - - // Adjust result for trailing zeros to the right of the decimal. Needed for both fast & slow paths. - - int32_t numTrailingFractionZeros = minFractionDigits - result.visibleDecimalDigitCount; - if (numTrailingFractionZeros > 0) { - double scaleFactor = pow(10.0, (double)numTrailingFractionZeros); - result.decimalDigits *= scaleFactor; - result.visibleDecimalDigitCount += numTrailingFractionZeros; - } - return result; } @@ -1083,22 +1061,38 @@ DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) co status = U_ILLEGAL_ARGUMENT_ERROR; return FixedDecimal(); } - - DigitList *digits = number.getDigitList(); - if (digits == NULL || digits->getCount() <= 15) { + + DigitList *dl = number.getDigitList(); + if (dl != NULL) { + DigitList clonedDL(*dl); + return getFixedDecimal(clonedDL, status); + } + + Formattable::Type type = number.getType(); + if (type == Formattable::kDouble || type == Formattable::kLong || + (type == Formattable::kInt64 && number.getInt64() == (int64_t)number.getDouble(status))) { return getFixedDecimal(number.getDouble(status), status); } - // We have an incoming DigitList in the formattable, and it holds more digits than - // a double can safely represent. - // Compute the fields of the fixed decimal directly from the digit list. - - FixedDecimal result; - result.source = digits->getDouble(); + // The only case left is type==int64_t, with a value with more digits than a double can represent. + // Any formattable originating as a big decimal will have had a pre-existing digit list. + // Any originating as a double or int32 will have been handled as a double. + + U_ASSERT(type == Formattable::kInt64); + DigitList digits; + digits.set(number.getInt64()); + return getFixedDecimal(digits, status); +} + +// Create a fixed decimal from a DigitList. +// The digit list may be modified. +// Internal function only. +FixedDecimal +DecimalFormat::getFixedDecimal(DigitList &number, UErrorCode &status) const { // Round the number according to the requirements of this Format. - DigitList roundedNum; - _round(*digits, roundedNum, result.isNegative, status); + FixedDecimal result; + _round(number, number, result.isNegative, status); // The int64_t fields in FixedDecimal can easily overflow. // In deciding what to discard in this event, consider that fixedDecimal @@ -1113,22 +1107,29 @@ DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) co // though they could hold most (but not all) 19 digit values. // Integer Digits. - int32_t di = roundedNum.getDecimalAt()-18; // Take at most 18 digits. + int32_t di = number.getDecimalAt()-18; // Take at most 18 digits. if (di < 0) { di = 0; } result.intValue = 0; - for (; di 0) { + // The number is something like 100000000000000000000000. + // More than 18 digits integer digits, but the least significant 18 are all zero. + // We don't want to return zero as the int part, but want to keep zeros + // for several of the least significant digits. + result.intValue = 100000000000000000; } // Fraction digits. result.visibleDecimalDigitCount = result.decimalDigits = result.decimalDigitsWithoutTrailingZeros = 0; - for (di = roundedNum.getDecimalAt(); di < roundedNum.getCount(); di++) { + for (di = number.getDecimalAt(); di < number.getCount(); di++) { result.visibleDecimalDigitCount++; if (result.decimalDigits < 100000000000000000LL) { // 9223372036854775807 Largest 64 bit signed integer - int32_t digitVal = roundedNum.getDigit(di) & 0x0f; // getDigit() returns a char, '0'-'9'. + int32_t digitVal = number.getDigit(di) & 0x0f; // getDigit() returns a char, '0'-'9'. result.decimalDigits = result.decimalDigits * 10 + digitVal; if (digitVal > 0) { result.decimalDigitsWithoutTrailingZeros = result.decimalDigits; @@ -1137,6 +1138,21 @@ DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) co } result.hasIntegerValue = (result.decimalDigits == 0); + + // Trailing fraction zeros. The format specification may require more trailing + // zeros than the numeric value. Add any such on now. + + int32_t minFractionDigits; + if (areSignificantDigitsUsed()) { + minFractionDigits = getMinimumSignificantDigits() - number.getDecimalAt(); + if (minFractionDigits < 0) { + minFractionDigits = 0; + } + } else { + minFractionDigits = getMinimumFractionDigits(); + } + result.adjustForMinFractionDigits(minFractionDigits); + return result; } @@ -1535,7 +1551,7 @@ DecimalFormat::_round(const DigitList &number, DigitList &adjustedNum, UBool& is if (fScale != 0) { DigitList ten; - ten.set((int32_t)10); + ten.set(10); if (fScale > 0) { for (int32_t i = fScale ; i > 0 ; i--) { adjustedNum.mult(ten, status); @@ -2180,7 +2196,7 @@ void DecimalFormat::parse(const UnicodeString& text, if (fScale != 0) { DigitList ten; - ten.set((int32_t)10); + ten.set(10); if (fScale > 0) { for (int32_t i = fScale; i > 0; i--) { UErrorCode ec = U_ZERO_ERROR; diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index e6d3b91794e..b3274f11eaf 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -1485,6 +1485,22 @@ int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) { } +void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) { + int32_t numTrailingFractionZeros = minFractionDigits - visibleDecimalDigitCount; + if (numTrailingFractionZeros > 0) { + for (int32_t i=0; i= 100000000000000000LL) { + break; + } + decimalDigits *= 10; + } + visibleDecimalDigitCount += numTrailingFractionZeros; + } +} + + double FixedDecimal::get(tokenType operand) const { switch(operand) { case tVariableN: return source; diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index cfd5bc0a876..13729a38ed2 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -198,6 +198,7 @@ class U_I18N_API FixedDecimal: public UMemory { void init(double n); UBool quickInit(double n); // Try a fast-path only initialization, // return TRUE if successful. + void adjustForMinFractionDigits(int32_t min); static int64_t getFractionalDigits(double n, int32_t v); static int32_t decimals(double n); diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 5e7257b6a64..e1f7a37633a 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1869,6 +1869,14 @@ public: */ FixedDecimal getFixedDecimal(const Formattable &number, UErrorCode &status) const; + /** + * Get a FixedDecimal corresponding to a DigitList as it would be + * formatted by this DecimalFormat. Note: the DigitList may be modified. + * Internal, not intended for public use. + * @internal + */ + FixedDecimal getFixedDecimal(DigitList &number, UErrorCode &status) const; + public: /** diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index 68cf41d961e..b316f4e5097 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -725,7 +725,7 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_SUCCESS(status); fd = df->getFixedDecimal(fable, status); ASSERT_SUCCESS(status); - ASSERT_EQUAL(0, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); ASSERT_EQUAL(0, fd.decimalDigits); ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); ASSERT_EQUAL(1, fd.intValue); @@ -744,6 +744,54 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_EQUAL(FALSE, fd.hasIntegerValue); ASSERT_EQUAL(TRUE, fd.isNegative); + // MinFractionDigits from format larger than from number. + fable.setDecimalNumber("1000000000000000000000.3", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(30, fd.decimalDigits); + ASSERT_EQUAL(3, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(100000000000000000, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + // Test some int64_t values that are out of the range of a double + fable.setInt64(4503599627370496LL); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(0, fd.decimalDigits); + ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(4503599627370496LL, fd.intValue); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + fable.setInt64(4503599627370497LL); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(0, fd.decimalDigits); + ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(4503599627370497LL, fd.intValue); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + fable.setInt64(9223372036854775807LL); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(0, fd.decimalDigits); + ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); + // note: going through DigitList path to FixedDecimal, which is trimming + // int64_t fields to 18 digits. See ticket Ticket #10374 + ASSERT_EQUAL(223372036854775807LL, fd.intValue); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + } #endif /* #if !UCONFIG_NO_FORMATTING */ -- 2.40.0