mirror of
https://github.com/stleary/JSON-java.git
synced 2025-08-03 03:15:32 -04:00
153 lines
5.9 KiB
Java
153 lines
5.9 KiB
Java
package org.json;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
|
|
class NumberConversionUtil {
|
|
|
|
/**
|
|
* Converts a string to a number using the narrowest possible type. Possible
|
|
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
|
|
* When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
|
|
*
|
|
* @param input value to convert
|
|
* @return Number representation of the value.
|
|
* @throws NumberFormatException thrown if the value is not a valid number. A public
|
|
* caller should catch this and wrap it in a {@link JSONException} if applicable.
|
|
*/
|
|
static Number stringToNumber(final String input) throws NumberFormatException {
|
|
String val = input;
|
|
if (val.startsWith(".")){
|
|
val = "0"+val;
|
|
}
|
|
if (val.startsWith("-.")){
|
|
val = "-0."+val.substring(2);
|
|
}
|
|
char initial = val.charAt(0);
|
|
if ( isNumericChar(initial) || 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);
|
|
}
|
|
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 ["+input+"] is not a valid number.");
|
|
}
|
|
return d;
|
|
} catch (NumberFormatException ignore) {
|
|
throw new NumberFormatException("val ["+input+"] is not a valid number.");
|
|
}
|
|
}
|
|
}
|
|
val = removeLeadingZerosOfNumber(input);
|
|
initial = val.charAt(0);
|
|
if(initial == '0' && val.length() > 1) {
|
|
char at1 = val.charAt(1);
|
|
if(isNumericChar(at1)) {
|
|
throw new NumberFormatException("val ["+input+"] is not a valid number.");
|
|
}
|
|
} else if (initial == '-' && val.length() > 2) {
|
|
char at1 = val.charAt(1);
|
|
char at2 = val.charAt(2);
|
|
if(at1 == '0' && isNumericChar(at2)) {
|
|
throw new NumberFormatException("val ["+input+"] 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 bitLength 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;
|
|
}
|
|
throw new NumberFormatException("val ["+input+"] is not a valid number.");
|
|
}
|
|
|
|
/**
|
|
* Checks if the character is a numeric digit ('0' to '9').
|
|
*
|
|
* @param c The character to be checked.
|
|
* @return true if the character is a numeric digit, false otherwise.
|
|
*/
|
|
private static boolean isNumericChar(char c) {
|
|
return (c <= '9' && c >= '0');
|
|
}
|
|
|
|
/**
|
|
* Checks if the value could be considered a number in decimal number system.
|
|
* @param value
|
|
* @return
|
|
*/
|
|
static boolean potentialNumber(String value){
|
|
if (value == null || value.isEmpty()){
|
|
return false;
|
|
}
|
|
return potentialPositiveNumberStartingAtIndex(value, (value.charAt(0)=='-'?1:0));
|
|
}
|
|
|
|
/**
|
|
* Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
|
|
*
|
|
* @param val value to test
|
|
* @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
|
|
*/
|
|
private static boolean isDecimalNotation(final String val) {
|
|
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|
|
|| val.indexOf('E') > -1 || "-0".equals(val);
|
|
}
|
|
|
|
private static boolean potentialPositiveNumberStartingAtIndex(String value,int index){
|
|
if (index >= value.length()){
|
|
return false;
|
|
}
|
|
return digitAtIndex(value, (value.charAt(index)=='.'?index+1:index));
|
|
}
|
|
|
|
private static boolean digitAtIndex(String value, int index){
|
|
if (index >= value.length()){
|
|
return false;
|
|
}
|
|
return value.charAt(index) >= '0' && value.charAt(index) <= '9';
|
|
}
|
|
|
|
/**
|
|
* For a prospective number, remove the leading zeros
|
|
* @param value prospective number
|
|
* @return number without leading zeros
|
|
*/
|
|
private static String removeLeadingZerosOfNumber(String value){
|
|
if (value.equals("-")){return value;}
|
|
boolean negativeFirstChar = (value.charAt(0) == '-');
|
|
int counter = negativeFirstChar ? 1:0;
|
|
while (counter < value.length()){
|
|
if (value.charAt(counter) != '0'){
|
|
if (negativeFirstChar) {return "-".concat(value.substring(counter));}
|
|
return value.substring(counter);
|
|
}
|
|
++counter;
|
|
}
|
|
if (negativeFirstChar) {return "-0";}
|
|
return "0";
|
|
}
|
|
}
|