• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 package java.lang;
19 
20 /**
21  * Used to parse a string and return either a single or double precision
22  * floating point number.
23  * @hide
24  */
25 final class StringToReal {
26 
27     private static final class StringExponentPair {
28         String s;
29         long e;
30         boolean negative;
31 
32         // Flags for two special non-error failure cases.
33         boolean infinity;
34         boolean zero;
35 
specialValue()36         public float specialValue() {
37             if (infinity) {
38                 return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
39             }
40             return negative ? -0.0f : 0.0f;
41         }
42     }
43 
44     /**
45      * Takes a String and an integer exponent. The String should hold a positive
46      * integer value (or zero). The exponent will be used to calculate the
47      * floating point number by taking the positive integer the String
48      * represents and multiplying by 10 raised to the power of the of the
49      * exponent. Returns the closest double value to the real number, or Double.longBitsToDouble(-1).
50      */
parseDblImpl(String s, int e)51     private static native double parseDblImpl(String s, int e);
52 
53     /**
54      * Takes a String and an integer exponent. The String should hold a positive
55      * integer value (or zero). The exponent will be used to calculate the
56      * floating point number by taking the positive integer the String
57      * represents and multiplying by 10 raised to the power of the of the
58      * exponent. Returns the closest float value to the real number, or Float.intBitsToFloat(-1).
59      */
parseFltImpl(String s, int e)60     private static native float parseFltImpl(String s, int e);
61 
invalidReal(String s, boolean isDouble)62     private static NumberFormatException invalidReal(String s, boolean isDouble) {
63         throw new NumberFormatException("Invalid " + (isDouble ? "double" : "float") + ": \"" + s + "\"");
64     }
65 
66     /**
67      * Returns a StringExponentPair containing a String with no leading or trailing white
68      * space and trailing zeroes eliminated. The exponent of the
69      * StringExponentPair will be used to calculate the floating point number by
70      * taking the positive integer the String represents and multiplying by 10
71      * raised to the power of the of the exponent.
72      */
initialParse(String s, int length, boolean isDouble)73     private static StringExponentPair initialParse(String s, int length, boolean isDouble) {
74         StringExponentPair result = new StringExponentPair();
75         if (length == 0) {
76             throw invalidReal(s, isDouble);
77         }
78         result.negative = (s.charAt(0) == '-');
79 
80         // We ignore trailing double or float indicators; the method you called determines
81         // what you'll get.
82         char c = s.charAt(length - 1);
83         if (c == 'D' || c == 'd' || c == 'F' || c == 'f') {
84             length--;
85             if (length == 0) {
86                 throw invalidReal(s, isDouble);
87             }
88         }
89 
90         int end = Math.max(s.indexOf('E'), s.indexOf('e'));
91         if (end != -1) {
92             // Is there anything after the 'e'?
93             if (end + 1 == length) {
94                 throw invalidReal(s, isDouble);
95             }
96 
97             // Do we have an optional explicit sign?
98             int exponentOffset = end + 1;
99             boolean negativeExponent = false;
100             char firstExponentChar = s.charAt(exponentOffset);
101             if (firstExponentChar == '+' || firstExponentChar == '-') {
102                 negativeExponent = (firstExponentChar == '-');
103                 ++exponentOffset;
104             }
105 
106             // Do we have a valid positive integer?
107             String exponentString = s.substring(exponentOffset, length);
108             if (exponentString.isEmpty()) {
109                 throw invalidReal(s, isDouble);
110             }
111             for (int i = 0; i < exponentString.length(); ++i) {
112                 char ch = exponentString.charAt(i);
113                 if (ch < '0' || ch > '9') {
114                     throw invalidReal(s, isDouble);
115                 }
116             }
117 
118             // Parse the integer exponent.
119             try {
120                 result.e = Integer.parseInt(exponentString);
121                 if (negativeExponent) {
122                     result.e = -result.e;
123                 }
124             } catch (NumberFormatException ex) {
125                 // We already checked the string, so the exponent must have been out of range for an int.
126                 if (negativeExponent) {
127                     result.zero = true;
128                 } else {
129                     result.infinity = true;
130                 }
131                 return result;
132             }
133         } else {
134             end = length;
135         }
136         if (length == 0) {
137             throw invalidReal(s, isDouble);
138         }
139 
140         int start = 0;
141         c = s.charAt(start);
142         if (c == '-') {
143             ++start;
144             --length;
145             result.negative = true;
146         } else if (c == '+') {
147             ++start;
148             --length;
149         }
150         if (length == 0) {
151             throw invalidReal(s, isDouble);
152         }
153 
154         int decimal = s.indexOf('.');
155         if (decimal > -1) {
156             result.e -= end - decimal - 1;
157             s = s.substring(start, decimal) + s.substring(decimal + 1, end);
158         } else {
159             s = s.substring(start, end);
160         }
161 
162         if ((length = s.length()) == 0) {
163             throw invalidReal(s, isDouble);
164         }
165 
166         end = length;
167         while (end > 1 && s.charAt(end - 1) == '0') {
168             --end;
169         }
170 
171         start = 0;
172         while (start < end - 1 && s.charAt(start) == '0') {
173             start++;
174         }
175 
176         if (end != length || start != 0) {
177             result.e += length - end;
178             s = s.substring(start, end);
179         }
180 
181         // This is a hack for https://issues.apache.org/jira/browse/HARMONY-329
182         // Trim the length of very small numbers, natives can only handle down
183         // to E-309
184         final int APPROX_MIN_MAGNITUDE = -359;
185         final int MAX_DIGITS = 52;
186         length = s.length();
187         if (length > MAX_DIGITS && result.e < APPROX_MIN_MAGNITUDE) {
188             int d = Math.min(APPROX_MIN_MAGNITUDE - (int) result.e, length - 1);
189             s = s.substring(0, length - d);
190             result.e += d;
191         }
192 
193         // This is a hack for https://issues.apache.org/jira/browse/HARMONY-6641
194         // The magic 1024 was determined experimentally; the more plausible -324 and +309 were
195         // not sufficient to pass both our tests and harmony's tests.
196         if (result.e < -1024) {
197             result.zero = true;
198             return result;
199         } else if (result.e > 1024) {
200             result.infinity = true;
201             return result;
202         }
203 
204         result.s = s;
205         return result;
206     }
207 
208     // Parses "+Nan", "NaN", "-Nan", "+Infinity", "Infinity", and "-Infinity", case-insensitively.
parseName(String name, boolean isDouble)209     private static float parseName(String name, boolean isDouble) {
210         // Explicit sign?
211         boolean negative = false;
212         int i = 0;
213         int length = name.length();
214         char firstChar = name.charAt(i);
215         if (firstChar == '-') {
216             negative = true;
217             ++i;
218             --length;
219         } else if (firstChar == '+') {
220             ++i;
221             --length;
222         }
223 
224         if (length == 8 && name.regionMatches(false, i, "Infinity", 0, 8)) {
225             return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
226         }
227         if (length == 3 && name.regionMatches(false, i, "NaN", 0, 3)) {
228             return Float.NaN;
229         }
230         throw invalidReal(name, isDouble);
231     }
232 
233     /**
234      * Returns the closest double value to the real number in the string.
235      *
236      * @param s
237      *            the String that will be parsed to a floating point
238      * @return the double closest to the real number
239      *
240      * @exception NumberFormatException
241      *                if the String doesn't represent a double
242      */
parseDouble(String s)243     public static double parseDouble(String s) {
244         s = s.trim();
245         int length = s.length();
246 
247         if (length == 0) {
248             throw invalidReal(s, true);
249         }
250 
251         // See if this could be a named double
252         char last = s.charAt(length - 1);
253         if (last == 'y' || last == 'N') {
254             return parseName(s, true);
255         }
256 
257         // See if it could be a hexadecimal representation.
258         // We don't use startsWith because there might be a leading sign.
259         if (s.indexOf("0x") != -1 || s.indexOf("0X") != -1) {
260             return HexStringParser.parseDouble(s);
261         }
262 
263         StringExponentPair info = initialParse(s, length, true);
264         if (info.infinity || info.zero) {
265             return info.specialValue();
266         }
267         double result = parseDblImpl(info.s, (int) info.e);
268         if (Double.doubleToRawLongBits(result) == 0xffffffffffffffffL) {
269             throw invalidReal(s, true);
270         }
271         return info.negative ? -result : result;
272     }
273 
274     /**
275      * Returns the closest float value to the real number in the string.
276      *
277      * @param s
278      *            the String that will be parsed to a floating point
279      * @return the float closest to the real number
280      *
281      * @exception NumberFormatException
282      *                if the String doesn't represent a float
283      */
parseFloat(String s)284     public static float parseFloat(String s) {
285         s = s.trim();
286         int length = s.length();
287 
288         if (length == 0) {
289             throw invalidReal(s, false);
290         }
291 
292         // See if this could be a named float
293         char last = s.charAt(length - 1);
294         if (last == 'y' || last == 'N') {
295             return parseName(s, false);
296         }
297 
298         // See if it could be a hexadecimal representation
299         // We don't use startsWith because there might be a leading sign.
300         if (s.indexOf("0x") != -1 || s.indexOf("0X") != -1) {
301             return HexStringParser.parseFloat(s);
302         }
303 
304         StringExponentPair info = initialParse(s, length, false);
305         if (info.infinity || info.zero) {
306             return info.specialValue();
307         }
308         float result = parseFltImpl(info.s, (int) info.e);
309         if (Float.floatToRawIntBits(result) == 0xffffffff) {
310             throw invalidReal(s, false);
311         }
312         return info.negative ? -result : result;
313     }
314 }
315