Skip to content

Commit 849b392

Browse files
author
John J. Aylward
committed
updates the getNumber/optNumber to not return invalid Doubles
1 parent a7f8ff2 commit 849b392

2 files changed

Lines changed: 54 additions & 30 deletions

File tree

‎JSONArray.java‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,11 @@ public BigInteger optBigInteger(int index, BigInteger defaultValue) {
738738
return BigInteger.valueOf(((Number) val).longValue());
739739
}
740740
try {
741-
return new BigDecimal(val.toString()).toBigInteger();
741+
final String valStr = val.toString();
742+
if(JSONObject.isDecimalNotation(valStr)) {
743+
return new BigDecimal(valStr).toBigInteger();
744+
}
745+
return new BigInteger(valStr);
742746
} catch (Exception e) {
743747
return defaultValue;
744748
}

‎JSONObject.java‎

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,11 @@ public BigInteger optBigInteger(String key, BigInteger defaultValue) {
10681068
// jo.optInt("double"); -- will return 1, not an error
10691069
// this conversion to BigDecimal then to BigInteger is to maintain
10701070
// that type cast support that may truncate the decimal.
1071-
return new BigDecimal(val.toString()).toBigInteger();
1071+
final String valStr = val.toString();
1072+
if(isDecimalNotation(valStr)) {
1073+
return new BigDecimal(valStr).toBigInteger();
1074+
}
1075+
return new BigInteger(valStr);
10721076
} catch (Exception e) {
10731077
return defaultValue;
10741078
}
@@ -1759,12 +1763,22 @@ public boolean similar(Object other) {
17591763
return false;
17601764
}
17611765
}
1762-
1766+
1767+
/**
1768+
* Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
1769+
*
1770+
* @param val value to test
1771+
* @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
1772+
*/
1773+
protected static boolean isDecimalNotation(final String val) {
1774+
return val.indexOf('.') > -1 || val.indexOf('e') > -1
1775+
|| val.indexOf('E') > -1 || "-0".equals(val);
1776+
}
1777+
17631778
/**
17641779
* Converts a string to a number using the narrowest possible type. Possible
17651780
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
1766-
*
1767-
* An Exception is thrown if
1781+
* When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
17681782
*
17691783
* @param val value to convert
17701784
* @return Number representation of the value.
@@ -1775,46 +1789,52 @@ protected static Number stringToNumber(final String val) throws NumberFormatExce
17751789
char initial = val.charAt(0);
17761790
if ((initial >= '0' && initial <= '9') || initial == '-') {
17771791
// decimal representation
1778-
if (val.indexOf('.') > -1 || val.indexOf('e') > -1
1779-
|| val.indexOf('E') > -1
1780-
|| "-0".equals(val)) {
1792+
if (isDecimalNotation(val)) {
17811793
// quick dirty way to see if we need a BigDecimal instead of a Double
1794+
// this only handles some cases of overflow or underflow
17821795
if (val.length()>14) {
17831796
return new BigDecimal(val);
17841797
}
1785-
return Double.valueOf(val);
1798+
final Double d = Double.valueOf(val);
1799+
if (d.isInfinite() || d.isNaN()) {
1800+
// if we can't parse it as a double, go up to BigDecimal
1801+
// this is probably due to underflow like 4.32e-678
1802+
// or overflow like 4.65e5324. The size of the string is small
1803+
// but can't be held in a Double.
1804+
return new BigDecimal(val);
1805+
}
1806+
return d;
17861807
}
17871808
// integer representation.
17881809
// This will narrow any values to the smallest reasonable Object representation
17891810
// (Integer, Long, or BigInteger)
1811+
1812+
// string version
17901813
// The compare string length method reduces GC,
17911814
// but leads to smaller integers being placed in larger wrappers even though not
17921815
// needed. i.e. 1,000,000,000 -> Long even though it's an Integer
17931816
// 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
1794-
1795-
// string version
1796-
if(val.length()<=9){
1797-
return Integer.valueOf(val);
1798-
}
1799-
if(val.length()<=18){
1800-
return Long.valueOf(val);
1801-
}
1802-
return new BigInteger(val);
1817+
//if(val.length()<=9){
1818+
// return Integer.valueOf(val);
1819+
//}
1820+
//if(val.length()<=18){
1821+
// return Long.valueOf(val);
1822+
//}
1823+
//return new BigInteger(val);
18031824

18041825
// BigInteger version: We use a similar bitLenth compare as
18051826
// BigInteger#intValueExact uses. Increases GC, but objects hold
18061827
// only what they need. i.e. Less runtime overhead if the value is
18071828
// long lived. Which is the better tradeoff? This is closer to what's
18081829
// in stringToValue.
1809-
1810-
//BigInteger bi = new BigInteger((String)val);
1811-
//if(bi.bitLength()<=31){
1812-
// return Integer.valueOf(bi.intValue());
1813-
//}
1814-
//if(bi.bitLength()<=63){
1815-
// return Long.valueOf(bi.longValue());
1816-
//}
1817-
//return bi;
1830+
BigInteger bi = new BigInteger(val);
1831+
if(bi.bitLength()<=31){
1832+
return Integer.valueOf(bi.intValue());
1833+
}
1834+
if(bi.bitLength()<=63){
1835+
return Long.valueOf(bi.longValue());
1836+
}
1837+
return bi;
18181838
}
18191839
throw new NumberFormatException("val ["+val+"] is not a valid number.");
18201840
}
@@ -1849,9 +1869,9 @@ public static Object stringToValue(String string) {
18491869
char initial = string.charAt(0);
18501870
if ((initial >= '0' && initial <= '9') || initial == '-') {
18511871
try {
1852-
if (string.indexOf('.') > -1 || string.indexOf('e') > -1
1853-
|| string.indexOf('E') > -1
1854-
|| "-0".equals(string)) {
1872+
// if we want full Big Number support this block can be replaced with:
1873+
// return stringToNumber(string);
1874+
if (isDecimalNotation(string)) {
18551875
Double d = Double.valueOf(string);
18561876
if (!d.isInfinite() && !d.isNaN()) {
18571877
return d;

0 commit comments

Comments
 (0)