diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index 72b2e76cb72..cfaa7907bc1 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -1022,19 +1022,42 @@ DecimalFormat::clone() const FixedDecimal -DecimalFormat::getFixedDecimal(double number, UErrorCode &status) { - DigitList digits; - digits.set(number); - UBool isNegative; - _round(digits, digits, isNegative, status); - double roundedNum = digits.getDouble(); - FixedDecimal result(roundedNum); - int32_t numTrailingFractionZeros = this->getMinimumFractionDigits() - result.visibleDecimalDigitCount; +DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const { + FixedDecimal result; + 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); + } 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; + } + } + } + + // 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, numTrailingFractionZeros); result.decimalDigits *= scaleFactor; result.visibleDecimalDigitCount += numTrailingFractionZeros; } + return result; } diff --git a/icu4c/source/i18n/plurrule.cpp b/icu4c/source/i18n/plurrule.cpp index 9160d5f75e4..a86ee2bf4d8 100644 --- a/icu4c/source/i18n/plurrule.cpp +++ b/icu4c/source/i18n/plurrule.cpp @@ -1330,10 +1330,14 @@ FixedDecimal::FixedDecimal(double n, int32_t v) { } FixedDecimal::FixedDecimal(double n) { - int64_t numFractionDigits = decimals(n); - init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits)); + init(n); } +FixedDecimal::FixedDecimal() { + init(0, 0, 0); +} + + // Create a FixedDecimal from a UnicodeString containing a number. // Inefficient, but only used for samples, so simplicity trumps efficiency. @@ -1369,6 +1373,12 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) { } +void FixedDecimal::init(double n) { + int64_t numFractionDigits = decimals(n); + init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits)); +} + + void FixedDecimal::init(double n, int32_t v, int64_t f) { isNegative = n < 0; source = fabs(n); @@ -1390,17 +1400,43 @@ void FixedDecimal::init(double n, int32_t v, int64_t f) { } } + +// Fast path only exact initialization. Return true if successful. +// Note: Do not multiply by 10 each time through loop, rounding cruft can build +// up that makes the check for an integer result fail. +// A single multiply of the original number works more reliably. +static int p10[] = {1, 10, 100, 1000, 10000}; +UBool FixedDecimal::quickInit(double n) { + UBool success = FALSE; + n = fabs(n); + int32_t numFractionDigits; + for (numFractionDigits = 0; numFractionDigits <= 3; numFractionDigits++) { + double scaledN = n * p10[numFractionDigits]; + if (scaledN == floor(scaledN)) { + success = TRUE; + break; + } + } + if (success) { + init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits)); + } + return success; +} + + + int32_t FixedDecimal::decimals(double n) { // Count the number of decimal digits in the fraction part of the number, excluding trailing zeros. + // fastpath the common cases, integers or fractions with 3 or fewer digits n = fabs(n); - double scaledN = n; for (int ndigits=0; ndigits<=3; ndigits++) { - // fastpath the common cases, integers or fractions with 3 or fewer digits + double scaledN = n * p10[ndigits]; if (scaledN == floor(scaledN)) { return ndigits; } - scaledN *= 10; } + + // Slow path, convert with sprintf, parse converted output. char buf[30] = {0}; sprintf(buf, "%1.15e", n); // formatted number looks like this: 1.234567890123457e-01 diff --git a/icu4c/source/i18n/plurrule_impl.h b/icu4c/source/i18n/plurrule_impl.h index 0d6f3a9247b..c9e7f4050ca 100644 --- a/icu4c/source/i18n/plurrule_impl.h +++ b/icu4c/source/i18n/plurrule_impl.h @@ -181,6 +181,7 @@ class U_I18N_API FixedDecimal: public UMemory { FixedDecimal(double n, int32_t v, int64_t f); FixedDecimal(double n, int32_t); explicit FixedDecimal(double n); + FixedDecimal(); FixedDecimal(const UnicodeString &s, UErrorCode &ec); FixedDecimal(const FixedDecimal &other); @@ -188,6 +189,9 @@ class U_I18N_API FixedDecimal: public UMemory { int32_t getVisibleFractionDigitCount() const; void init(double n, int32_t v, int64_t f); + void init(double n); + UBool quickInit(double n); // Try a fast-path only initialization, + // return TRUE if successful. 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 8f98eafe3b1..6683807b55f 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1859,7 +1859,7 @@ public: * Internal, not intended for public use. * @internal */ - FixedDecimal getFixedDecimal(double number, UErrorCode &status); + FixedDecimal getFixedDecimal(double number, UErrorCode &status) const; public: diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index fb7b22a16a8..f4df4f4d4af 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -634,6 +634,32 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { ASSERT_EQUAL(123, fd.intValue); ASSERT_EQUAL(FALSE, fd.hasIntegerValue); ASSERT_EQUAL(FALSE, fd.isNegative); + + df.adoptInstead(new DecimalFormat("@@@@@", status)); // Significant Digits + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(123, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(0, fd.decimalDigits); + ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(123, fd.intValue); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + df.adoptInstead(new DecimalFormat("@@@@@", status)); // Significant Digits + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(1.23, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(4, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(2300, fd.decimalDigits); + ASSERT_EQUAL(23, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(1, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + + + } #endif /* #if !UCONFIG_NO_FORMATTING */