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 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