ICU-5698 Set maximum limit for digits used for creating BigInteger/BigDecimal in DecimalFormat to prevent the parse method to trigger OutOfMemoryException. Also added test case for some extreme cases.
X-SVN-Rev: 23216
This commit is contained in:
parent
b33fe49157
commit
849c401c9b
@ -1,7 +1,7 @@
|
||||
//##header J2SE15
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2001-2007, International Business Machines Corporation and *
|
||||
* Copyright (C) 2001-2008, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
@ -231,4 +231,54 @@ public class NumberFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Test case for ticket#5698 - parsing extremely large/small values
|
||||
*/
|
||||
public void TestT5698() {
|
||||
final String[] data = {
|
||||
"12345679E66666666666666666",
|
||||
"-12345679E66666666666666666",
|
||||
".1E2147483648", // exponent > max int
|
||||
".1E2147483647", // exponent == max int
|
||||
".1E-2147483648", // exponent == min int
|
||||
".1E-2147483649", // exponent < min int
|
||||
"1.23E350", // value > max double
|
||||
"1.23E300", // value < max double
|
||||
"-1.23E350", // value < min double
|
||||
"-1.23E300", // value > min double
|
||||
"4.9E-324", // value = smallest non-zero double
|
||||
"1.0E-325", // 0 < value < smallest non-zero positive double0
|
||||
"-1.0E-325", // 0 > value > largest non-zero negative double
|
||||
};
|
||||
final double[] expected = {
|
||||
Double.POSITIVE_INFINITY,
|
||||
Double.NEGATIVE_INFINITY,
|
||||
Double.POSITIVE_INFINITY,
|
||||
Double.POSITIVE_INFINITY,
|
||||
0.0,
|
||||
0.0,
|
||||
Double.POSITIVE_INFINITY,
|
||||
1.23e300d,
|
||||
Double.NEGATIVE_INFINITY,
|
||||
-1.23e300d,
|
||||
4.9e-324d,
|
||||
0.0,
|
||||
-0.0,
|
||||
};
|
||||
|
||||
NumberFormat nfmt = NumberFormat.getInstance();
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
try {
|
||||
Number n = nfmt.parse(data[i]);
|
||||
if (expected[i] != n.doubleValue()) {
|
||||
errln("Failed: Parsed result for " + data[i] + ": "
|
||||
+ n.doubleValue() + " / expected: " + expected[i]);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
errln("Failed: ParseException is thrown for " + data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
//##header J2SE15
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 1996-2007, International Business Machines Corporation and *
|
||||
* Copyright (C) 1996-2008, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
@ -1651,9 +1651,14 @@ public class DecimalFormat extends NumberFormat {
|
||||
: Double.NEGATIVE_INFINITY);
|
||||
}
|
||||
|
||||
// Handle underflow
|
||||
else if (status[STATUS_UNDERFLOW]) {
|
||||
n = status[STATUS_POSITIVE] ? new Double("0.0") : new Double("-0.0");
|
||||
}
|
||||
|
||||
// Handle -0.0
|
||||
else if (!status[STATUS_POSITIVE] && digitList.isZero()) {
|
||||
n = new Double(-0.0);
|
||||
n = new Double("-0.0");
|
||||
}
|
||||
|
||||
else {
|
||||
@ -1689,7 +1694,6 @@ public class DecimalFormat extends NumberFormat {
|
||||
(Number) new Long(big.longValue()) : (Number) big;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-integral values or the case where parseBigDecimal is set
|
||||
else {
|
||||
BigDecimal big = digitList.getBigDecimalICU(status[STATUS_POSITIVE]);
|
||||
@ -1708,7 +1712,8 @@ public class DecimalFormat extends NumberFormat {
|
||||
|
||||
private static final int STATUS_INFINITE = 0;
|
||||
private static final int STATUS_POSITIVE = 1;
|
||||
private static final int STATUS_LENGTH = 2;
|
||||
private static final int STATUS_UNDERFLOW = 2;
|
||||
private static final int STATUS_LENGTH = 3;
|
||||
private static final UnicodeSet dotEquivalents =(UnicodeSet) new UnicodeSet(
|
||||
"[.\u2024\u3002\uFE12\uFE52\uFF0E\uFF61]").freeze();
|
||||
private static final UnicodeSet commaEquivalents = (UnicodeSet) new UnicodeSet(
|
||||
@ -1728,6 +1733,14 @@ public class DecimalFormat extends NumberFormat {
|
||||
private static final UnicodeSet strictDefaultGroupingSeparators = (UnicodeSet) new UnicodeSet(
|
||||
strictDotEquivalents).addAll(strictCommaEquivalents).addAll(strictOtherGroupingSeparators).freeze();
|
||||
|
||||
// When parsing a number with big exponential value, it requires to transform
|
||||
// the value into a string representation to construct BigInteger instance.
|
||||
// We want to set the maximum size because it can easily trigger OutOfMemoryException.
|
||||
// PARSE_MAX_EXPONENT is currently set to 1000, which is much bigger than
|
||||
// MAX_VALUE of Double (
|
||||
// See the problem reported by ticket#5698
|
||||
private static final int PARSE_MAX_EXPONENT = 1000;
|
||||
|
||||
/**
|
||||
* <strong><font face=helvetica color=red>CHANGED</font></strong>
|
||||
* Parse the given text into a number. The text is parsed beginning at
|
||||
@ -1806,7 +1819,7 @@ public class DecimalFormat extends NumberFormat {
|
||||
boolean sawDecimal = false;
|
||||
boolean sawExponent = false;
|
||||
boolean sawDigit = false;
|
||||
int exponent = 0; // Set to the exponent value, if any
|
||||
long exponent = 0; // Set to the exponent value, if any
|
||||
int digit = 0;
|
||||
|
||||
// strict parsing
|
||||
@ -2012,10 +2025,22 @@ public class DecimalFormat extends NumberFormat {
|
||||
}
|
||||
}
|
||||
|
||||
exponentDigits.decimalAt = exponentDigits.count;
|
||||
exponent = (int) exponentDigits.getLong();
|
||||
if (negExp) {
|
||||
exponent = -exponent;
|
||||
// Quick overflow check for exponential part.
|
||||
// Actual limit check will be done later in this code.
|
||||
if (exponentDigits.count > 10 /* maximum decimal digits for int */) {
|
||||
if (negExp) {
|
||||
// set underflow flag
|
||||
status[STATUS_UNDERFLOW] = true;
|
||||
} else {
|
||||
// set infinite flag
|
||||
status[STATUS_INFINITE] = true;
|
||||
}
|
||||
} else {
|
||||
exponentDigits.decimalAt = exponentDigits.count;
|
||||
exponent = exponentDigits.getLong();
|
||||
if (negExp) {
|
||||
exponent = -exponent;
|
||||
}
|
||||
}
|
||||
position = pos; // Advance past the exponent
|
||||
sawExponent = true;
|
||||
@ -2048,7 +2073,14 @@ public class DecimalFormat extends NumberFormat {
|
||||
if (!sawDecimal) digits.decimalAt = digitCount; // Not digits.count!
|
||||
|
||||
// Adjust for exponent, if any
|
||||
digits.decimalAt += exponent;
|
||||
exponent += digits.decimalAt;
|
||||
if (exponent < -PARSE_MAX_EXPONENT) {
|
||||
status[STATUS_UNDERFLOW] = true;
|
||||
} else if (exponent > PARSE_MAX_EXPONENT) {
|
||||
status[STATUS_INFINITE] = true;
|
||||
} else {
|
||||
digits.decimalAt = (int)exponent;
|
||||
}
|
||||
|
||||
// If none of the text string was recognized. For example, parse
|
||||
// "x" with pattern "#0.00" (return index and error index both 0)
|
||||
|
@ -1,7 +1,7 @@
|
||||
//##header J2SE15
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 1996-2005, International Business Machines Corporation and *
|
||||
* Copyright (C) 1996-2008, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
@ -227,7 +227,7 @@ final class DigitList {
|
||||
}
|
||||
return stringRep.toString();
|
||||
}
|
||||
|
||||
|
||||
//#if defined(FOUNDATION10) || defined(J2SE13)
|
||||
//#else
|
||||
/**
|
||||
@ -238,8 +238,41 @@ final class DigitList {
|
||||
* @return the value of this object as a <code>BigDecimal</code>
|
||||
*/
|
||||
public java.math.BigDecimal getBigDecimal(boolean isPositive) {
|
||||
if (isZero()) return java.math.BigDecimal.valueOf(0);
|
||||
return new java.math.BigDecimal(getStringRep(isPositive));
|
||||
if (isZero()) {
|
||||
return java.math.BigDecimal.valueOf(0);
|
||||
}
|
||||
// if exponential notion is negative,
|
||||
// we prefer to use BigDecimal constructor with scale,
|
||||
// because it works better when extremely small value
|
||||
// is used. See #5698.
|
||||
long scale = (long)count - (long)decimalAt;
|
||||
if (scale > 0) {
|
||||
int numDigits = count;
|
||||
if (scale > (long)Integer.MAX_VALUE) {
|
||||
// try to reduce the scale
|
||||
long numShift = scale - (long)Integer.MAX_VALUE;
|
||||
if (numShift < count) {
|
||||
numDigits -= numShift;
|
||||
} else {
|
||||
// fallback to 0
|
||||
return new java.math.BigDecimal(0);
|
||||
}
|
||||
}
|
||||
StringBuffer significantDigits = new StringBuffer(numDigits + 1);
|
||||
if (!isPositive) {
|
||||
significantDigits.append('-');
|
||||
}
|
||||
for (int i = 0; i < numDigits; i++) {
|
||||
significantDigits.append((char)digits[i]);
|
||||
}
|
||||
BigInteger unscaledVal = new BigInteger(significantDigits.toString());
|
||||
return new java.math.BigDecimal(unscaledVal, (int)scale);
|
||||
} else {
|
||||
// We should be able to use a negative scale value for a positive exponential
|
||||
// value on JDK1.5. But it is not supported by older JDK. So, for now,
|
||||
// we always use BigDecimal constructor which takes String.
|
||||
return new java.math.BigDecimal(getStringRep(isPositive));
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
|
||||
@ -251,8 +284,38 @@ final class DigitList {
|
||||
* @return the value of this object as a <code>BigDecimal</code>
|
||||
*/
|
||||
public com.ibm.icu.math.BigDecimal getBigDecimalICU(boolean isPositive) {
|
||||
if (isZero()) return com.ibm.icu.math.BigDecimal.valueOf(0);
|
||||
return new com.ibm.icu.math.BigDecimal(getStringRep(isPositive));
|
||||
if (isZero()) {
|
||||
return com.ibm.icu.math.BigDecimal.valueOf(0);
|
||||
}
|
||||
// if exponential notion is negative,
|
||||
// we prefer to use BigDecimal constructor with scale,
|
||||
// because it works better when extremely small value
|
||||
// is used. See #5698.
|
||||
long scale = (long)count - (long)decimalAt;
|
||||
if (scale > 0) {
|
||||
int numDigits = count;
|
||||
if (scale > (long)Integer.MAX_VALUE) {
|
||||
// try to reduce the scale
|
||||
long numShift = scale - (long)Integer.MAX_VALUE;
|
||||
if (numShift < count) {
|
||||
numDigits -= numShift;
|
||||
} else {
|
||||
// fallback to 0
|
||||
return new com.ibm.icu.math.BigDecimal(0);
|
||||
}
|
||||
}
|
||||
StringBuffer significantDigits = new StringBuffer(numDigits + 1);
|
||||
if (!isPositive) {
|
||||
significantDigits.append('-');
|
||||
}
|
||||
for (int i = 0; i < numDigits; i++) {
|
||||
significantDigits.append((char)digits[i]);
|
||||
}
|
||||
BigInteger unscaledVal = new BigInteger(significantDigits.toString());
|
||||
return new com.ibm.icu.math.BigDecimal(unscaledVal, (int)scale);
|
||||
} else {
|
||||
return new com.ibm.icu.math.BigDecimal(getStringRep(isPositive));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user