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.lookup.Lookup.MH; 29 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 30 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 31 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 32 33 import java.lang.invoke.MethodHandle; 34 import java.lang.invoke.MethodHandles; 35 import java.lang.invoke.MethodType; 36 import java.math.RoundingMode; 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.SpecializedFunction; 47 import jdk.nashorn.internal.objects.annotations.Where; 48 import jdk.nashorn.internal.runtime.JSType; 49 import jdk.nashorn.internal.runtime.PropertyMap; 50 import jdk.nashorn.internal.runtime.ScriptObject; 51 import jdk.nashorn.internal.runtime.ScriptRuntime; 52 import jdk.nashorn.internal.runtime.linker.NashornGuards; 53 import jdk.nashorn.internal.runtime.linker.PrimitiveLookup; 54 55 /** 56 * ECMA 15.7 Number Objects. 57 * 58 */ 59 @ScriptClass("Number") 60 public final class NativeNumber extends ScriptObject { 61 62 /** Method handle to create an object wrapper for a primitive number. */ 63 static final MethodHandle WRAPFILTER = findOwnMH("wrapFilter", MH.type(NativeNumber.class, Object.class)); 64 /** Method handle to retrieve the Number prototype object. */ 65 private static final MethodHandle PROTOFILTER = findOwnMH("protoFilter", MH.type(Object.class, Object.class)); 66 67 /** ECMA 15.7.3.2 largest positive finite value */ 68 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 69 public static final double MAX_VALUE = Double.MAX_VALUE; 70 71 /** ECMA 15.7.3.3 smallest positive finite value */ 72 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 73 public static final double MIN_VALUE = Double.MIN_VALUE; 74 75 /** ECMA 15.7.3.4 NaN */ 76 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 77 public static final double NaN = Double.NaN; 78 79 /** ECMA 15.7.3.5 negative infinity */ 80 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 81 public static final double NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY; 82 83 /** ECMA 15.7.3.5 positive infinity */ 84 @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 85 public static final double POSITIVE_INFINITY = Double.POSITIVE_INFINITY; 86 87 private final double value; 88 89 // initialized by nasgen 90 private static PropertyMap $nasgenmap$; 91 NativeNumber(final double value, final ScriptObject proto, final PropertyMap map)92 private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) { 93 super(proto, map); 94 this.value = value; 95 } 96 NativeNumber(final double value, final Global global)97 NativeNumber(final double value, final Global global) { 98 this(value, global.getNumberPrototype(), $nasgenmap$); 99 } 100 NativeNumber(final double value)101 private NativeNumber(final double value) { 102 this(value, Global.instance()); 103 } 104 105 106 @Override safeToString()107 public String safeToString() { 108 return "[Number " + toString() + "]"; 109 } 110 111 @Override toString()112 public String toString() { 113 return Double.toString(getValue()); 114 } 115 116 /** 117 * Get the value of this Number 118 * @return a {@code double} representing the Number value 119 */ getValue()120 public double getValue() { 121 return doubleValue(); 122 } 123 124 /** 125 * Get the value of this Number 126 * @return a {@code double} representing the Number value 127 */ doubleValue()128 public double doubleValue() { 129 return value; 130 } 131 132 @Override getClassName()133 public String getClassName() { 134 return "Number"; 135 } 136 137 /** 138 * ECMA 15.7.2 - The Number constructor 139 * 140 * @param newObj is this Number instantiated with the new operator 141 * @param self self reference 142 * @param args value of number 143 * @return the Number instance (internally represented as a {@code NativeNumber}) 144 */ 145 @Constructor(arity = 1) constructor(final boolean newObj, final Object self, final Object... args)146 public static Object constructor(final boolean newObj, final Object self, final Object... args) { 147 final double num = (args.length > 0) ? JSType.toNumber(args[0]) : 0.0; 148 149 return newObj? new NativeNumber(num) : num; 150 } 151 152 /** 153 * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) 154 * 155 * @param self self reference 156 * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 157 * 158 * @return number in decimal fixed point notation 159 */ 160 @Function(attributes = Attribute.NOT_ENUMERABLE) toFixed(final Object self, final Object fractionDigits)161 public static String toFixed(final Object self, final Object fractionDigits) { 162 return toFixed(self, JSType.toInteger(fractionDigits)); 163 } 164 165 /** 166 * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) specialized for int fractionDigits 167 * 168 * @param self self reference 169 * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 170 * 171 * @return number in decimal fixed point notation 172 */ 173 @SpecializedFunction toFixed(final Object self, final int fractionDigits)174 public static String toFixed(final Object self, final int fractionDigits) { 175 if (fractionDigits < 0 || fractionDigits > 20) { 176 throw rangeError("invalid.fraction.digits", "toFixed"); 177 } 178 179 final double x = getNumberValue(self); 180 if (Double.isNaN(x)) { 181 return "NaN"; 182 } 183 184 if (Math.abs(x) >= 1e21) { 185 return JSType.toString(x); 186 } 187 188 final NumberFormat format = NumberFormat.getNumberInstance(Locale.US); 189 format.setMinimumFractionDigits(fractionDigits); 190 format.setMaximumFractionDigits(fractionDigits); 191 format.setGroupingUsed(false); 192 format.setRoundingMode(RoundingMode.HALF_UP); 193 194 return format.format(x); 195 } 196 197 /** 198 * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits) 199 * 200 * @param self self reference 201 * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number. 202 * 203 * @return number in decimal exponential notation 204 */ 205 @Function(attributes = Attribute.NOT_ENUMERABLE) toExponential(final Object self, final Object fractionDigits)206 public static String toExponential(final Object self, final Object fractionDigits) { 207 final double x = getNumberValue(self); 208 final boolean trimZeros = fractionDigits == UNDEFINED; 209 final int f = trimZeros ? 16 : JSType.toInteger(fractionDigits); 210 211 if (Double.isNaN(x)) { 212 return "NaN"; 213 } else if (Double.isInfinite(x)) { 214 return x > 0? "Infinity" : "-Infinity"; 215 } 216 217 if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) { 218 throw rangeError("invalid.fraction.digits", "toExponential"); 219 } 220 221 final String res = String.format(Locale.US, "%1." + f + "e", x); 222 return fixExponent(res, trimZeros); 223 } 224 225 /** 226 * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) 227 * 228 * @param self self reference 229 * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link JSType#toString} if undefined 230 * 231 * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 232 */ 233 @Function(attributes = Attribute.NOT_ENUMERABLE) toPrecision(final Object self, final Object precision)234 public static String toPrecision(final Object self, final Object precision) { 235 final double x = getNumberValue(self); 236 if (precision == UNDEFINED) { 237 return JSType.toString(x); 238 } 239 return (toPrecision(x, JSType.toInteger(precision))); 240 } 241 242 /** 243 * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) specialized f 244 * 245 * @param self self reference 246 * @param precision use {@code precision - 1} digits after the significand's decimal point. 247 * 248 * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 249 */ 250 @SpecializedFunction toPrecision(final Object self, final int precision)251 public static String toPrecision(final Object self, final int precision) { 252 return toPrecision(getNumberValue(self), precision); 253 } 254 toPrecision(final double x, final int p)255 private static String toPrecision(final double x, final int p) { 256 if (Double.isNaN(x)) { 257 return "NaN"; 258 } else if (Double.isInfinite(x)) { 259 return x > 0? "Infinity" : "-Infinity"; 260 } 261 262 if (p < 1 || p > 21) { 263 throw rangeError("invalid.precision"); 264 } 265 266 // workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160 267 if (x == 0.0 && p <= 1) { 268 return "0"; 269 } 270 271 return fixExponent(String.format(Locale.US, "%." + p + "g", x), false); 272 } 273 274 /** 275 * ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] ) 276 * 277 * @param self self reference 278 * @param radix radix to use for string conversion 279 * @return string representation of this Number in the given radix 280 */ 281 @Function(attributes = Attribute.NOT_ENUMERABLE) toString(final Object self, final Object radix)282 public static String toString(final Object self, final Object radix) { 283 if (radix != UNDEFINED) { 284 final int intRadix = JSType.toInteger(radix); 285 if (intRadix != 10) { 286 if (intRadix < 2 || intRadix > 36) { 287 throw rangeError("invalid.radix"); 288 } 289 return JSType.toString(getNumberValue(self), intRadix); 290 } 291 } 292 293 return JSType.toString(getNumberValue(self)); 294 } 295 296 /** 297 * ECMA 15.7.4.3 Number.prototype.toLocaleString() 298 * 299 * @param self self reference 300 * @return localized string for this Number 301 */ 302 @Function(attributes = Attribute.NOT_ENUMERABLE) toLocaleString(final Object self)303 public static String toLocaleString(final Object self) { 304 return JSType.toString(getNumberValue(self)); 305 } 306 307 308 /** 309 * ECMA 15.7.4.4 Number.prototype.valueOf ( ) 310 * 311 * @param self self reference 312 * @return number value for this Number 313 */ 314 @Function(attributes = Attribute.NOT_ENUMERABLE) valueOf(final Object self)315 public static double valueOf(final Object self) { 316 return getNumberValue(self); 317 } 318 319 /** 320 * Lookup the appropriate method for an invoke dynamic call. 321 * @param request The link request 322 * @param receiver receiver of call 323 * @return Link to be invoked at call site. 324 */ lookupPrimitive(final LinkRequest request, final Object receiver)325 public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) { 326 return PrimitiveLookup.lookupPrimitive(request, NashornGuards.getNumberGuard(), new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER, PROTOFILTER); 327 } 328 329 @SuppressWarnings("unused") wrapFilter(final Object receiver)330 private static NativeNumber wrapFilter(final Object receiver) { 331 return new NativeNumber(((Number)receiver).doubleValue()); 332 } 333 334 @SuppressWarnings("unused") protoFilter(final Object object)335 private static Object protoFilter(final Object object) { 336 return Global.instance().getNumberPrototype(); 337 } 338 getNumberValue(final Object self)339 private static double getNumberValue(final Object self) { 340 if (self instanceof Number) { 341 return ((Number)self).doubleValue(); 342 } else if (self instanceof NativeNumber) { 343 return ((NativeNumber)self).getValue(); 344 } else if (self != null && self == Global.instance().getNumberPrototype()) { 345 return 0.0; 346 } else { 347 throw typeError("not.a.number", ScriptRuntime.safeToString(self)); 348 } 349 } 350 351 // Exponent of Java "e" or "E" formatter is always 2 digits and zero 352 // padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain 353 // exact number of digits e+1, e+0, e+12 etc. Fix the exponent here. 354 // 355 // Additionally, if trimZeros is true, this cuts trailing zeros in the 356 // fraction part for calls to toExponential() with undefined fractionDigits 357 // argument. fixExponent(final String str, final boolean trimZeros)358 private static String fixExponent(final String str, final boolean trimZeros) { 359 final int index = str.indexOf('e'); 360 if (index < 1) { 361 // no exponent, do nothing.. 362 return str; 363 } 364 365 // check if character after e+ or e- is 0 366 final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2; 367 // check if there are any trailing zeroes we should remove 368 369 int fractionOffset = index; 370 if (trimZeros) { 371 assert fractionOffset > 0; 372 char c = str.charAt(fractionOffset - 1); 373 while (fractionOffset > 1 && (c == '0' || c == '.')) { 374 c = str.charAt(--fractionOffset - 1); 375 } 376 377 } 378 // if anything needs to be done compose a new string 379 if (fractionOffset < index || expPadding == 3) { 380 return str.substring(0, fractionOffset) 381 + str.substring(index, index + 2) 382 + str.substring(index + expPadding); 383 } 384 return str; 385 } 386 findOwnMH(final String name, final MethodType type)387 private static MethodHandle findOwnMH(final String name, final MethodType type) { 388 return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type); 389 } 390 } 391