]> granicus.if.org Git - icu/commitdiff
ICU-10273 add DecimalFormat::getFixedDecimal(Formattable &)
authorAndy Heninger <andy.heninger@gmail.com>
Wed, 11 Sep 2013 01:17:28 +0000 (01:17 +0000)
committerAndy Heninger <andy.heninger@gmail.com>
Wed, 11 Sep 2013 01:17:28 +0000 (01:17 +0000)
X-SVN-Rev: 34267

icu4c/source/i18n/decimfmt.cpp
icu4c/source/i18n/unicode/decimfmt.h
icu4c/source/i18n/unicode/fmtable.h
icu4c/source/test/intltest/dcfmapts.cpp

index f18284bed644e50c464c56d3ad51a46e4d0456b4..e6e1558d10db8fdfac80b00852dfa048f2124008 100644 (file)
@@ -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<roundedNum.getDecimalAt(); di++) {
+        result.intValue = result.intValue * 10 + (roundedNum.getDigit(di) & 0x0f);
+    }
+    
+    // Fraction digits.
+    result.visibleDecimalDigitCount = result.decimalDigits = result.decimalDigitsWithoutTrailingZeros = 0;
+    for (di = roundedNum.getDecimalAt(); di < roundedNum.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'.
+            result.decimalDigits = result.decimalDigits * 10 + digitVal;
+            if (digitVal > 0) {
+                result.decimalDigitsWithoutTrailingZeros = result.decimalDigits;
+            }
+        }
+    }
+
+    result.hasIntegerValue = (result.decimalDigits == 0);
+    return result;
+}
+
+
 //------------------------------------------------------------------------------
 
 UnicodeString&
index 6683807b55f787e8e5ab6638639c2f0b79d5874e..5e7257b6a642e0dfd2b279c2dcc865ce5ea3683a 100644 (file)
@@ -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:
 
     /**
index 2f6977db77552b9e38d790152e144b29a53ddd77..4d9e07910347dbff9d5228138838b6647efeb42a 100644 (file)
@@ -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.
index d9c21c0704a6673c859f958883474d646ea7f55c..68cf41d961ea43e473186d6a35ab456ed36771a5 100644 (file)
 #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 */