8233760: Result of BigDecimal.toString throws overflow exception on new BigDecimal(str)

Reviewed-by: darcy
This commit is contained in:
Raffaello Giulietti 2022-06-08 16:23:04 +00:00 committed by Joe Darcy
parent b92ce2699b
commit c15e10fb6c
2 changed files with 44 additions and 47 deletions

View file

@ -361,7 +361,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
private static final long serialVersionUID = 6108874887143696463L; private static final long serialVersionUID = 6108874887143696463L;
// Cache of common small BigDecimal values. // Cache of common small BigDecimal values.
private static final BigDecimal ZERO_THROUGH_TEN[] = { private static final BigDecimal[] ZERO_THROUGH_TEN = {
new BigDecimal(BigInteger.ZERO, 0, 0, 1), new BigDecimal(BigInteger.ZERO, 0, 0, 1),
new BigDecimal(BigInteger.ONE, 1, 0, 1), new BigDecimal(BigInteger.ONE, 1, 0, 1),
new BigDecimal(BigInteger.TWO, 2, 0, 1), new BigDecimal(BigInteger.TWO, 2, 0, 1),
@ -516,7 +516,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
// Use locals for all fields values until completion // Use locals for all fields values until completion
int prec = 0; // record precision value int prec = 0; // record precision value
int scl = 0; // record scale value long scl = 0; // record scale value
long rs = 0; // the compact value in long long rs = 0; // the compact value in long
BigInteger rb = null; // the inflated value in BigInteger BigInteger rb = null; // the inflated value in BigInteger
// use array bounds checking to handle too-long, len == 0, // use array bounds checking to handle too-long, len == 0,
@ -535,7 +535,6 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
// should now be at numeric part of the significand // should now be at numeric part of the significand
boolean dot = false; // true when there is a '.' boolean dot = false; // true when there is a '.'
long exp = 0; // exponent
char c; // current character char c; // current character
boolean isCompact = (len <= MAX_COMPACT_DIGITS); boolean isCompact = (len <= MAX_COMPACT_DIGITS);
// integer significand array & idx is the index to it. The array // integer significand array & idx is the index to it. The array
@ -585,10 +584,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
if (dot) if (dot)
++scl; ++scl;
} else if ((c == 'e') || (c == 'E')) { } else if ((c == 'e') || (c == 'E')) {
exp = parseExp(in, offset, len); scl -= parseExp(in, offset, len);
// Next test is required for backwards compatibility
if ((int) exp != exp) // overflow
throw new NumberFormatException("Exponent overflow.");
break; // [saves a test] break; // [saves a test]
} else { } else {
throw new NumberFormatException("Character " + c throw new NumberFormatException("Character " + c
@ -598,24 +594,20 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
} }
if (prec == 0) // no digits found if (prec == 0) // no digits found
throw new NumberFormatException("No digits found."); throw new NumberFormatException("No digits found.");
// Adjust scale if exp is not zero.
if (exp != 0) { // had significant exponent
scl = adjustScale(scl, exp);
}
rs = isneg ? -rs : rs; rs = isneg ? -rs : rs;
int mcp = mc.precision; int mcp = mc.precision;
int drop = prec - mcp; // prec has range [1, MAX_INT], mcp has range [0, MAX_INT]; int drop = prec - mcp; // prec has range [1, MAX_INT], mcp has range [0, MAX_INT];
// therefore, this subtract cannot overflow // therefore, this subtraction cannot overflow
if (mcp > 0 && drop > 0) { // do rounding if (mcp > 0 && drop > 0) { // do rounding
while (drop > 0) { while (drop > 0) {
scl = checkScaleNonZero((long) scl - drop); scl -= drop;
rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode); rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
prec = longDigitLength(rs); prec = longDigitLength(rs);
drop = prec - mcp; drop = prec - mcp;
} }
} }
} else { } else {
char coeff[] = new char[len]; char[] coeff = new char[len];
for (; len > 0; offset++, len--) { for (; len > 0; offset++, len--) {
c = in[offset]; c = in[offset];
// have digit // have digit
@ -652,19 +644,12 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
if ((c != 'e') && (c != 'E')) if ((c != 'e') && (c != 'E'))
throw new NumberFormatException("Character array" throw new NumberFormatException("Character array"
+ " is missing \"e\" notation exponential mark."); + " is missing \"e\" notation exponential mark.");
exp = parseExp(in, offset, len); scl -= parseExp(in, offset, len);
// Next test is required for backwards compatibility
if ((int) exp != exp) // overflow
throw new NumberFormatException("Exponent overflow.");
break; // [saves a test] break; // [saves a test]
} }
// here when no characters left // here when no characters left
if (prec == 0) // no digits found if (prec == 0) // no digits found
throw new NumberFormatException("No digits found."); throw new NumberFormatException("No digits found.");
// Adjust scale if exp is not zero.
if (exp != 0) { // had significant exponent
scl = adjustScale(scl, exp);
}
// Remove leading zeros from precision (digits count) // Remove leading zeros from precision (digits count)
rb = new BigInteger(coeff, isneg ? -1 : 1, prec); rb = new BigInteger(coeff, isneg ? -1 : 1, prec);
rs = compactValFor(rb); rs = compactValFor(rb);
@ -673,7 +658,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
if (rs == INFLATED) { if (rs == INFLATED) {
int drop = prec - mcp; int drop = prec - mcp;
while (drop > 0) { while (drop > 0) {
scl = checkScaleNonZero((long) scl - drop); scl -= drop;
rb = divideAndRoundByTenPow(rb, drop, mc.roundingMode.oldMode); rb = divideAndRoundByTenPow(rb, drop, mc.roundingMode.oldMode);
rs = compactValFor(rb); rs = compactValFor(rb);
if (rs != INFLATED) { if (rs != INFLATED) {
@ -687,7 +672,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
if (rs != INFLATED) { if (rs != INFLATED) {
int drop = prec - mcp; int drop = prec - mcp;
while (drop > 0) { while (drop > 0) {
scl = checkScaleNonZero((long) scl - drop); scl -= drop;
rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode); rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
prec = longDigitLength(rs); prec = longDigitLength(rs);
drop = prec - mcp; drop = prec - mcp;
@ -701,20 +686,14 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
nfe.initCause(e); nfe.initCause(e);
throw nfe; throw nfe;
} }
this.scale = scl; if ((int) scl != scl) // overflow
throw new NumberFormatException("Exponent overflow.");
this.scale = (int) scl;
this.precision = prec; this.precision = prec;
this.intCompact = rs; this.intCompact = rs;
this.intVal = rb; this.intVal = rb;
} }
private int adjustScale(int scl, long exp) {
long adjustedScale = scl - exp;
if (adjustedScale > Integer.MAX_VALUE || adjustedScale < Integer.MIN_VALUE)
throw new NumberFormatException("Scale out of range.");
scl = (int) adjustedScale;
return scl;
}
/* /*
* parse exponent * parse exponent
*/ */
@ -819,9 +798,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
* *
* <p>The exponent consists of the character {@code 'e'} * <p>The exponent consists of the character {@code 'e'}
* (<code>'&#92;u0065'</code>) or {@code 'E'} (<code>'&#92;u0045'</code>) * (<code>'&#92;u0065'</code>) or {@code 'E'} (<code>'&#92;u0045'</code>)
* followed by one or more decimal digits. The value of the * followed by one or more decimal digits.
* exponent must lie between -{@link Integer#MAX_VALUE} ({@link
* Integer#MIN_VALUE}+1) and {@link Integer#MAX_VALUE}, inclusive.
* *
* <p>More formally, the strings this constructor accepts are * <p>More formally, the strings this constructor accepts are
* described by the following grammar: * described by the following grammar:
@ -1440,7 +1417,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
long padding = (long) lhs.scale - augend.scale; long padding = (long) lhs.scale - augend.scale;
if (padding != 0) { // scales differ; alignment needed if (padding != 0) { // scales differ; alignment needed
BigDecimal arg[] = preAlign(lhs, augend, padding, mc); BigDecimal[] arg = preAlign(lhs, augend, padding, mc);
matchScale(arg); matchScale(arg);
lhs = arg[0]; lhs = arg[0];
augend = arg[1]; augend = arg[1];
@ -1975,7 +1952,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
* @since 1.5 * @since 1.5
*/ */
public BigDecimal remainder(BigDecimal divisor) { public BigDecimal remainder(BigDecimal divisor) {
BigDecimal divrem[] = this.divideAndRemainder(divisor); BigDecimal[] divrem = this.divideAndRemainder(divisor);
return divrem[1]; return divrem[1];
} }
@ -2005,7 +1982,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
* @since 1.5 * @since 1.5
*/ */
public BigDecimal remainder(BigDecimal divisor, MathContext mc) { public BigDecimal remainder(BigDecimal divisor, MathContext mc) {
BigDecimal divrem[] = this.divideAndRemainder(divisor, mc); BigDecimal[] divrem = this.divideAndRemainder(divisor, mc);
return divrem[1]; return divrem[1];
} }
@ -3831,7 +3808,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
* Powers of 10 which can be represented exactly in {@code * Powers of 10 which can be represented exactly in {@code
* double}. * double}.
*/ */
private static final double DOUBLE_10_POW[] = { private static final double[] DOUBLE_10_POW = {
1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5,
1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11, 1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11,
1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17,
@ -3842,7 +3819,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
* Powers of 10 which can be represented exactly in {@code * Powers of 10 which can be represented exactly in {@code
* float}. * float}.
*/ */
private static final float FLOAT_10_POW[] = { private static final float[] FLOAT_10_POW = {
1.0e0f, 1.0e1f, 1.0e2f, 1.0e3f, 1.0e4f, 1.0e5f, 1.0e0f, 1.0e1f, 1.0e2f, 1.0e3f, 1.0e4f, 1.0e5f,
1.0e6f, 1.0e7f, 1.0e8f, 1.0e9f, 1.0e10f 1.0e6f, 1.0e7f, 1.0e8f, 1.0e9f, 1.0e10f
}; };
@ -4148,7 +4125,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
1000000000000000000L // 18 / 10^18 1000000000000000000L // 18 / 10^18
}; };
private static volatile BigInteger BIG_TEN_POWERS_TABLE[] = { private static volatile BigInteger[] BIG_TEN_POWERS_TABLE = {
BigInteger.ONE, BigInteger.ONE,
BigInteger.valueOf(10), BigInteger.valueOf(10),
BigInteger.valueOf(100), BigInteger.valueOf(100),
@ -4175,7 +4152,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
private static final int BIG_TEN_POWERS_TABLE_MAX = private static final int BIG_TEN_POWERS_TABLE_MAX =
16 * BIG_TEN_POWERS_TABLE_INITLEN; 16 * BIG_TEN_POWERS_TABLE_INITLEN;
private static final long THRESHOLDS_TABLE[] = { private static final long[] THRESHOLDS_TABLE = {
Long.MAX_VALUE, // 0 Long.MAX_VALUE, // 0
Long.MAX_VALUE/10L, // 1 Long.MAX_VALUE/10L, // 1
Long.MAX_VALUE/100L, // 2 Long.MAX_VALUE/100L, // 2
@ -4908,7 +4885,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
* @throws ArithmeticException if scale overflows. * @throws ArithmeticException if scale overflows.
*/ */
private static BigDecimal createAndStripZerosToMatchScale(BigInteger intVal, int scale, long preferredScale) { private static BigDecimal createAndStripZerosToMatchScale(BigInteger intVal, int scale, long preferredScale) {
BigInteger qr[]; // quotient-remainder pair BigInteger[] qr; // quotient-remainder pair
while (intVal.compareMagnitude(BigInteger.TEN) >= 0 while (intVal.compareMagnitude(BigInteger.TEN) >= 0
&& scale > preferredScale) { && scale > preferredScale) {
if (intVal.testBit(0)) if (intVal.testBit(0))

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,7 +26,7 @@
* @library /test/lib * @library /test/lib
* @build jdk.test.lib.RandomFactory * @build jdk.test.lib.RandomFactory
* @run main StringConstructor * @run main StringConstructor
* @bug 4103117 4331084 4488017 4490929 6255285 6268365 8074460 8078672 * @bug 4103117 4331084 4488017 4490929 6255285 6268365 8074460 8078672 8233760
* @summary Tests the BigDecimal string constructor (use -Dseed=X to set PRNG seed). * @summary Tests the BigDecimal string constructor (use -Dseed=X to set PRNG seed).
* @key randomness * @key randomness
*/ */
@ -66,11 +66,20 @@ public class StringConstructor {
constructWithError("10e"+Integer.MIN_VALUE); constructWithError("10e"+Integer.MIN_VALUE);
constructWithError("0.01e"+Integer.MIN_VALUE); constructWithError("0.01e"+Integer.MIN_VALUE);
constructWithError("1e"+((long)Integer.MIN_VALUE-1)); constructWithError("1e"+((long)Integer.MIN_VALUE-1));
constructWithError("1e"+((long)Integer.MAX_VALUE + 1));
leadingExponentZeroTest(); leadingExponentZeroTest();
nonAsciiZeroTest(); nonAsciiZeroTest();
/* These BigDecimals produce a string with an exponent > Integer.MAX_VALUE */
roundtripWithAbnormalExponent(BigDecimal.valueOf(10, Integer.MIN_VALUE));
roundtripWithAbnormalExponent(BigDecimal.valueOf(Long.MIN_VALUE, Integer.MIN_VALUE));
roundtripWithAbnormalExponent(new BigDecimal(new BigInteger("1" + "0".repeat(100)), Integer.MIN_VALUE));
/* These Strings have an exponent > Integer.MAX_VALUE */
roundtripWithAbnormalExponent("1.0E+2147483649");
roundtripWithAbnormalExponent("-9.223372036854775808E+2147483666");
roundtripWithAbnormalExponent("1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E+2147483748");
// Roundtrip tests // Roundtrip tests
Random random = RandomFactory.getRandom(); Random random = RandomFactory.getRandom();
for (int i=0; i<100; i++) { for (int i=0; i<100; i++) {
@ -95,6 +104,17 @@ public class StringConstructor {
} }
} }
private static void roundtripWithAbnormalExponent(BigDecimal bd) {
if (!bd.equals(new BigDecimal(bd.toString()))) {
throw new RuntimeException("Abnormal exponent roundtrip failure");
}
}
private static void roundtripWithAbnormalExponent(String s) {
if (!s.equals(new BigDecimal(s).toString())) {
throw new RuntimeException("Abnormal exponent roundtrip failure");
}
}
/* /*
* Verify precision is set properly if the significand has * Verify precision is set properly if the significand has