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