Home | History | Annotate | Download | only in lang
      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 
     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
    126                 // int.
    127                 if (negativeExponent) {
    128                     result.zero = true;
    129                 } else {
    130                     result.infinity = true;
    131                 }
    132                 // Fall through: We want to check the content of the mantissa and throw an
    133                 // exception if it contains invalid characters. For example: "JUNK" * 10^12 should
    134                 // be treated as an error, not as infinity.
    135             }
    136         } else {
    137             end = length;
    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         // Confirm that the mantissa should parse.
    155         int decimal = -1;
    156         for (int i = start; i < end; i++) {
    157             char mc = s.charAt(i);
    158             if (mc == '.') {
    159                 if (decimal != -1) {
    160                     throw invalidReal(s, isDouble);
    161                 }
    162                 decimal = i;
    163             } else if (mc < '0' || mc > '9') {
    164                 throw invalidReal(s, isDouble);
    165             }
    166         }
    167         if (decimal > -1) {
    168             result.e -= end - decimal - 1;
    169             s = s.substring(start, decimal) + s.substring(decimal + 1, end);
    170         } else {
    171             s = s.substring(start, end);
    172         }
    173 
    174         length = s.length();
    175         if (length == 0) {
    176             throw invalidReal(s, isDouble);
    177         }
    178 
    179         // All syntactic checks that might throw an exception are above. If we have established
    180         // one of the non-exception error conditions we can stop here.
    181         if (result.infinity || result.zero) {
    182             return result;
    183         }
    184 
    185         end = length;
    186         while (end > 1 && s.charAt(end - 1) == '0') {
    187             --end;
    188         }
    189 
    190         start = 0;
    191         while (start < end - 1 && s.charAt(start) == '0') {
    192             start++;
    193         }
    194 
    195         if (end != length || start != 0) {
    196             result.e += length - end;
    197             s = s.substring(start, end);
    198         }
    199 
    200         // This is a hack for https://issues.apache.org/jira/browse/HARMONY-329
    201         // Trim the length of very small numbers, natives can only handle down
    202         // to E-309
    203         final int APPROX_MIN_MAGNITUDE = -359;
    204         final int MAX_DIGITS = 52;
    205         length = s.length();
    206         if (length > MAX_DIGITS && result.e < APPROX_MIN_MAGNITUDE) {
    207             int d = Math.min(APPROX_MIN_MAGNITUDE - (int) result.e, length - 1);
    208             s = s.substring(0, length - d);
    209             result.e += d;
    210         }
    211 
    212         // This is a hack for https://issues.apache.org/jira/browse/HARMONY-6641
    213         // The magic 1024 was determined experimentally; the more plausible -324 and +309 were
    214         // not sufficient to pass both our tests and harmony's tests.
    215         if (result.e < -1024) {
    216             result.zero = true;
    217             return result;
    218         } else if (result.e > 1024) {
    219             result.infinity = true;
    220             return result;
    221         }
    222 
    223         result.s = s;
    224         return result;
    225     }
    226 
    227     // Parses "+Nan", "NaN", "-Nan", "+Infinity", "Infinity", and "-Infinity", case-insensitively.
    228     private static float parseName(String name, boolean isDouble) {
    229         // Explicit sign?
    230         boolean negative = false;
    231         int i = 0;
    232         int length = name.length();
    233         char firstChar = name.charAt(i);
    234         if (firstChar == '-') {
    235             negative = true;
    236             ++i;
    237             --length;
    238         } else if (firstChar == '+') {
    239             ++i;
    240             --length;
    241         }
    242 
    243         if (length == 8 && name.regionMatches(false, i, "Infinity", 0, 8)) {
    244             return negative ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
    245         }
    246         if (length == 3 && name.regionMatches(false, i, "NaN", 0, 3)) {
    247             return Float.NaN;
    248         }
    249         throw invalidReal(name, isDouble);
    250     }
    251 
    252     /**
    253      * Returns the closest double value to the real number in the string.
    254      *
    255      * @param s
    256      *            the String that will be parsed to a floating point
    257      * @return the double closest to the real number
    258      *
    259      * @throws NumberFormatException
    260      *                if the String doesn't represent a double
    261      */
    262     public static double parseDouble(String s) {
    263         s = s.trim();
    264         int length = s.length();
    265 
    266         if (length == 0) {
    267             throw invalidReal(s, true);
    268         }
    269 
    270         // See if this could be a named double
    271         char last = s.charAt(length - 1);
    272         if (last == 'y' || last == 'N') {
    273             return parseName(s, true);
    274         }
    275 
    276         // See if it could be a hexadecimal representation.
    277         // We don't use startsWith because there might be a leading sign.
    278         if (s.indexOf("0x") != -1 || s.indexOf("0X") != -1) {
    279             return HexStringParser.parseDouble(s);
    280         }
    281 
    282         StringExponentPair info = initialParse(s, length, true);
    283         if (info.infinity || info.zero) {
    284             return info.specialValue();
    285         }
    286         double result = parseDblImpl(info.s, (int) info.e);
    287         if (Double.doubleToRawLongBits(result) == 0xffffffffffffffffL) {
    288             throw invalidReal(s, true);
    289         }
    290         return info.negative ? -result : result;
    291     }
    292 
    293     /**
    294      * Returns the closest float value to the real number in the string.
    295      *
    296      * @param s
    297      *            the String that will be parsed to a floating point
    298      * @return the float closest to the real number
    299      *
    300      * @throws NumberFormatException
    301      *                if the String doesn't represent a float
    302      */
    303     public static float parseFloat(String s) {
    304         s = s.trim();
    305         int length = s.length();
    306 
    307         if (length == 0) {
    308             throw invalidReal(s, false);
    309         }
    310 
    311         // See if this could be a named float
    312         char last = s.charAt(length - 1);
    313         if (last == 'y' || last == 'N') {
    314             return parseName(s, false);
    315         }
    316 
    317         // See if it could be a hexadecimal representation
    318         // We don't use startsWith because there might be a leading sign.
    319         if (s.indexOf("0x") != -1 || s.indexOf("0X") != -1) {
    320             return HexStringParser.parseFloat(s);
    321         }
    322 
    323         StringExponentPair info = initialParse(s, length, false);
    324         if (info.infinity || info.zero) {
    325             return info.specialValue();
    326         }
    327         float result = parseFltImpl(info.s, (int) info.e);
    328         if (Float.floatToRawIntBits(result) == 0xffffffff) {
    329             throw invalidReal(s, false);
    330         }
    331         return info.negative ? -result : result;
    332     }
    333 }
    334