/*
* 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 org.apache.harmony.luni.util;
/**
* Used to parse a string and return either a single or double precision
* floating point number.
*/
public final class FloatingPointParser {
private static final class StringExponentPair {
String s;
int e;
boolean negative;
StringExponentPair(String s, int e, boolean negative) {
this.s = s;
this.e = e;
this.negative = negative;
}
}
/**
* 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
* exponent. Returns the closest double value to the real number
*
* @param s
* the String that will be parsed to a floating point
* @param e
* an int represent the 10 to part
* @return the double closest to the real number
*
* @exception NumberFormatException
* if the String doesn't represent a positive integer value
*/
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
* exponent. Returns the closest float value to the real number
*
* @param s
* the String that will be parsed to a floating point
* @param e
* an int represent the 10 to part
* @return the float closest to the real number
*
* @exception NumberFormatException
* if the String doesn't represent a positive integer value
*/
private static native float parseFltImpl(String s, int e);
/**
* Takes a String and does some initial parsing. Should return 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 exponent.
*
* @param s
* the String that will be parsed to a floating point
* @param length
* the length of s
* @return a StringExponentPair with necessary values
*
* @exception NumberFormatException
* if the String doesn't pass basic tests
*/
private static StringExponentPair initialParse(String s, int length) {
boolean negative = false;
char c;
int start, end, decimal;
int e = 0;
start = 0;
if (length == 0)
throw new NumberFormatException(s);
c = s.charAt(length - 1);
if (c == 'D' || c == 'd' || c == 'F' || c == 'f') {
length--;
if (length == 0)
throw new NumberFormatException(s);
}
end = Math.max(s.indexOf('E'), s.indexOf('e'));
if (end > -1) {
if (end + 1 == length)
throw new NumberFormatException(s);
int exponent_offset = end + 1;
if (s.charAt(exponent_offset) == '+') {
if (s.charAt(exponent_offset + 1) == '-') {
throw new NumberFormatException(s);
}
exponent_offset++; // skip the plus sign
}
try {
e = Integer.parseInt(s.substring(exponent_offset,
length));
} catch (NumberFormatException ex) {
// ex contains the exponent substring
// only so throw a new exception with
// the correct string
throw new NumberFormatException(s);
}
} else {
end = length;
}
if (length == 0)
throw new NumberFormatException(s);
c = s.charAt(start);
if (c == '-') {
++start;
--length;
negative = true;
} else if (c == '+') {
++start;
--length;
}
if (length == 0)
throw new NumberFormatException(s);
decimal = s.indexOf('.');
if (decimal > -1) {
e -= end - decimal - 1;
s = s.substring(start, decimal) + s.substring(decimal + 1, end);
} else {
s = s.substring(start, end);
}
if ((length = s.length()) == 0)
throw new NumberFormatException();
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) {
e += length - end;
s = s.substring(start, end);
}
// 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 && e < APPROX_MIN_MAGNITUDE) {
int d = Math.min(APPROX_MIN_MAGNITUDE - e, length - 1);
s = s.substring(0, length - d);
e += d;
}
return new StringExponentPair(s, e, negative);
}
/*
* Assumes the string is trimmed.
*/
private static double parseDblName(String namedDouble, int length) {
// Valid strings are only +Nan, NaN, -Nan, +Infinity, Infinity,
// -Infinity.
if ((length != 3) && (length != 4) && (length != 8) && (length != 9)) {
throw new NumberFormatException();
}
boolean negative = false;
int cmpstart = 0;
switch (namedDouble.charAt(0)) {
case '-':
negative = true; // fall through
case '+':
cmpstart = 1;
default:
}
if (namedDouble.regionMatches(false, cmpstart, "Infinity", 0, 8)) {
return negative ? Double.NEGATIVE_INFINITY
: Float.POSITIVE_INFINITY;
}
if (namedDouble.regionMatches(false, cmpstart, "NaN", 0, 3)) {
return Double.NaN;
}
throw new NumberFormatException();
}
/*
* Assumes the string is trimmed.
*/
private static float parseFltName(String namedFloat, int length) {
// Valid strings are only +Nan, NaN, -Nan, +Infinity, Infinity,
// -Infinity.
if ((length != 3) && (length != 4) && (length != 8) && (length != 9)) {
throw new NumberFormatException();
}
boolean negative = false;
int cmpstart = 0;
switch (namedFloat.charAt(0)) {
case '-':
negative = true; // fall through
case '+':
cmpstart = 1;
default:
}
if (namedFloat.regionMatches(false, cmpstart, "Infinity", 0, 8)) {
return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
}
if (namedFloat.regionMatches(false, cmpstart, "NaN", 0, 3)) {
return Float.NaN;
}
throw new NumberFormatException();
}
/**
* 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
*
* @exception 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 new NumberFormatException(s);
}
// See if this could be a named double
char last = s.charAt(length - 1);
if ((last == 'y') || (last == 'N')) {
return parseDblName(s, length);
}
// See if it could be a hexadecimal representation
if (s.toLowerCase().indexOf("0x") != -1) { //$NON-NLS-1$
return HexStringParser.parseDouble(s);
}
StringExponentPair info = initialParse(s, length);
double result = parseDblImpl(info.s, info.e);
if (info.negative)
result = -result;
return 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
*
* @exception 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 new NumberFormatException(s);
}
// See if this could be a named float
char last = s.charAt(length - 1);
if ((last == 'y') || (last == 'N')) {
return parseFltName(s, length);
}
// See if it could be a hexadecimal representation
if (s.toLowerCase().indexOf("0x") != -1) { //$NON-NLS-1$
return HexStringParser.parseFloat(s);
}
StringExponentPair info = initialParse(s, length);
float result = parseFltImpl(info.s, info.e);
if (info.negative)
result = -result;
return result;
}
}