ICU-10273 DecimalFormat::getFixedDecimal(), improved handling of rounding, overflow.

X-SVN-Rev: 34280
This commit is contained in:
Andy Heninger 2013-09-12 01:00:04 +00:00
parent fbb5f5ba1f
commit 8abaebe06e
5 changed files with 134 additions and 45 deletions

View 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;

View 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;

View 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);

View 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:
/**

View 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 */