]> granicus.if.org Git - icu/commitdiff
ICU-10273 DecimalFormat::getFixedDecimal(), improved handling of rounding, overflow.
authorAndy Heninger <andy.heninger@gmail.com>
Thu, 12 Sep 2013 01:00:04 +0000 (01:00 +0000)
committerAndy Heninger <andy.heninger@gmail.com>
Thu, 12 Sep 2013 01:00:04 +0000 (01:00 +0000)
X-SVN-Rev: 34280

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

index e8ceb601cae528a94b1cc5ae1fdfc4114dea384e..c2ccb60edf5a72cc5f315edbdfa92b7b12899754 100644 (file)
@@ -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<roundedNum.getDecimalAt(); di++) {
-        result.intValue = result.intValue * 10 + (roundedNum.getDigit(di) & 0x0f);
+    for (; di<number.getDecimalAt(); di++) {
+        result.intValue = result.intValue * 10 + (number.getDigit(di) & 0x0f);
+    }
+    if (result.intValue == 0 && number.getDecimalAt()-18 > 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;
index e6d3b91794e4c6ded510e6d45233cf930d0c9a2f..b3274f11eaf3720cf99c877526b9c5e66c0271fa 100644 (file)
@@ -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<numTrailingFractionZeros; i++) {
+            // Do not let the decimalDigits value overflow if there are many trailing zeros.
+            // Limit the value to 18 digits, the most that a 64 bit int can fully represent.
+            if (decimalDigits >= 100000000000000000LL) {
+                break;
+            }
+            decimalDigits *= 10;
+        }
+        visibleDecimalDigitCount += numTrailingFractionZeros;
+    }
+}
+        
+
 double FixedDecimal::get(tokenType operand) const {
     switch(operand) {
         case tVariableN: return source;
index cfd5bc0a876a17f6c6f24ce1026c8218dbacb49f..13729a38ed2ac403dd1b03d67db7247eab008403 100644 (file)
@@ -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);
 
index 5e7257b6a642e0dfd2b279c2dcc865ce5ea3683a..e1f7a37633aca5ce1ef8592adc791ed40a03e77e 100644 (file)
@@ -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:
 
     /**
index 68cf41d961ea43e473186d6a35ab456ed36771a5..b316f4e5097527027fb971da0c15c56aaea92550 100644 (file)
@@ -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 */