ICU-20494 Fixes to very large magnitude exponents in number parsing.

- Do not depend on ArithmeticException string in ICU4J.
- Return correct string in ICU4C.
- Fix related issue in applyMaxInteger.
This commit is contained in:
Shane Carr 2019-03-12 18:06:21 -07:00 committed by Shane F. Carr
parent b5ad35dda8
commit e318c0c374
8 changed files with 128 additions and 9 deletions

View File

@ -732,7 +732,11 @@ CharString *Formattable::internalGetCharString(UErrorCode &status) {
// Older ICUs called uprv_decNumberToString here, which is not exactly the same as // Older ICUs called uprv_decNumberToString here, which is not exactly the same as
// DecimalQuantity::toScientificString(). The biggest difference is that uprv_decNumberToString does // DecimalQuantity::toScientificString(). The biggest difference is that uprv_decNumberToString does
// not print scientific notation for magnitudes greater than -5 and smaller than some amount (+5?). // not print scientific notation for magnitudes greater than -5 and smaller than some amount (+5?).
if (fDecimalQuantity->isZero()) { if (fDecimalQuantity->isInfinite()) {
fDecimalStr->append("Infinity", status);
} else if (fDecimalQuantity->isNaN()) {
fDecimalStr->append("NaN", status);
} else if (fDecimalQuantity->isZero()) {
fDecimalStr->append("0", -1, status); fDecimalStr->append("0", -1, status);
} else if (fType==kLong || fType==kInt64 || // use toPlainString for integer types } else if (fType==kLong || fType==kInt64 || // use toPlainString for integer types
(fDecimalQuantity->getMagnitude() != INT32_MIN && std::abs(fDecimalQuantity->getMagnitude()) < 5)) { (fDecimalQuantity->getMagnitude() != INT32_MIN && std::abs(fDecimalQuantity->getMagnitude()) < 5)) {

View File

@ -160,6 +160,11 @@ void DecimalQuantity::applyMaxInteger(int32_t maxInt) {
return; return;
} }
if (maxInt <= scale) {
setBcdToZero();
return;
}
int32_t magnitude = getMagnitude(); int32_t magnitude = getMagnitude();
if (maxInt <= magnitude) { if (maxInt <= magnitude) {
popFromLeft(magnitude - maxInt + 1); popFromLeft(magnitude - maxInt + 1);
@ -983,6 +988,7 @@ void DecimalQuantity::shiftRight(int32_t numDigits) {
} }
void DecimalQuantity::popFromLeft(int32_t numDigits) { void DecimalQuantity::popFromLeft(int32_t numDigits) {
U_ASSERT(numDigits <= precision);
if (usingBytes) { if (usingBytes) {
int i = precision - 1; int i = precision - 1;
for (; i >= precision - numDigits; i--) { for (; i >= precision - numDigits; i--) {

View File

@ -1657,6 +1657,31 @@ void NumberFormatterApiTest::integerWidth() {
u"00.08765", u"00.08765",
u"00.008765", u"00.008765",
u"00"); u"00");
assertFormatSingle(
u"Integer Width Remove All A",
u"integer-width/00",
NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
"en",
2500,
u"00");
assertFormatSingle(
u"Integer Width Remove All B",
u"integer-width/00",
NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
"en",
25000,
u"00");
assertFormatSingle(
u"Integer Width Remove All B, Bytes Mode",
u"integer-width/00",
NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
"en",
// Note: this double produces all 17 significant digits
10000000000000002000.0,
u"00");
} }
void NumberFormatterApiTest::symbols() { void NumberFormatterApiTest::symbols() {

View File

@ -9443,6 +9443,32 @@ void NumberFormatTest::Test20037_ScientificIntegerOverflow() {
assertEquals(u"Should not overflow", assertEquals(u"Should not overflow",
u"3E-2147483648", u"3E-2147483648",
{sp.data(), sp.length(), US_INV}); {sp.data(), sp.length(), US_INV});
// Test largest parseable exponent
result = Formattable();
nf->parse(u"9876e2147483643", result, status);
sp = result.getDecimalNumber(status);
assertEquals(u"Should not overflow",
u"9.876E+2147483646",
{sp.data(), sp.length(), US_INV});
// Test max value as well
const char16_t* infinityInputs[] = {
u"9876e2147483644",
u"9876e2147483645",
u"9876e2147483646",
u"9876e2147483647",
u"9876e2147483648",
u"9876e2147483649",
};
for (const auto& input : infinityInputs) {
result = Formattable();
nf->parse(input, result, status);
sp = result.getDecimalNumber(status);
assertEquals(UnicodeString("Should become Infinity: ") + input,
u"Infinity",
{sp.data(), sp.length(), US_INV});
}
} }
void NumberFormatTest::Test13840_ParseLongStringCrash() { void NumberFormatTest::Test13840_ParseLongStringCrash() {

View File

@ -141,6 +141,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
return; return;
} }
if (maxInt <= scale) {
setBcdToZero();
return;
}
int magnitude = getMagnitude(); int magnitude = getMagnitude();
if (maxInt <= magnitude) { if (maxInt <= magnitude) {
popFromLeft(magnitude - maxInt + 1); popFromLeft(magnitude - maxInt + 1);
@ -205,6 +210,8 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
if (precision != 0) { if (precision != 0) {
scale = Utility.addExact(scale, delta); scale = Utility.addExact(scale, delta);
origDelta = Utility.addExact(origDelta, delta); origDelta = Utility.addExact(origDelta, delta);
// Make sure that precision + scale won't overflow, either
Utility.addExact(scale, precision);
} }
} }

View File

@ -156,6 +156,7 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
@Override @Override
protected void popFromLeft(int numDigits) { protected void popFromLeft(int numDigits) {
assert numDigits <= precision;
if (usingBytes) { if (usingBytes) {
int i = precision - 1; int i = precision - 1;
for (; i >= precision - numDigits; i--) { for (; i >= precision - numDigits; i--) {
@ -252,17 +253,16 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
tempLong = tempLong * 10 + getDigitPos(shift); tempLong = tempLong * 10 + getDigitPos(shift);
} }
BigDecimal result = BigDecimal.valueOf(tempLong); BigDecimal result = BigDecimal.valueOf(tempLong);
try { // Test that the new scale fits inside the BigDecimal
long newScale = result.scale() + scale;
if (newScale <= Integer.MIN_VALUE) {
result = BigDecimal.ZERO;
} else {
result = result.scaleByPowerOfTen(scale); result = result.scaleByPowerOfTen(scale);
} catch (ArithmeticException e) {
if (e.getMessage().contains("Underflow")) {
result = BigDecimal.ZERO;
} else {
throw e;
}
} }
if (isNegative()) if (isNegative()) {
result = result.negate(); result = result.negate();
}
return result; return result;
} }
} }

View File

@ -6511,6 +6511,32 @@ public class NumberFormatTest extends TestFmwk {
result = nf.parse(".0003e-2147483644"); result = nf.parse(".0003e-2147483644");
assertEquals("Should not overflow", assertEquals("Should not overflow",
"0", result.toString()); "0", result.toString());
// Test largest parseable exponent
// This is limited by ICU's BigDecimal implementation
result = nf.parse("1e999999999");
assertEquals("Should not overflow",
"1E+999999999", result.toString());
// Test max value as well
String[] infinityInputs = {
"9876e1000000000",
"9876e2147483640",
"9876e2147483641",
"9876e2147483642",
"9876e2147483643",
"9876e2147483644",
"9876e2147483645",
"9876e2147483646",
"9876e2147483647",
"9876e2147483648",
"9876e2147483649",
};
for (String input : infinityInputs) {
result = nf.parse(input);
assertEquals("Should become Infinity: " + input,
"Infinity", result.toString());
}
} }
@Test @Test

View File

@ -1591,6 +1591,31 @@ public class NumberFormatterApiTest {
"00.08765", "00.08765",
"00.008765", "00.008765",
"00"); "00");
assertFormatSingle(
"Integer Width Remove All A",
"integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
2500,
"00");
assertFormatSingle(
"Integer Width Remove All B",
"integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
25000,
"00");
assertFormatSingle(
"Integer Width Remove All B, Bytes Mode",
"integer-width/00",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
ULocale.ENGLISH,
// Note: this double produces all 17 significant digits
10000000000000002000.0,
"00");
} }
@Test @Test