NativeNumber.java revision 953:221a84ef44c0
1279377Simp/* 2279377Simp * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3279377Simp * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4279377Simp * 5279377Simp * This code is free software; you can redistribute it and/or modify it 6279377Simp * under the terms of the GNU General Public License version 2 only, as 7279377Simp * published by the Free Software Foundation. Oracle designates this 8279377Simp * particular file as subject to the "Classpath" exception as provided 9279377Simp * by Oracle in the LICENSE file that accompanied this code. 10279377Simp * 11279377Simp * This code is distributed in the hope that it will be useful, but WITHOUT 12279377Simp * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13279377Simp * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14279377Simp * version 2 for more details (a copy is included in the LICENSE file that 15279377Simp * accompanied this code). 16279377Simp * 17279377Simp * You should have received a copy of the GNU General Public License version 18279377Simp * 2 along with this work; if not, write to the Free Software Foundation, 19279377Simp * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20279377Simp * 21279377Simp * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22279377Simp * or visit www.oracle.com if you need additional information or have any 23279377Simp * questions. 24279377Simp */ 25279377Simp 26279377Simppackage jdk.nashorn.internal.objects; 27279377Simp 28279377Simpimport static jdk.nashorn.internal.lookup.Lookup.MH; 29279377Simpimport static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 30279377Simpimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 31279377Simpimport static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 32279377Simp 33279377Simpimport java.lang.invoke.MethodHandle; 34279377Simpimport java.lang.invoke.MethodHandles; 35279377Simpimport java.lang.invoke.MethodType; 36279377Simpimport java.text.NumberFormat; 37279377Simpimport java.util.Locale; 38279377Simpimport jdk.internal.dynalink.linker.GuardedInvocation; 39279377Simpimport jdk.internal.dynalink.linker.LinkRequest; 40279377Simpimport jdk.nashorn.internal.objects.annotations.Attribute; 41279377Simpimport jdk.nashorn.internal.objects.annotations.Constructor; 42279377Simpimport jdk.nashorn.internal.objects.annotations.Function; 43279377Simpimport jdk.nashorn.internal.objects.annotations.Property; 44279377Simpimport jdk.nashorn.internal.objects.annotations.ScriptClass; 45279377Simpimport jdk.nashorn.internal.objects.annotations.SpecializedFunction; 46279377Simpimport jdk.nashorn.internal.objects.annotations.Where; 47279377Simpimport jdk.nashorn.internal.runtime.JSType; 48279377Simpimport jdk.nashorn.internal.runtime.PropertyMap; 49279377Simpimport jdk.nashorn.internal.runtime.ScriptObject; 50279377Simpimport jdk.nashorn.internal.runtime.ScriptRuntime; 51279377Simpimport jdk.nashorn.internal.runtime.linker.PrimitiveLookup; 52279377Simp 53279377Simp/** 54279377Simp * ECMA 15.7 Number Objects. 55279377Simp * 56279377Simp */ 57295436Sandrew@ScriptClass("Number") 58279377Simppublic final class NativeNumber extends ScriptObject { 59279377Simp 60279377Simp // Method handle to create an object wrapper for a primitive number 61279377Simp private static final MethodHandle WRAPFILTER = findOwnMH("wrapFilter", MH.type(NativeNumber.class, Object.class)); 62279377Simp // Method handle to retrieve the Number prototype object 63279377Simp private static final MethodHandle PROTOFILTER = findOwnMH("protoFilter", MH.type(Object.class, Object.class)); 64279377Simp 65279377Simp /** ECMA 15.7.3.2 largest positive finite value */ 66279377Simp @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 67295436Sandrew public static final double MAX_VALUE = Double.MAX_VALUE; 68295436Sandrew 69295436Sandrew /** ECMA 15.7.3.3 smallest positive finite value */ 70279377Simp @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 71279377Simp public static final double MIN_VALUE = Double.MIN_VALUE; 72279377Simp 73279377Simp /** ECMA 15.7.3.4 NaN */ 74279377Simp @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 75279377Simp public static final double NaN = Double.NaN; 76279377Simp 77279377Simp /** ECMA 15.7.3.5 negative infinity */ 78279377Simp @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 79279377Simp public static final double NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY; 80279377Simp 81279377Simp /** ECMA 15.7.3.5 positive infinity */ 82279377Simp @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 83279377Simp public static final double POSITIVE_INFINITY = Double.POSITIVE_INFINITY; 84279377Simp 85279377Simp private final double value; 86279377Simp 87279377Simp // initialized by nasgen 88279377Simp private static PropertyMap $nasgenmap$; 89279377Simp 90279377Simp private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) { 91279377Simp super(proto, map); 92279377Simp this.value = value; 93279377Simp } 94279377Simp 95279377Simp NativeNumber(final double value, final Global global) { 96279377Simp this(value, global.getNumberPrototype(), $nasgenmap$); 97279377Simp } 98279377Simp 99279377Simp private NativeNumber(final double value) { 100279377Simp this(value, Global.instance()); 101279377Simp } 102279377Simp 103279377Simp 104279377Simp @Override 105279377Simp public String safeToString() { 106279377Simp return "[Number " + toString() + "]"; 107279377Simp } 108279377Simp 109279377Simp @Override 110279377Simp public String toString() { 111279377Simp return Double.toString(getValue()); 112279377Simp } 113279377Simp 114279377Simp /** 115279377Simp * Get the value of this Number 116279377Simp * @return a {@code double} representing the Number value 117279377Simp */ 118279377Simp public double getValue() { 119279377Simp return doubleValue(); 120279377Simp } 121279377Simp 122279377Simp /** 123279377Simp * Get the value of this Number 124279377Simp * @return a {@code double} representing the Number value 125279377Simp */ 126279377Simp public double doubleValue() { 127279377Simp return value; 128279377Simp } 129279377Simp 130279377Simp @Override 131279377Simp public String getClassName() { 132279377Simp return "Number"; 133279377Simp } 134279377Simp 135279377Simp /** 136279377Simp * ECMA 15.7.2 - The Number constructor 137279377Simp * 138279377Simp * @param newObj is this Number instantiated with the new operator 139279377Simp * @param self self reference 140279377Simp * @param args value of number 141279377Simp * @return the Number instance (internally represented as a {@code NativeNumber}) 142279377Simp */ 143279377Simp @Constructor(arity = 1) 144279377Simp public static Object constructor(final boolean newObj, final Object self, final Object... args) { 145279377Simp final double num = (args.length > 0) ? JSType.toNumber(args[0]) : 0.0; 146279377Simp 147279377Simp return newObj? new NativeNumber(num) : num; 148279377Simp } 149279377Simp 150279377Simp /** 151279377Simp * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) 152279377Simp * 153279377Simp * @param self self reference 154295436Sandrew * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 155295436Sandrew * 156295436Sandrew * @return number in decimal fixed point notation 157279377Simp */ 158295436Sandrew @Function(attributes = Attribute.NOT_ENUMERABLE) 159295436Sandrew public static String toFixed(final Object self, final Object fractionDigits) { 160295436Sandrew return toFixed(self, JSType.toInteger(fractionDigits)); 161295436Sandrew } 162295436Sandrew 163279377Simp /** 164295436Sandrew * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) specialized for int fractionDigits 165295436Sandrew * 166295436Sandrew * @param self self reference 167295436Sandrew * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 168295436Sandrew * 169279377Simp * @return number in decimal fixed point notation 170295436Sandrew */ 171295436Sandrew @SpecializedFunction 172295436Sandrew public static String toFixed(final Object self, final int fractionDigits) { 173295436Sandrew if (fractionDigits < 0 || fractionDigits > 20) { 174295436Sandrew throw rangeError("invalid.fraction.digits", "toFixed"); 175279377Simp } 176295436Sandrew 177295436Sandrew final double x = getNumberValue(self); 178295436Sandrew if (Double.isNaN(x)) { 179295436Sandrew return "NaN"; 180279377Simp } 181295436Sandrew 182295436Sandrew if (Math.abs(x) >= 1e21) { 183295436Sandrew return JSType.toString(x); 184295436Sandrew } 185279377Simp 186295436Sandrew final NumberFormat format = NumberFormat.getNumberInstance(Locale.US); 187295436Sandrew format.setMinimumFractionDigits(fractionDigits); 188295436Sandrew format.setMaximumFractionDigits(fractionDigits); 189295436Sandrew format.setGroupingUsed(false); 190279377Simp 191279377Simp return format.format(x); 192279377Simp } 193279377Simp 194279377Simp /** 195279377Simp * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits) 196279377Simp * 197279377Simp * @param self self reference 198279377Simp * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number. 199279377Simp * 200279377Simp * @return number in decimal exponential notation 201279377Simp */ 202279377Simp @Function(attributes = Attribute.NOT_ENUMERABLE) 203279377Simp public static String toExponential(final Object self, final Object fractionDigits) { 204279377Simp final double x = getNumberValue(self); 205279377Simp final boolean trimZeros = fractionDigits == UNDEFINED; 206279377Simp final int f = trimZeros ? 16 : JSType.toInteger(fractionDigits); 207279377Simp 208279377Simp if (Double.isNaN(x)) { 209279377Simp return "NaN"; 210279377Simp } else if (Double.isInfinite(x)) { 211279377Simp return x > 0? "Infinity" : "-Infinity"; 212279377Simp } 213279377Simp 214279377Simp if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) { 215279377Simp throw rangeError("invalid.fraction.digits", "toExponential"); 216279377Simp } 217279377Simp 218279377Simp final String res = String.format(Locale.US, "%1." + f + "e", x); 219279377Simp return fixExponent(res, trimZeros); 220279377Simp } 221279377Simp 222279377Simp /** 223279377Simp * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) 224279377Simp * 225279377Simp * @param self self reference 226279377Simp * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link JSType#toString} if undefined 227279377Simp * 228279377Simp * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 229279377Simp */ 230279377Simp @Function(attributes = Attribute.NOT_ENUMERABLE) 231279377Simp public static String toPrecision(final Object self, final Object precision) { 232279377Simp final double x = getNumberValue(self); 233279377Simp if (precision == UNDEFINED) { 234279377Simp return JSType.toString(x); 235279377Simp } 236279377Simp return (toPrecision(x, JSType.toInteger(precision))); 237279377Simp } 238279377Simp 239279377Simp /** 240279377Simp * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) specialized f 241279377Simp * 242279377Simp * @param self self reference 243279377Simp * @param precision use {@code precision - 1} digits after the significand's decimal point. 244279377Simp * 245279377Simp * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 246279377Simp */ 247279377Simp @SpecializedFunction 248279377Simp public static String toPrecision(final Object self, final int precision) { 249279377Simp return toPrecision(getNumberValue(self), precision); 250279377Simp } 251279377Simp 252279377Simp private static String toPrecision(final double x, final int p) { 253279377Simp if (Double.isNaN(x)) { 254279377Simp return "NaN"; 255279377Simp } else if (Double.isInfinite(x)) { 256279377Simp return x > 0? "Infinity" : "-Infinity"; 257279377Simp } 258279377Simp 259279377Simp if (p < 1 || p > 21) { 260279377Simp throw rangeError("invalid.precision"); 261279377Simp } 262279377Simp 263279377Simp // workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160 264279377Simp if (x == 0.0 && p <= 1) { 265279377Simp return "0"; 266279377Simp } 267279377Simp 268279377Simp return fixExponent(String.format(Locale.US, "%." + p + "g", x), false); 269279377Simp } 270279377Simp 271279377Simp /** 272279377Simp * ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] ) 273279377Simp * 274279377Simp * @param self self reference 275279377Simp * @param radix radix to use for string conversion 276279377Simp * @return string representation of this Number in the given radix 277279377Simp */ 278279377Simp @Function(attributes = Attribute.NOT_ENUMERABLE) 279279377Simp public static String toString(final Object self, final Object radix) { 280279377Simp if (radix != UNDEFINED) { 281279377Simp final int intRadix = JSType.toInteger(radix); 282279377Simp if (intRadix != 10) { 283279377Simp if (intRadix < 2 || intRadix > 36) { 284279377Simp throw rangeError("invalid.radix"); 285279377Simp } 286279377Simp return JSType.toString(getNumberValue(self), intRadix); 287279377Simp } 288279377Simp } 289279377Simp 290279377Simp return JSType.toString(getNumberValue(self)); 291279377Simp } 292279377Simp 293279377Simp /** 294279377Simp * ECMA 15.7.4.3 Number.prototype.toLocaleString() 295279377Simp * 296279377Simp * @param self self reference 297279377Simp * @return localized string for this Number 298279377Simp */ 299279377Simp @Function(attributes = Attribute.NOT_ENUMERABLE) 300279377Simp public static String toLocaleString(final Object self) { 301279377Simp return JSType.toString(getNumberValue(self)); 302279377Simp } 303279377Simp 304279377Simp 305279377Simp /** 306279377Simp * ECMA 15.7.4.4 Number.prototype.valueOf ( ) 307279377Simp * 308279377Simp * @param self self reference 309279377Simp * @return number value for this Number 310279377Simp */ 311279377Simp @Function(attributes = Attribute.NOT_ENUMERABLE) 312279377Simp public static double valueOf(final Object self) { 313279377Simp return getNumberValue(self); 314279377Simp } 315279377Simp 316279377Simp /** 317279377Simp * Lookup the appropriate method for an invoke dynamic call. 318279377Simp * @param request The link request 319279377Simp * @param receiver receiver of call 320279377Simp * @return Link to be invoked at call site. 321279377Simp */ 322279377Simp public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) { 323279377Simp return PrimitiveLookup.lookupPrimitive(request, Number.class, new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER, PROTOFILTER); 324279377Simp } 325279377Simp 326279377Simp @SuppressWarnings("unused") 327279377Simp private static NativeNumber wrapFilter(final Object receiver) { 328 return new NativeNumber(((Number)receiver).doubleValue()); 329 } 330 331 @SuppressWarnings("unused") 332 private static Object protoFilter(final Object object) { 333 return Global.instance().getNumberPrototype(); 334 } 335 336 private static double getNumberValue(final Object self) { 337 if (self instanceof Number) { 338 return ((Number)self).doubleValue(); 339 } else if (self instanceof NativeNumber) { 340 return ((NativeNumber)self).getValue(); 341 } else if (self != null && self == Global.instance().getNumberPrototype()) { 342 return 0.0; 343 } else { 344 throw typeError("not.a.number", ScriptRuntime.safeToString(self)); 345 } 346 } 347 348 // Exponent of Java "e" or "E" formatter is always 2 digits and zero 349 // padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain 350 // exact number of digits e+1, e+0, e+12 etc. Fix the exponent here. 351 // 352 // Additionally, if trimZeros is true, this cuts trailing zeros in the 353 // fraction part for calls to toExponential() with undefined fractionDigits 354 // argument. 355 private static String fixExponent(final String str, final boolean trimZeros) { 356 final int index = str.indexOf('e'); 357 if (index < 1) { 358 // no exponent, do nothing.. 359 return str; 360 } 361 362 // check if character after e+ or e- is 0 363 final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2; 364 // check if there are any trailing zeroes we should remove 365 366 int fractionOffset = index; 367 if (trimZeros) { 368 assert fractionOffset > 0; 369 char c = str.charAt(fractionOffset - 1); 370 while (fractionOffset > 1 && (c == '0' || c == '.')) { 371 c = str.charAt(--fractionOffset - 1); 372 } 373 374 } 375 // if anything needs to be done compose a new string 376 if (fractionOffset < index || expPadding == 3) { 377 return str.substring(0, fractionOffset) 378 + str.substring(index, index + 2) 379 + str.substring(index + expPadding); 380 } 381 return str; 382 } 383 384 private static MethodHandle findOwnMH(final String name, final MethodType type) { 385 return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type); 386 } 387} 388