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