Skip to content

Commit

Permalink
Merge pull request #453 from johnjaylward/UseBigDecimalDefaultParse
Browse files Browse the repository at this point in the history
changes number parsing to use BigDecimal as the backing type
  • Loading branch information
stleary authored Jun 1, 2020
2 parents fee6ddb + 56d33b8 commit 19bb6fd
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 253 deletions.
84 changes: 37 additions & 47 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -2109,48 +2109,54 @@ protected static Number stringToNumber(final String val) throws NumberFormatExce
if ((initial >= '0' && initial <= '9') || initial == '-') {
// decimal representation
if (isDecimalNotation(val)) {
// quick dirty way to see if we need a BigDecimal instead of a Double
// this only handles some cases of overflow or underflow
if (val.length()>14) {
return new BigDecimal(val);
// Use a BigDecimal all the time so we keep the original
// representation. BigDecimal doesn't support -0.0, ensure we
// keep that by forcing a decimal.
try {
BigDecimal bd = new BigDecimal(val);
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
return Double.valueOf(-0.0);
}
return bd;
} catch (NumberFormatException retryAsDouble) {
// this is to support "Hex Floats" like this: 0x1.0P-1074
try {
Double d = Double.valueOf(val);
if(d.isNaN() || d.isInfinite()) {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
return d;
} catch (NumberFormatException ignore) {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
}
}
// block items like 00 01 etc. Java number parsers treat these as Octal.
if(initial == '0' && val.length() > 1) {
char at1 = val.charAt(1);
if(at1 >= '0' && at1 <= '9') {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
final Double d = Double.valueOf(val);
if (d.isInfinite() || d.isNaN()) {
// if we can't parse it as a double, go up to BigDecimal
// this is probably due to underflow like 4.32e-678
// or overflow like 4.65e5324. The size of the string is small
// but can't be held in a Double.
return new BigDecimal(val);
} else if (initial == '-' && val.length() > 2) {
char at1 = val.charAt(1);
char at2 = val.charAt(2);
if(at1 == '0' && at2 >= '0' && at2 <= '9') {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
return d;
}
// integer representation.
// This will narrow any values to the smallest reasonable Object representation
// (Integer, Long, or BigInteger)

// string version
// The compare string length method reduces GC,
// but leads to smaller integers being placed in larger wrappers even though not
// needed. i.e. 1,000,000,000 -> Long even though it's an Integer
// 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
//if(val.length()<=9){
// return Integer.valueOf(val);
//}
//if(val.length()<=18){
// return Long.valueOf(val);
//}
//return new BigInteger(val);

// BigInteger version: We use a similar bitLength compare as
// BigInteger down conversion: We use a similar bitLenth compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is
// long lived. Which is the better tradeoff? This is closer to what's
// in stringToValue.
// long lived.
BigInteger bi = new BigInteger(val);
if(bi.bitLength()<=31){
if(bi.bitLength() <= 31){
return Integer.valueOf(bi.intValue());
}
if(bi.bitLength()<=63){
if(bi.bitLength() <= 63){
return Long.valueOf(bi.longValue());
}
return bi;
Expand Down Expand Up @@ -2194,23 +2200,7 @@ public static Object stringToValue(String string) {
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
// if we want full Big Number support the contents of this
// `try` block can be replaced with:
// return stringToNumber(string);
if (isDecimalNotation(string)) {
Double d = Double.valueOf(string);
if (!d.isInfinite() && !d.isNaN()) {
return d;
}
} else {
Long myLong = Long.valueOf(string);
if (string.equals(myLong.toString())) {
if (myLong.longValue() == myLong.intValue()) {
return Integer.valueOf(myLong.intValue());
}
return myLong;
}
}
return stringToNumber(string);
} catch (Exception ignore) {
}
}
Expand Down
99 changes: 80 additions & 19 deletions src/main/java/org/json/XML.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ of this software and associated documentation files (the "Software"), to deal

import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;

/**
Expand Down Expand Up @@ -424,17 +426,20 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
*/
// To maintain compatibility with the Android API, this method is a direct copy of
// the one in JSONObject. Changes made here should be reflected there.
// This method should not make calls out of the XML object.
public static Object stringToValue(String string) {
if (string.equals("")) {
if ("".equals(string)) {
return string;
}
if (string.equalsIgnoreCase("true")) {

// check JSON key words true/false/null
if ("true".equalsIgnoreCase(string)) {
return Boolean.TRUE;
}
if (string.equalsIgnoreCase("false")) {
if ("false".equalsIgnoreCase(string)) {
return Boolean.FALSE;
}
if (string.equalsIgnoreCase("null")) {
if ("null".equalsIgnoreCase(string)) {
return JSONObject.NULL;
}

Expand All @@ -446,28 +451,84 @@ public static Object stringToValue(String string) {
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
// if we want full Big Number support this block can be replaced with:
// return stringToNumber(string);
if (string.indexOf('.') > -1 || string.indexOf('e') > -1
|| string.indexOf('E') > -1 || "-0".equals(string)) {
Double d = Double.valueOf(string);
if (!d.isInfinite() && !d.isNaN()) {
return d;
return stringToNumber(string);
} catch (Exception ignore) {
}
}
return string;
}

/**
* direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
*/
private static Number stringToNumber(final String val) throws NumberFormatException {
char initial = val.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
// decimal representation
if (isDecimalNotation(val)) {
// Use a BigDecimal all the time so we keep the original
// representation. BigDecimal doesn't support -0.0, ensure we
// keep that by forcing a decimal.
try {
BigDecimal bd = new BigDecimal(val);
if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
return Double.valueOf(-0.0);
}
} else {
Long myLong = Long.valueOf(string);
if (string.equals(myLong.toString())) {
if (myLong.longValue() == myLong.intValue()) {
return Integer.valueOf(myLong.intValue());
return bd;
} catch (NumberFormatException retryAsDouble) {
// this is to support "Hex Floats" like this: 0x1.0P-1074
try {
Double d = Double.valueOf(val);
if(d.isNaN() || d.isInfinite()) {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
return myLong;
return d;
} catch (NumberFormatException ignore) {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
}
} catch (Exception ignore) {
}
// block items like 00 01 etc. Java number parsers treat these as Octal.
if(initial == '0' && val.length() > 1) {
char at1 = val.charAt(1);
if(at1 >= '0' && at1 <= '9') {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
} else if (initial == '-' && val.length() > 2) {
char at1 = val.charAt(1);
char at2 = val.charAt(2);
if(at1 == '0' && at2 >= '0' && at2 <= '9') {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
}
// integer representation.
// This will narrow any values to the smallest reasonable Object representation
// (Integer, Long, or BigInteger)

// BigInteger down conversion: We use a similar bitLenth compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is
// long lived.
BigInteger bi = new BigInteger(val);
if(bi.bitLength() <= 31){
return Integer.valueOf(bi.intValue());
}
if(bi.bitLength() <= 63){
return Long.valueOf(bi.longValue());
}
return bi;
}
return string;
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}

/**
* direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
*/
private static boolean isDecimalNotation(final String val) {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}


/**
* Convert a well-formed (but not necessarily valid) XML string into a
Expand Down
Loading

0 comments on commit 19bb6fd

Please sign in to comment.