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