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
18package 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 */
25final 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
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     */
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     */
60    private static native float parseFltImpl(String s, int e);
61
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     */
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.
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     */
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     */
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