8211936: Better String parsing

Co-authored-by: Joe Darcy <joe.darcy@oracle.com>
Co-authored-by: Paul Hohensee <hohensee@amazon.com>
Reviewed-by: bpb, darcy
This commit is contained in:
Brian Burkhalter 2019-01-22 09:45:30 -08:00
parent 52da980fa2
commit 724120457a
4 changed files with 482 additions and 60 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2019, 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
@ -3414,9 +3414,32 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
*/ */
@Override @Override
public long longValue(){ public long longValue(){
return (intCompact != INFLATED && scale == 0) ? if (intCompact != INFLATED && scale == 0) {
intCompact: return intCompact;
toBigInteger().longValue(); } else {
// Fastpath zero and small values
if (this.signum() == 0 || fractionOnly() ||
// Fastpath very large-scale values that will result
// in a truncated value of zero. If the scale is -64
// or less, there are at least 64 powers of 10 in the
// value of the numerical result. Since 10 = 2*5, in
// that case there would also be 64 powers of 2 in the
// result, meaning all 64 bits of a long will be zero.
scale <= -64) {
return 0;
} else {
return toBigInteger().longValue();
}
}
}
/**
* Return true if a nonzero BigDecimal has an absolute value less
* than one; i.e. only has fraction digits.
*/
private boolean fractionOnly() {
assert this.signum() != 0;
return (this.precision() - this.scale) <= 0;
} }
/** /**
@ -3434,15 +3457,20 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
public long longValueExact() { public long longValueExact() {
if (intCompact != INFLATED && scale == 0) if (intCompact != INFLATED && scale == 0)
return intCompact; return intCompact;
// Fastpath zero
if (this.signum() == 0)
return 0;
// Fastpath numbers less than 1.0 (the latter can be very slow
// to round if very small)
if (fractionOnly())
throw new ArithmeticException("Rounding necessary");
// If more than 19 digits in integer part it cannot possibly fit // If more than 19 digits in integer part it cannot possibly fit
if ((precision() - scale) > 19) // [OK for negative scale too] if ((precision() - scale) > 19) // [OK for negative scale too]
throw new java.lang.ArithmeticException("Overflow"); throw new java.lang.ArithmeticException("Overflow");
// Fastpath zero and < 1.0 numbers (the latter can be very slow
// to round if very small)
if (this.signum() == 0)
return 0;
if ((this.precision() - this.scale) <= 0)
throw new ArithmeticException("Rounding necessary");
// round to an integer, with Exception if decimal part non-0 // round to an integer, with Exception if decimal part non-0
BigDecimal num = this.setScale(0, ROUND_UNNECESSARY); BigDecimal num = this.setScale(0, ROUND_UNNECESSARY);
if (num.precision() >= 19) // need to check carefully if (num.precision() >= 19) // need to check carefully
@ -3486,7 +3514,7 @@ public class BigDecimal extends Number implements Comparable<BigDecimal> {
public int intValue() { public int intValue() {
return (intCompact != INFLATED && scale == 0) ? return (intCompact != INFLATED && scale == 0) ?
(int)intCompact : (int)intCompact :
toBigInteger().intValue(); (int)longValue();
} }
/** /**

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8211936
* @summary Tests of BigDecimal.intValueExact
*/
import java.math.*;
import java.util.List;
import java.util.Map;
import static java.util.Map.entry;
public class IntValueExactTests {
public static void main(String... args) {
int failures = 0;
failures += intValueExactSuccessful();
failures += intValueExactExceptional();
if (failures > 0) {
throw new RuntimeException("Incurred " + failures +
" failures while testing intValueExact.");
}
}
private static int simpleIntValueExact(BigDecimal bd) {
return bd.toBigIntegerExact().intValue();
}
private static int intValueExactSuccessful() {
int failures = 0;
// Strings used to create BigDecimal instances on which invoking
// intValueExact() will succeed.
Map<BigDecimal, Integer> successCases =
Map.ofEntries(entry(new BigDecimal("2147483647"), Integer.MAX_VALUE), // 2^31 -1
entry(new BigDecimal("2147483647.0"), Integer.MAX_VALUE),
entry(new BigDecimal("2147483647.00"), Integer.MAX_VALUE),
entry(new BigDecimal("-2147483648"), Integer.MIN_VALUE), // -2^31
entry(new BigDecimal("-2147483648.0"), Integer.MIN_VALUE),
entry(new BigDecimal("-2147483648.00"),Integer.MIN_VALUE),
entry(new BigDecimal("1e0"), 1),
entry(new BigDecimal(BigInteger.ONE, -9), 1_000_000_000),
entry(new BigDecimal("0e13"), 0), // Fast path zero
entry(new BigDecimal("0e32"), 0),
entry(new BigDecimal("0e512"), 0),
entry(new BigDecimal("10.000000000000000000000000000000000"), 10));
for (var testCase : successCases.entrySet()) {
BigDecimal bd = testCase.getKey();
int expected = testCase.getValue();
try {
int intValueExact = bd.intValueExact();
if (expected != intValueExact ||
intValueExact != simpleIntValueExact(bd)) {
failures++;
System.err.println("Unexpected intValueExact result " + intValueExact +
" on " + bd);
}
} catch (Exception e) {
failures++;
System.err.println("Error on " + bd + "\tException message:" + e.getMessage());
}
}
return failures;
}
private static int intValueExactExceptional() {
int failures = 0;
List<BigDecimal> exceptionalCases =
List.of(new BigDecimal("2147483648"), // Integer.MAX_VALUE + 1
new BigDecimal("2147483648.0"),
new BigDecimal("2147483648.00"),
new BigDecimal("-2147483649"), // Integer.MIN_VALUE - 1
new BigDecimal("-2147483649.1"),
new BigDecimal("-2147483649.01"),
new BigDecimal("9999999999999999999999999999999"),
new BigDecimal("10000000000000000000000000000000"),
new BigDecimal("0.99"),
new BigDecimal("0.999999999999999999999"));
for (BigDecimal bd : exceptionalCases) {
try {
int intValueExact = bd.intValueExact();
failures++;
System.err.println("Unexpected non-exceptional intValueExact on " + bd);
} catch (ArithmeticException e) {
// Success;
}
}
return failures;
}
}

