diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index f18284bed64..e6e1558d10d 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -1073,6 +1073,73 @@ DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const { } +FixedDecimal +DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) const { + if (U_FAILURE(status)) { + return FixedDecimal(); + } + if (!number.isNumeric()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return FixedDecimal(); + } + + DigitList *digits = number.getDigitList(); + if (digits == NULL || digits->getCount() <= 15) { + return getFixedDecimal(number.getDouble(), 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(); + + // Round the number according to the requirements of this Format. + DigitList roundedNum; + _round(*digits, roundedNum, result.isNegative, status); + + // The int64_t fields in FixedDecimal can easily overflow. + // In deciding what to discard in this event, consider that fixedDecimal + // is being used only with PluralRules, and those rules mostly look at least significant + // few digits of the integer part, and whether the fraction part is zero or not. + // + // So, in case of overflow when filling in the fields of the FixedDecimal object, + // for the integer part, discard the most significant digits. + // for the fraction part, discard the least significant digits, + // don't truncate the fraction value to zero. + // For simplicity, the int64_t fields are limited to 18 decimal digits, even + // though they could hold most (but not all) 19 digit values. + + // Integer Digits. + int32_t di = roundedNum.getDecimalAt()-18; // Take at most 18 digits. + if (di < 0) { + di = 0; + } + result.intValue = 0; + for (; di 0) { + result.decimalDigitsWithoutTrailingZeros = result.decimalDigits; + } + } + } + + result.hasIntegerValue = (result.decimalDigits == 0); + return result; +} + + //------------------------------------------------------------------------------ UnicodeString& diff --git a/icu4c/source/i18n/unicode/decimfmt.h b/icu4c/source/i18n/unicode/decimfmt.h index 6683807b55f..5e7257b6a64 100644 --- a/icu4c/source/i18n/unicode/decimfmt.h +++ b/icu4c/source/i18n/unicode/decimfmt.h @@ -1861,6 +1861,14 @@ public: */ FixedDecimal getFixedDecimal(double number, UErrorCode &status) const; + /** + * Get a FixedDecimal corresponding to a formattable as it would be + * formatted by this DecimalFormat. + * Internal, not intended for public use. + * @internal + */ + FixedDecimal getFixedDecimal(const Formattable &number, UErrorCode &status) const; + public: /** diff --git a/icu4c/source/i18n/unicode/fmtable.h b/icu4c/source/i18n/unicode/fmtable.h index 2f6977db775..4d9e0791034 100644 --- a/icu4c/source/i18n/unicode/fmtable.h +++ b/icu4c/source/i18n/unicode/fmtable.h @@ -127,7 +127,7 @@ public: * Creates a Formattable object of an appropriate numeric type from a * a decimal number in string form. The Formattable will retain the * full precision of the input in decimal format, even when it exceeds - * what can be represented by a double of int64_t. + * what can be represented by a double or int64_t. * * @param number the unformatted (not localized) string representation * of the Decimal number. diff --git a/icu4c/source/test/intltest/dcfmapts.cpp b/icu4c/source/test/intltest/dcfmapts.cpp index d9c21c0704a..68cf41d961e 100644 --- a/icu4c/source/test/intltest/dcfmapts.cpp +++ b/icu4c/source/test/intltest/dcfmapts.cpp @@ -13,8 +13,10 @@ #include "unicode/currpinf.h" #include "unicode/dcfmtsym.h" #include "unicode/decimfmt.h" +#include "unicode/fmtable.h" #include "unicode/localpointer.h" #include "unicode/parseerr.h" +#include "unicode/stringpiece.h" #include "putilimp.h" #include "plurrule_impl.h" @@ -666,6 +668,82 @@ void IntlTestDecimalFormatAPI::TestFixedDecimal() { fd = df->getFixedDecimal(uprv_getNaN(), status); ASSERT_EQUAL(TRUE, fd.isNanOrInfinity); ASSERT_SUCCESS(status); + + // Test Big Decimal input. + // 22 digits before and after decimal, will exceed the precision of a double + // and force DecimalFormat::getFixedDecimal() to work with a digit list. + df.adoptInstead(new DecimalFormat("#####################0.00####################", status)); + ASSERT_SUCCESS(status); + Formattable fable("12.34", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(2, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(34, fd.decimalDigits); + ASSERT_EQUAL(34, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(12, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + fable.setDecimalNumber("12.345678901234567890123456789", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(345678901234567890LL, fd.decimalDigits); + ASSERT_EQUAL(34567890123456789LL, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(12, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + // On field overflow, Integer part is truncated on the left, fraction part on the right. + fable.setDecimalNumber("123456789012345678901234567890.123456789012345678901234567890", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(123456789012345678LL, fd.decimalDigits); + ASSERT_EQUAL(123456789012345678LL, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(345678901234567890LL, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + // Digits way to the right of the decimal but within the format's precision aren't truncated + fable.setDecimalNumber("1.0000000000000000000012", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(12, fd.decimalDigits); + ASSERT_EQUAL(12, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(1, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + // Digits beyond the precision of the format are rounded away + fable.setDecimalNumber("1.000000000000000000000012", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(0, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(0, fd.decimalDigits); + ASSERT_EQUAL(0, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(1, fd.intValue); + ASSERT_EQUAL(TRUE, fd.hasIntegerValue); + ASSERT_EQUAL(FALSE, fd.isNegative); + + // Negative numbers come through + fable.setDecimalNumber("-1.0000000000000000000012", status); + ASSERT_SUCCESS(status); + fd = df->getFixedDecimal(fable, status); + ASSERT_SUCCESS(status); + ASSERT_EQUAL(22, fd.visibleDecimalDigitCount); + ASSERT_EQUAL(12, fd.decimalDigits); + ASSERT_EQUAL(12, fd.decimalDigitsWithoutTrailingZeros); + ASSERT_EQUAL(1, fd.intValue); + ASSERT_EQUAL(FALSE, fd.hasIntegerValue); + ASSERT_EQUAL(TRUE, fd.isNegative); + } #endif /* #if !UCONFIG_NO_FORMATTING */