blob: 4bd4fb19d405b1c929905e7e52f3062d6e6c1e9f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.lang;
/**
* Used to parse a string and return either a single or double precision
* floating point number.
* @hide
*/
final class StringToReal {
private static final class StringExponentPair {
String s;
long e;
boolean negative;
// Flags for two special non-error failure cases.
boolean infinity;
boolean zero;
public float specialValue() {
if (infinity) {
return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
}
return negative ? -0.0f : 0.0f;
}
}
/**
* Takes a String and an integer exponent. The String should hold a positive
* integer value (or zero). The exponent will be used to calculate the
* floating point number by taking the positive integer the String
* represents and multiplying by 10 raised to the power of the of the
* exponent. Returns the closest double value to the real number, or Double.longBitsToDouble(-1).
*/
private static native double parseDblImpl(String s, int e);
/**
* Takes a String and an integer exponent. The String should hold a positive
* integer value (or zero). The exponent will be used to calculate the
* floating point number by taking the positive integer the String
* represents and multiplying by 10 raised to the power of the of the
* exponent. Returns the closest float value to the real number, or Float.intBitsToFloat(-1).
*/
private static native float parseFltImpl(String s, int e);
private static NumberFormatException invalidReal(String s, boolean isDouble) {
throw new NumberFormatException("Invalid " + (isDouble ? "double" : "float") + ": \"" + s + "\"");
}
/**
* Returns a StringExponentPair containing a String with no leading or trailing white
* space and trailing zeroes eliminated. The exponent of the
* StringExponentPair will be used to calculate the floating point number by
* taking the positive integer the String represents and multiplying by 10
* raised to the power of the of the exponent.
*/
private static StringExponentPair initialParse(String s, int length, boolean isDouble) {
StringExponentPair result = new StringExponentPair();
if (length == 0) {
throw invalidReal(s, isDouble);
}
result.negative = (s.charAt(0) == '-');
// We ignore trailing double or float indicators; the method you called determines
// what you'll get.
char c = s.charAt(length - 1);
if (c == 'D' || c == 'd' || c == 'F' || c == 'f') {
length--;
if (length == 0) {
throw invalidReal(s, isDouble);
}
}
int end = Math.max(s.indexOf('E'), s.indexOf('e'));
if (end != -1) {
// Is there anything after the 'e'?
if (end + 1 == length) {
throw invalidReal(s, isDouble);
}
// Do we have an optional explicit sign?
int exponentOffset = end + 1;
boolean negativeExponent = false;
char firstExponentChar = s.charAt(exponentOffset);
if (firstExponentChar == '+' || firstExponentChar == '-') {
negativeExponent = (firstExponentChar == '-');
++exponentOffset;
}
// Do we have a valid positive integer?
String exponentString = s.substring(exponentOffset, length);
if (exponentString.isEmpty()) {
throw invalidReal(s, isDouble);
}
for (int i = 0; i < exponentString.length(); ++i) {
char ch = exponentString.charAt(i);
if (ch < '0' || ch > '9') {
throw invalidReal(s, isDouble);
}
}
// Parse the integer exponent.
try {
result.e = Integer.parseInt(exponentString);
if (negativeExponent) {
result.e = -result.e;
}
} catch (NumberFormatException ex) {
// We already checked the string, so the exponent must have been out of range for an
// int.
if (negativeExponent) {
result.zero = true;
} else {
result.infinity = true;
}
// Fall through: We want to check the content of the mantissa and throw an
// exception if it contains invalid characters. For example: "JUNK" * 10^12 should
// be treated as an error, not as infinity.
}
} else {
end = length;
}
int start = 0;
c = s.charAt(start);
if (c == '-') {
++start;
--length;
result.negative = true;
} else if (c == '+') {
++start;
--length;
}
if (length == 0) {
throw invalidReal(s, isDouble);
}
// Confirm that the mantissa should parse.
int decimal = -1;
for (int i = start; i < end; i++) {
char mc = s.charAt(i);
if (mc == '.') {
if (decimal != -1) {
throw invalidReal(s, isDouble);
}
decimal = i;
} else if (mc < '0' || mc > '9') {
throw invalidReal(s, isDouble);
}
}
if (decimal > -1) {
result.e -= end - decimal - 1;
s = s.substring(start, decimal) + s.substring(decimal + 1, end);
} else {
s = s.substring(start, end);
}
length = s.length();
if (length == 0) {
throw invalidReal(s, isDouble);
}
// All syntactic checks that might throw an exception are above. If we have established
// one of the non-exception error conditions we can stop here.
if (result.infinity || result.zero) {
return result;
}
end = length;
while (end > 1 && s.charAt(end - 1) == '0') {
--end;
}
start = 0;
while (start < end - 1 && s.charAt(start) == '0') {
start++;
}
if (end != length || start != 0) {
result.e += length - end;
s = s.substring(start, end);
}
// This is a hack for https://issues.apache.org/jira/browse/HARMONY-329
// Trim the length of very small numbers, natives can only handle down
// to E-309
final int APPROX_MIN_MAGNITUDE = -359;
final int MAX_DIGITS = 52;
length = s.length();
if (length > MAX_DIGITS && result.e < APPROX_MIN_MAGNITUDE) {
int d = Math.min(APPROX_MIN_MAGNITUDE - (int) result.e, length - 1);
s = s.substring(0, length - d);
result.e += d;
}
// This is a hack for https://issues.apache.org/jira/browse/HARMONY-6641
// The magic 1024 was determined experimentally; the more plausible -324 and +309 were
// not sufficient to pass both our tests and harmony's tests.
if (result.e < -1024) {
result.zero = true;
return result;
} else if (result.e > 1024) {
result.infinity = true;
return result;
}
result.s = s;
return result;
}
// Parses "+Nan", "NaN", "-Nan", "+Infinity", "Infinity", and "-Infinity", case-insensitively.
private static float parseName(String name, boolean isDouble) {
// Explicit sign?
boolean negative = false;
int i = 0;
int length = name.length();
char firstChar = name.charAt(i);
if (firstChar == '-') {
negative = true;
++i;
--length;
} else if (firstChar == '+') {
++i;
--length;
}
if (length == 8 && name.regionMatches(false, i, "Infinity", 0, 8)) {
return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
}
if (length == 3 && name.regionMatches(false, i, "NaN", 0, 3)) {
return Float.NaN;
}
throw invalidReal(name, isDouble);
}
/**
* Returns the closest double value to the real number in the string.
*
* @param s
* the String that will be parsed to a floating point
* @return the double closest to the real number
*
* @throws NumberFormatException
* if the String doesn't represent a double
*/
public static double parseDouble(String s) {
s = s.trim();
int length = s.length();
if (length == 0) {
throw invalidReal(s, true);
}
// See if this could be a named double
char last = s.charAt(length - 1);
if (last == 'y' || last == 'N') {
return parseName(s, true);
}
// See if it could be a hexadecimal representation.
// We don't use startsWith because there might be a leading sign.
if (s.indexOf("0x") != -1 || s.indexOf("0X") != -1) {
return HexStringParser.parseDouble(s);
}
StringExponentPair info = initialParse(s, length, true);
if (info.infinity || info.zero) {
return info.specialValue();
}
double result = parseDblImpl(info.s, (int) info.e);
if (Double.doubleToRawLongBits(result) == 0xffffffffffffffffL) {
throw invalidReal(s, true);
}
return info.negative ? -result : result;
}
/**
* Returns the closest float value to the real number in the string.
*
* @param s
* the String that will be parsed to a floating point
* @return the float closest to the real number
*
* @throws NumberFormatException
* if the String doesn't represent a float
*/
public static float parseFloat(String s) {
s = s.trim();
int length = s.length();
if (length == 0) {
throw invalidReal(s, false);
}
// See if this could be a named float
char last = s.charAt(length - 1);
if (last == 'y' || last == 'N') {
return parseName(s, false);
}
// See if it could be a hexadecimal representation
// We don't use startsWith because there might be a leading sign.
if (s.indexOf("0x") != -1 || s.indexOf("0X") != -1) {
return HexStringParser.parseFloat(s);
}
StringExponentPair info = initialParse(s, length, false);
if (info.infinity || info.zero) {
return info.specialValue();
}
float result = parseFltImpl(info.s, (int) info.e);
if (Float.floatToRawIntBits(result) == 0xffffffff) {
throw invalidReal(s, false);
}
return info.negative ? -result : result;
}
}