View File

@ -0,0 +1,241 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8211936
* @summary Tests of BigDecimal.intValue() and BigDecimal.longValue()
*/
import java.math.BigDecimal;
import java.util.Map;
public class IntegralValueTests {
public static void main(String... args) {
int failures =
integralValuesTest(INT_VALUES, true) +
integralValuesTest(LONG_VALUES, false);
if (failures != 0) {
throw new RuntimeException
("Incurred " + failures + " failures for {int,long}Value().");
}
}
private static final Map<BigDecimal, Number> INT_VALUES =
Map.ofEntries(
// 2**31 - 1
Map.entry(new BigDecimal("2147483647"), Integer.MAX_VALUE),
Map.entry(new BigDecimal("2147483647.0"), Integer.MAX_VALUE),
Map.entry(new BigDecimal("2147483647.00"), Integer.MAX_VALUE),
Map.entry(new BigDecimal("-2147483647"), -Integer.MAX_VALUE),
Map.entry(new BigDecimal("-2147483647.0"), -Integer.MAX_VALUE),
// -2**31
Map.entry(new BigDecimal("-2147483648"), Integer.MIN_VALUE),
Map.entry(new BigDecimal("-2147483648.1"), Integer.MIN_VALUE),
Map.entry(new BigDecimal("-2147483648.01"), Integer.MIN_VALUE),
// -2**31 + 1 truncation to 2**31 - 1
Map.entry(new BigDecimal("-2147483649"), Integer.MAX_VALUE),
// 2**64 - 1 truncation to 1
Map.entry(new BigDecimal("4294967295"), -1),
// 2**64 truncation to 0
Map.entry(new BigDecimal("4294967296"), 0),
// Fast path truncation to 0
Map.entry(new BigDecimal("1e32"), 0),
// Slow path truncation to -2**31
Map.entry(new BigDecimal("1e31"), Integer.MIN_VALUE),
// Slow path
Map.entry(new BigDecimal("1e0"), 1),
// Fast path round to 0
Map.entry(new BigDecimal("9e-1"), 0),
// Some random values
Map.entry(new BigDecimal("900e-1"), 90), // Increasing negative exponents
Map.entry(new BigDecimal("900e-2"), 9),
Map.entry(new BigDecimal("900e-3"), 0),
// Fast path round to 0
Map.entry(new BigDecimal("123456789e-9"), 0),
// Slow path round to 1
Map.entry(new BigDecimal("123456789e-8"), 1),
// Increasing positive exponents
Map.entry(new BigDecimal("10000001e1"), 100000010),
Map.entry(new BigDecimal("10000001e10"), -1315576832),
Map.entry(new BigDecimal("10000001e100"), 0),
Map.entry(new BigDecimal("10000001e1000"), 0),
Map.entry(new BigDecimal("10000001e10000"), 0),
Map.entry(new BigDecimal("10000001e100000"), 0),
Map.entry(new BigDecimal("10000001e1000000"), 0),
Map.entry(new BigDecimal("10000001e10000000"), 0),
Map.entry(new BigDecimal("10000001e100000000"), 0),
Map.entry(new BigDecimal("10000001e1000000000"), 0),
// Increasing negative exponents
Map.entry(new BigDecimal("10000001e-1"), 1000000),
Map.entry(new BigDecimal("10000001e-10"), 0),
Map.entry(new BigDecimal("10000001e-100"), 0),
Map.entry(new BigDecimal("10000001e-1000"), 0),
Map.entry(new BigDecimal("10000001e-10000"), 0),
Map.entry(new BigDecimal("10000001e-100000"), 0),
Map.entry(new BigDecimal("10000001e-1000000"), 0),
Map.entry(new BigDecimal("10000001e-10000000"), 0),
Map.entry(new BigDecimal("10000001e-100000000"), 0),
Map.entry(new BigDecimal("10000001e-1000000000"), 0),
// Currency calculation to 4 places
Map.entry(new BigDecimal("12345.0001"), 12345),
Map.entry(new BigDecimal("12345.9999"), 12345),
Map.entry(new BigDecimal("-12345.0001"), -12345),
Map.entry(new BigDecimal("-12345.9999"), -12345));
private static final Map<BigDecimal, Number> LONG_VALUES =
Map.ofEntries(
// 2**63 - 1
Map.entry(new BigDecimal("9223372036854775807"), Long.MAX_VALUE),
Map.entry(new BigDecimal("9223372036854775807.0"), Long.MAX_VALUE),
Map.entry(new BigDecimal("9223372036854775807.00"), Long.MAX_VALUE),
// 2**63 truncation to -2**63
Map.entry(new BigDecimal("-9223372036854775808"), Long.MIN_VALUE),
Map.entry(new BigDecimal("-9223372036854775808.1"), Long.MIN_VALUE),
Map.entry(new BigDecimal("-9223372036854775808.01"), Long.MIN_VALUE),
// -2**63 + 1 truncation to 2**63 - 1
Map.entry(new BigDecimal("-9223372036854775809"), 9223372036854775807L),
// 2**64 - 1 truncation to -1
Map.entry(new BigDecimal("18446744073709551615"), -1L),
// 2**64 truncation to 0
Map.entry(new BigDecimal("18446744073709551616"), 0L),
// Slow path truncation to -2**63
Map.entry(new BigDecimal("1e63"), -9223372036854775808L),
Map.entry(new BigDecimal("-1e63"), -9223372036854775808L),
// Fast path with larger magnitude scale
Map.entry(new BigDecimal("1e64"), 0L),
Map.entry(new BigDecimal("-1e64"), 0L),
Map.entry(new BigDecimal("1e65"), 0L),
Map.entry(new BigDecimal("-1e65"), 0L),
// Slow path
Map.entry(new BigDecimal("1e0"), 1L),
// Fast path round to 0
Map.entry(new BigDecimal("9e-1"), 0L),
// Some random values
Map.entry(new BigDecimal("900e-1"), 90L), // Increasing negative exponents
Map.entry(new BigDecimal("900e-2"), 9L),
Map.entry(new BigDecimal("900e-3"), 0L),
// Fast path round to 0
Map.entry(new BigDecimal("123456789e-9"), 0L),
// Slow path round to 1
Map.entry(new BigDecimal("123456789e-8"), 1L),
// Increasing positive exponents
Map.entry(new BigDecimal("10000001e1"), 100000010L),
Map.entry(new BigDecimal("10000001e10"), 100000010000000000L),
Map.entry(new BigDecimal("10000001e100"), 0L),
Map.entry(new BigDecimal("10000001e1000"), 0L),
Map.entry(new BigDecimal("10000001e10000"), 0L),
Map.entry(new BigDecimal("10000001e100000"), 0L),
Map.entry(new BigDecimal("10000001e1000000"), 0L),
Map.entry(new BigDecimal("10000001e10000000"), 0L),
Map.entry(new BigDecimal("10000001e100000000"), 0L),
Map.entry(new BigDecimal("10000001e1000000000"), 0L),
// Increasing negative exponents
Map.entry(new BigDecimal("10000001e-1"), 1000000L),
Map.entry(new BigDecimal("10000001e-10"), 0L),
Map.entry(new BigDecimal("10000001e-100"), 0L),
Map.entry(new BigDecimal("10000001e-1000"), 0L),
Map.entry(new BigDecimal("10000001e-10000"), 0L),
Map.entry(new BigDecimal("10000001e-100000"), 0L),
Map.entry(new BigDecimal("10000001e-1000000"), 0L),
Map.entry(new BigDecimal("10000001e-10000000"), 0L),
Map.entry(new BigDecimal("10000001e-100000000"), 0L),
Map.entry(new BigDecimal("10000001e-1000000000"), 0L),
// Currency calculation to 4 places
Map.entry(new BigDecimal("12345.0001"), 12345L),
Map.entry(new BigDecimal("12345.9999"), 12345L),
Map.entry(new BigDecimal("-12345.0001"), -12345L),
Map.entry(new BigDecimal("-12345.9999"), -12345L));
private static int integralValuesTest(Map<BigDecimal, Number> v, boolean isInt) {
System.err.format("Testing %s%n", isInt ? "Integer" : "Long");
int failures = 0;
for (var testCase : v.entrySet()) {
BigDecimal bd = testCase.getKey();
Number expected = testCase.getValue();
try {
if (isInt) {
int intValue = bd.intValue();
if (intValue != (int)expected) {
failures += reportError(bd, expected, intValue, isInt);
}
} else {
long longValue = bd.longValue();
if (longValue != (long)expected) {
failures += reportError(bd, expected, longValue, isInt);
}
}
} catch (Exception e) {
failures++;
System.err.format("Unexpected exception %s for %s%n",
e, bd.toString());
}
}
return failures;
}
private static int reportError(BigDecimal bd, Number expected, long longValue, boolean isInt) {
System.err.format("For %s, scale=%d, expected %d, actual %d, simple %d%n",
bd.toString(), bd.scale(),
(isInt ? (Integer) expected : (Long) expected ),
longValue,
(isInt ? simpleIntValue(bd): simpleLongValue(bd) ));
return 1;
}
private static long simpleLongValue(BigDecimal bd) {
return bd.toBigInteger().longValue();
}
private static int simpleIntValue(BigDecimal bd) {
return bd.toBigInteger().intValue();
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2005, 2019, 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
@ -23,65 +23,98 @@
/** /**
* @test * @test
* @bug 6806261 * @bug 6806261 8211936
* @summary Tests of BigDecimal.longValueExact * @summary Tests of BigDecimal.longValueExact
*/ */
import java.math.*; import java.math.*;
import java.util.List;
import java.util.Map;
import static java.util.Map.entry;
public class LongValueExactTests { public class LongValueExactTests {
public static void main(String... args) {
private static int longValueExactTests() {
int failures = 0; int failures = 0;
String[] testStrings = { failures += longValueExactSuccessful();
"9223372036854775807", failures += longValueExactExceptional();
"9223372036854775807.0",
"9223372036854775807.00",
"-9223372036854775808",
"-9223372036854775808.0",
"-9223372036854775808.00",
};
for (String longValue : testStrings) {
try {
BigDecimal bd = new BigDecimal(longValue);
long longValueExact = bd.longValueExact();
} catch (Exception e) {
failures++;
}
}
// The following Strings are supposed to make longValueExact throw
// ArithmeticException.
String[] testStrings2 = {
"9223372036854775808",
"9223372036854775808.0",
"9223372036854775808.00",
"-9223372036854775809",
"-9223372036854775808.1",
"-9223372036854775808.01",
};
for (String bigValue : testStrings2) {
try {
BigDecimal bd = new BigDecimal(bigValue);
long longValueExact = bd.longValueExact();
failures++;
} catch (ArithmeticException e) {
// Success;
}
}
return failures;
}
public static void main(String argv[]) {
int failures = 0;
failures += longValueExactTests();
if (failures > 0) { if (failures > 0) {
throw new RuntimeException("Incurred " + failures + throw new RuntimeException("Incurred " + failures +
" failures while testing longValueExact."); " failures while testing longValueExact.");
} }
} }
private static long simpleLongValueExact(BigDecimal bd) {
return bd.toBigIntegerExact().longValue();
}
private static int longValueExactSuccessful() {
int failures = 0;
// Strings used to create BigDecimal instances on which invoking
// longValueExact() will succeed.
Map<BigDecimal, Long> successCases =
Map.ofEntries(entry(new BigDecimal("9223372036854775807"), Long.MAX_VALUE), // 2^63 -1
entry(new BigDecimal("9223372036854775807.0"), Long.MAX_VALUE),
entry(new BigDecimal("9223372036854775807.00"), Long.MAX_VALUE),
entry(new BigDecimal("-9223372036854775808"), Long.MIN_VALUE), // -2^63
entry(new BigDecimal("-9223372036854775808.0"), Long.MIN_VALUE),
entry(new BigDecimal("-9223372036854775808.00"),Long.MIN_VALUE),
entry(new BigDecimal("1e0"), 1L),
entry(new BigDecimal(BigInteger.ONE, -18), 1_000_000_000_000_000_000L),
entry(new BigDecimal("0e13"), 0L), // Fast path zero
entry(new BigDecimal("0e64"), 0L),
entry(new BigDecimal("0e1024"), 0L),
entry(new BigDecimal("10.000000000000000000000000000000000"), 10L));
for (var testCase : successCases.entrySet()) {
BigDecimal bd = testCase.getKey();
long expected = testCase.getValue();
try {
long longValueExact = bd.longValueExact();
if (expected != longValueExact ||
longValueExact != simpleLongValueExact(bd)) {
failures++;
System.err.println("Unexpected longValueExact result " + longValueExact +
" on " + bd);
}
} catch (Exception e) {
failures++;
System.err.println("Error on " + bd + "\tException message:" + e.getMessage());
}
}
return failures;
}
private static int longValueExactExceptional() {
int failures = 0;
List<BigDecimal> exceptionalCases =
List.of(new BigDecimal("9223372036854775808"), // Long.MAX_VALUE + 1
new BigDecimal("9223372036854775808.0"),
new BigDecimal("9223372036854775808.00"),
new BigDecimal("-9223372036854775809"), // Long.MIN_VALUE - 1
new BigDecimal("-9223372036854775808.1"),
new BigDecimal("-9223372036854775808.01"),
new BigDecimal("9999999999999999999"),
new BigDecimal("10000000000000000000"),
new BigDecimal("0.99"),
new BigDecimal("0.999999999999999999999"));
for (BigDecimal bd : exceptionalCases) {
try {
long longValueExact = bd.longValueExact();
failures++;
System.err.println("Unexpected non-exceptional longValueExact on " + bd);
} catch (ArithmeticException e) {
// Success;
}
}
return failures;
}
} }