1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.objects; 27 28 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 30 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 31 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsLong; 32 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 33 import static jdk.nashorn.internal.lookup.Lookup.MH; 34 35 import java.lang.invoke.MethodHandle; 36 import java.lang.invoke.MethodHandles; 37 import java.text.NumberFormat; 38 import java.util.Locale; 39 import jdk.internal.dynalink.linker.GuardedInvocation; 40 import jdk.internal.dynalink.linker.LinkRequest; 41 import jdk.nashorn.internal.objects.annotations.Attribute; 42 import jdk.nashorn.internal.objects.annotations.Constructor; 43 import jdk.nashorn.internal.objects.annotations.Function; 44 import jdk.nashorn.internal.objects.annotations.Property; 45 import jdk.nashorn.internal.objects.annotations.ScriptClass; 46 import jdk.nashorn.internal.objects.annotations.Where; 47 import jdk.nashorn.internal.runtime.JSType; 48 import jdk.nashorn.internal.runtime.PropertyMap; 49 import jdk.nashorn.internal.runtime.ScriptObject; 50 import jdk.nashorn.internal.runtime.ScriptRuntime; 51 import jdk.nashorn.internal.runtime.linker.PrimitiveLookup; 52 53 /** 54 * ECMA 15.7 Number Objects. 55 * 56 */ 57 @ScriptClass("Number") 58 public final class NativeNumber extends ScriptObject { 59 60 static final MethodHandle WRAPFILTER = findWrapFilter(); 61 62 /** ECMA 15.7.3.2 largest positive finite value */ 63 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 64 public static final double MAX_VALUE = Double.MAX_VALUE; 65 66 /** ECMA 15.7.3.3 smallest positive finite value */ 67 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 68 public static final double MIN_VALUE = Double.MIN_VALUE; 69 70 /** ECMA 15.7.3.4 NaN */ 71 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 72 public static final double NaN = Double.NaN; 73 74 /** ECMA 15.7.3.5 negative infinity */ 75 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 76 public static final double NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY; 77 78 /** ECMA 15.7.3.5 positive infinity */ 79 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 80 public static final double POSITIVE_INFINITY = Double.POSITIVE_INFINITY; 81 82 private final double value; 83 private final boolean isInt; 84 private final boolean isLong; 85 86 // initialized by nasgen 87 private static PropertyMap $nasgenmap$; 88 89 static PropertyMap getInitialMap() { 90 return $nasgenmap$; 91 } 92 93 private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) { 94 super(proto, map); 95 this.value = value; 96 this.isInt = isRepresentableAsInt(value); 97 this.isLong = isRepresentableAsLong(value); 98 } 99 100 NativeNumber(final double value, final Global global) { 101 this(value, global.getNumberPrototype(), global.getNumberMap()); 102 } 103 104 private NativeNumber(final double value) { 105 this(value, Global.instance()); 106 } 107 108 109 @Override 110 public String safeToString() { 111 return "[Number " + toString() + "]"; 112 } 113 114 @Override 115 public String toString() { 116 return Double.toString(getValue()); 117 } 118 119 /** 120 * Get the value of this Number 121 * @return a {@code double} representing the Number value 122 */ 123 public double getValue() { 124 return doubleValue(); 125 } 126 127 /** 128 * Get the value of this Number 129 * @return a {@code double} representing the Number value 130 */ 131 public double doubleValue() { 132 return value; 133 } 134 135 /** 136 * Get the value of this Number as a {@code int} 137 * @return an {@code int} representing the Number value 138 * @throws ClassCastException If number is not representable as an {@code int} 139 */ 140 public int intValue() throws ClassCastException { 141 if (isInt) { 142 return (int)value; 143 } 144 throw new ClassCastException(); 145 } 146 147 /** 148 * Get the value of this Number as a {@code long} 149 * @return a {@code long} representing the Number value 150 * @throws ClassCastException If number is not representable as an {@code long} 151 */ 152 public long longValue() throws ClassCastException { 153 if (isLong) { 154 return (long)value; 155 } 156 throw new ClassCastException(); 157 } 158 159 @Override 160 public String getClassName() { 161 return "Number"; 162 } 163 164 /** 165 * ECMA 15.7.2 - The Number constructor 166 * 167 * @param newObj is this Number instantiated with the new operator 168 * @param self self reference 169 * @param args value of number 170 * @return the Number instance (internally represented as a {@code NativeNumber}) 171 */ 172 @Constructor(arity = 1) 173 public static Object constructor(final boolean newObj, final Object self, final Object... args) { 174 final double num = (args.length > 0) ? JSType.toNumber(args[0]) : 0.0; 175 176 return newObj? new NativeNumber(num) : num; 177 } 178 179 /** 180 * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) 181 * 182 * @param self self reference 183 * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 184 * 185 * @return number in decimal fixed point notation 186 */ 187 @Function(attributes = Attribute.NOT_ENUMERABLE) 188 public static Object toFixed(final Object self, final Object fractionDigits) { 189 final int f = JSType.toInteger(fractionDigits); 190 if (f < 0 || f > 20) { 191 throw rangeError("invalid.fraction.digits", "toFixed"); 192 } 193 194 final double x = getNumberValue(self); 195 if (Double.isNaN(x)) { 196 return "NaN"; 197 } 198 199 if (Math.abs(x) >= 1e21) { 200 return JSType.toString(x); 201 } 202 203 final NumberFormat format = NumberFormat.getNumberInstance(Locale.US); 204 format.setMinimumFractionDigits(f); 205 format.setMaximumFractionDigits(f); 206 format.setGroupingUsed(false); 207 208 return format.format(x); 209 } 210 211 /** 212 * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits) 213 * 214 * @param self self reference 215 * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number. 216 * 217 * @return number in decimal exponential notation 218 */ 219 @Function(attributes = Attribute.NOT_ENUMERABLE) 220 public static Object toExponential(final Object self, final Object fractionDigits) { 221 final double x = getNumberValue(self); 222 final boolean trimZeros = fractionDigits == UNDEFINED; 223 final int f = trimZeros ? 16 : JSType.toInteger(fractionDigits); 224 225 if (Double.isNaN(x)) { 226 return "NaN"; 227 } else if (Double.isInfinite(x)) { 228 return x > 0? "Infinity" : "-Infinity"; 229 } 230 231 if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) { 232 throw rangeError("invalid.fraction.digits", "toExponential"); 233 } 234 235 final String res = String.format(Locale.US, "%1." + f + "e", x); 236 return fixExponent(res, trimZeros); 237 } 238 239 /** 240 * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) 241 * 242 * @param self self reference 243 * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link NativeDate#toString} if undefined 244 * 245 * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 246 */ 247 @Function(attributes = Attribute.NOT_ENUMERABLE) 248 public static Object toPrecision(final Object self, final Object precision) { 249 final double x = getNumberValue(self); 250 if (precision == UNDEFINED) { 251 return JSType.toString(x); 252 } 253 254 final int p = JSType.toInteger(precision); 255 if (Double.isNaN(x)) { 256 return "NaN"; 257 } else if (Double.isInfinite(x)) { 258 return x > 0? "Infinity" : "-Infinity"; 259 } 260 261 if (p < 1 || p > 21) { 262 throw rangeError("invalid.precision"); 263 } 264 265 // workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160 266 if (x == 0.0 && p <= 1) { 267 return "0"; 268 } 269 270 return fixExponent(String.format(Locale.US, "%." + p + "g", x), false); 271 } 272 273 /** 274 * ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] ) 275 * 276 * @param self self reference 277 * @param radix radix to use for string conversion 278 * @return string representation of this Number in the given radix 279 */ 280 @Function(attributes = Attribute.NOT_ENUMERABLE) 281 public static Object toString(final Object self, final Object radix) { 282 if (radix != UNDEFINED) { 283 final int intRadix = JSType.toInteger(radix); 284 if (intRadix != 10) { 285 if (intRadix < 2 || intRadix > 36) { 286 throw rangeError("invalid.radix"); 287 } 288 return JSType.toString(getNumberValue(self), intRadix); 289 } 290 } 291 292 return JSType.toString(getNumberValue(self)); 293 } 294 295 /** 296 * ECMA 15.7.4.3 Number.prototype.toLocaleString() 297 * 298 * @param self self reference 299 * @return localized string for this Number 300 */ 301 @Function(attributes = Attribute.NOT_ENUMERABLE) 302 public static Object toLocaleString(final Object self) { 303 return JSType.toString(getNumberValue(self)); 304 } 305 306 307 /** 308 * ECMA 15.7.4.4 Number.prototype.valueOf ( ) 309 * 310 * @param self self reference 311 * @return boxed number value for this Number 312 */ 313 @Function(attributes = Attribute.NOT_ENUMERABLE) 314 public static Object valueOf(final Object self) { 315 return getNumberValue(self); 316 } 317 318 /** 319 * Lookup the appropriate method for an invoke dynamic call. 320 * @param request The link request 321 * @param receiver receiver of call 322 * @return Link to be invoked at call site. 323 */ 324 public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) { 325 return PrimitiveLookup.lookupPrimitive(request, Number.class, new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER); 326 } 327 328 @SuppressWarnings("unused") 329 private static NativeNumber wrapFilter(final Object receiver) { 330 return new NativeNumber(((Number)receiver).doubleValue()); 331 } 332 333 private static double getNumberValue(final Object self) { 334 if (self instanceof Number) { 335 return ((Number)self).doubleValue(); 336 } else if (self instanceof NativeNumber) { 337 return ((NativeNumber)self).getValue(); 338 } else if (self != null && self == Global.instance().getNumberPrototype()) { 339 return 0.0; 340 } else { 341 throw typeError("not.a.number", ScriptRuntime.safeToString(self)); 342 } 343 } 344 345 // Exponent of Java "e" or "E" formatter is always 2 digits and zero 346 // padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain 347 // exact number of digits e+1, e+0, e+12 etc. Fix the exponent here. 348 // 349 // Additionally, if trimZeros is true, this cuts trailing zeros in the 350 // fraction part for calls to toExponential() with undefined fractionDigits 351 // argument. 352 private static String fixExponent(final String str, final boolean trimZeros) { 353 final int index = str.indexOf('e'); 354 if (index < 1) { 355 // no exponent, do nothing.. 356 return str; 357 } 358 359 // check if character after e+ or e- is 0 360 final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2; 361 // check if there are any trailing zeroes we should remove 362 363 int fractionOffset = index; 364 if (trimZeros) { 365 assert fractionOffset > 0; 366 char c = str.charAt(fractionOffset - 1); 367 while (fractionOffset > 1 && (c == '0' || c == '.')) { 368 c = str.charAt(--fractionOffset - 1); 369 } 370 371 } 372 // if anything needs to be done compose a new string 373 if (fractionOffset < index || expPadding == 3) { 374 return str.substring(0, fractionOffset) 375 + str.substring(index, index + 2) 376 + str.substring(index + expPadding); 377 } 378 return str; 379 } 380 381 private static MethodHandle findWrapFilter() { 382 return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, "wrapFilter", MH.type(NativeNumber.class, Object.class)); 383 } 384 }