NativeNumber.java revision 1551:f3b883bec2d0
1227569Sphilip/* 2227569Sphilip * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3227569Sphilip * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4227569Sphilip * 5227569Sphilip * This code is free software; you can redistribute it and/or modify it 6227569Sphilip * under the terms of the GNU General Public License version 2 only, as 7227569Sphilip * published by the Free Software Foundation. Oracle designates this 8227569Sphilip * particular file as subject to the "Classpath" exception as provided 9227569Sphilip * by Oracle in the LICENSE file that accompanied this code. 10227569Sphilip * 11227569Sphilip * This code is distributed in the hope that it will be useful, but WITHOUT 12227569Sphilip * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13227569Sphilip * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14227569Sphilip * version 2 for more details (a copy is included in the LICENSE file that 15227569Sphilip * accompanied this code). 16227569Sphilip * 17227569Sphilip * You should have received a copy of the GNU General Public License version 18227569Sphilip * 2 along with this work; if not, write to the Free Software Foundation, 19227569Sphilip * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20227569Sphilip * 21227569Sphilip * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22227569Sphilip * or visit www.oracle.com if you need additional information or have any 23227569Sphilip * questions. 24227569Sphilip */ 25227569Sphilip 26228100Sphilippackage jdk.nashorn.internal.objects; 27228100Sphilip 28228100Sphilipimport static jdk.nashorn.internal.lookup.Lookup.MH; 29227569Sphilipimport static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 30227569Sphilipimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 31227569Sphilipimport static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 32227569Sphilip 33227569Sphilipimport java.lang.invoke.MethodHandle; 34227569Sphilipimport java.lang.invoke.MethodHandles; 35227569Sphilipimport java.lang.invoke.MethodType; 36227569Sphilipimport java.math.RoundingMode; 37227569Sphilipimport java.text.NumberFormat; 38227569Sphilipimport java.util.Locale; 39227569Sphilipimport jdk.dynalink.linker.GuardedInvocation; 40227569Sphilipimport jdk.dynalink.linker.LinkRequest; 41227569Sphilipimport jdk.nashorn.internal.objects.annotations.Attribute; 42227569Sphilipimport jdk.nashorn.internal.objects.annotations.Constructor; 43227569Sphilipimport jdk.nashorn.internal.objects.annotations.Function; 44227569Sphilipimport jdk.nashorn.internal.objects.annotations.Property; 45227569Sphilipimport jdk.nashorn.internal.objects.annotations.ScriptClass; 46227569Sphilipimport jdk.nashorn.internal.objects.annotations.SpecializedFunction; 47227569Sphilipimport jdk.nashorn.internal.objects.annotations.Where; 48227569Sphilipimport jdk.nashorn.internal.runtime.JSType; 49227569Sphilipimport jdk.nashorn.internal.runtime.PropertyMap; 50227569Sphilipimport jdk.nashorn.internal.runtime.ScriptObject; 51227569Sphilipimport jdk.nashorn.internal.runtime.ScriptRuntime; 52227569Sphilipimport jdk.nashorn.internal.runtime.doubleconv.DoubleConversion; 53227569Sphilipimport jdk.nashorn.internal.runtime.linker.PrimitiveLookup; 54227569Sphilip 55227569Sphilip/** 56227569Sphilip * ECMA 15.7 Number Objects. 57227569Sphilip * 58227569Sphilip */ 59227569Sphilip@ScriptClass("Number") 60227569Sphilippublic final class NativeNumber extends ScriptObject { 61227569Sphilip 62227569Sphilip /** Method handle to create an object wrapper for a primitive number. */ 63227569Sphilip static final MethodHandle WRAPFILTER = findOwnMH("wrapFilter", MH.type(NativeNumber.class, Object.class)); 64227569Sphilip /** Method handle to retrieve the Number prototype object. */ 65227569Sphilip private static final MethodHandle PROTOFILTER = findOwnMH("protoFilter", MH.type(Object.class, Object.class)); 66227569Sphilip 67227569Sphilip /** ECMA 15.7.3.2 largest positive finite value */ 68227569Sphilip @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 69227569Sphilip public static final double MAX_VALUE = Double.MAX_VALUE; 70227569Sphilip 71227569Sphilip /** ECMA 15.7.3.3 smallest positive finite value */ 72227569Sphilip @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 73227569Sphilip public static final double MIN_VALUE = Double.MIN_VALUE; 74227569Sphilip 75227569Sphilip /** ECMA 15.7.3.4 NaN */ 76227569Sphilip @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 77227569Sphilip public static final double NaN = Double.NaN; 78227569Sphilip 79227569Sphilip /** ECMA 15.7.3.5 negative infinity */ 80227569Sphilip @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 81227569Sphilip public static final double NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY; 82227569Sphilip 83227569Sphilip /** ECMA 15.7.3.5 positive infinity */ 84227569Sphilip @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR) 85227569Sphilip public static final double POSITIVE_INFINITY = Double.POSITIVE_INFINITY; 86227569Sphilip 87227569Sphilip private final double value; 88227569Sphilip 89227569Sphilip // initialized by nasgen 90227569Sphilip private static PropertyMap $nasgenmap$; 91227569Sphilip 92227569Sphilip private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) { 93227569Sphilip super(proto, map); 94227569Sphilip this.value = value; 95227569Sphilip } 96227569Sphilip 97227569Sphilip NativeNumber(final double value, final Global global) { 98227569Sphilip this(value, global.getNumberPrototype(), $nasgenmap$); 99227569Sphilip } 100227569Sphilip 101227569Sphilip private NativeNumber(final double value) { 102227569Sphilip this(value, Global.instance()); 103227569Sphilip } 104227569Sphilip 105227569Sphilip 106227569Sphilip @Override 107227569Sphilip public String safeToString() { 108227569Sphilip return "[Number " + toString() + "]"; 109227569Sphilip } 110227569Sphilip 111227569Sphilip @Override 112227569Sphilip public String toString() { 113227569Sphilip return Double.toString(getValue()); 114227569Sphilip } 115227569Sphilip 116227569Sphilip /** 117227569Sphilip * Get the value of this Number 118227569Sphilip * @return a {@code double} representing the Number value 119227569Sphilip */ 120227569Sphilip public double getValue() { 121227569Sphilip return doubleValue(); 122227569Sphilip } 123227569Sphilip 124227569Sphilip /** 125227569Sphilip * Get the value of this Number 126227569Sphilip * @return a {@code double} representing the Number value 127227569Sphilip */ 128227569Sphilip public double doubleValue() { 129227569Sphilip return value; 130227569Sphilip } 131227569Sphilip 132227569Sphilip @Override 133227569Sphilip public String getClassName() { 134227569Sphilip return "Number"; 135227569Sphilip } 136227569Sphilip 137227569Sphilip /** 138227569Sphilip * ECMA 15.7.2 - The Number constructor 139227569Sphilip * 140227569Sphilip * @param newObj is this Number instantiated with the new operator 141227569Sphilip * @param self self reference 142227569Sphilip * @param args value of number 143227569Sphilip * @return the Number instance (internally represented as a {@code NativeNumber}) 144227569Sphilip */ 145227569Sphilip @Constructor(arity = 1) 146227569Sphilip public static Object constructor(final boolean newObj, final Object self, final Object... args) { 147227569Sphilip final double num = (args.length > 0) ? JSType.toNumber(args[0]) : 0.0; 148227569Sphilip 149227569Sphilip return newObj? new NativeNumber(num) : num; 150227569Sphilip } 151227569Sphilip 152227569Sphilip /** 153227569Sphilip * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) 154227569Sphilip * 155227569Sphilip * @param self self reference 156227569Sphilip * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 157227569Sphilip * 158227569Sphilip * @return number in decimal fixed point notation 159227569Sphilip */ 160227569Sphilip @Function(attributes = Attribute.NOT_ENUMERABLE) 161227569Sphilip public static String toFixed(final Object self, final Object fractionDigits) { 162227569Sphilip return toFixed(self, JSType.toInteger(fractionDigits)); 163227569Sphilip } 164227569Sphilip 165227569Sphilip /** 166227569Sphilip * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) specialized for int fractionDigits 167227569Sphilip * 168227569Sphilip * @param self self reference 169227569Sphilip * @param fractionDigits how many digits should be after the decimal point, 0 if undefined 170227569Sphilip * 171227569Sphilip * @return number in decimal fixed point notation 172227569Sphilip */ 173227569Sphilip @SpecializedFunction 174227569Sphilip public static String toFixed(final Object self, final int fractionDigits) { 175227569Sphilip if (fractionDigits < 0 || fractionDigits > 20) { 176227569Sphilip throw rangeError("invalid.fraction.digits", "toFixed"); 177227569Sphilip } 178227569Sphilip 179227569Sphilip final double x = getNumberValue(self); 180227569Sphilip if (Double.isNaN(x)) { 181227569Sphilip return "NaN"; 182227569Sphilip } 183227569Sphilip 184227569Sphilip if (Math.abs(x) >= 1e21) { 185227569Sphilip return JSType.toString(x); 186227569Sphilip } 187227569Sphilip 188227569Sphilip return DoubleConversion.toFixed(x, fractionDigits); 189227569Sphilip } 190227569Sphilip 191227569Sphilip /** 192227569Sphilip * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits) 193227569Sphilip * 194227569Sphilip * @param self self reference 195227569Sphilip * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number. 196227569Sphilip * 197227569Sphilip * @return number in decimal exponential notation 198227569Sphilip */ 199227569Sphilip @Function(attributes = Attribute.NOT_ENUMERABLE) 200227569Sphilip public static String toExponential(final Object self, final Object fractionDigits) { 201227569Sphilip final double x = getNumberValue(self); 202227569Sphilip final boolean trimZeros = fractionDigits == UNDEFINED; 203227569Sphilip final int f = trimZeros ? 16 : JSType.toInteger(fractionDigits); 204227569Sphilip 205227569Sphilip if (Double.isNaN(x)) { 206227569Sphilip return "NaN"; 207227569Sphilip } else if (Double.isInfinite(x)) { 208227569Sphilip return x > 0? "Infinity" : "-Infinity"; 209227569Sphilip } 210227569Sphilip 211227569Sphilip if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) { 212227569Sphilip throw rangeError("invalid.fraction.digits", "toExponential"); 213227569Sphilip } 214227569Sphilip 215227569Sphilip final String res = String.format(Locale.US, "%1." + f + "e", x); 216227569Sphilip return fixExponent(res, trimZeros); 217227569Sphilip } 218227569Sphilip 219227569Sphilip /** 220227569Sphilip * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) 221227569Sphilip * 222227569Sphilip * @param self self reference 223227569Sphilip * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link JSType#toString} if undefined 224227569Sphilip * 225227569Sphilip * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 226227569Sphilip */ 227227569Sphilip @Function(attributes = Attribute.NOT_ENUMERABLE) 228227569Sphilip public static String toPrecision(final Object self, final Object precision) { 229227569Sphilip final double x = getNumberValue(self); 230227569Sphilip if (precision == UNDEFINED) { 231227569Sphilip return JSType.toString(x); 232227569Sphilip } 233227569Sphilip return (toPrecision(x, JSType.toInteger(precision))); 234227569Sphilip } 235227569Sphilip 236227569Sphilip /** 237227569Sphilip * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) specialized f 238227569Sphilip * 239227569Sphilip * @param self self reference 240227569Sphilip * @param precision use {@code precision - 1} digits after the significand's decimal point. 241227569Sphilip * 242227569Sphilip * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision} 243227569Sphilip */ 244227569Sphilip @SpecializedFunction 245227569Sphilip public static String toPrecision(final Object self, final int precision) { 246227569Sphilip return toPrecision(getNumberValue(self), precision); 247227569Sphilip } 248227569Sphilip 249227569Sphilip private static String toPrecision(final double x, final int p) { 250227569Sphilip if (Double.isNaN(x)) { 251227569Sphilip return "NaN"; 252227569Sphilip } else if (Double.isInfinite(x)) { 253227569Sphilip return x > 0? "Infinity" : "-Infinity"; 254227569Sphilip } 255227569Sphilip 256227569Sphilip if (p < 1 || p > 21) { 257227569Sphilip throw rangeError("invalid.precision"); 258227569Sphilip } 259227569Sphilip 260227569Sphilip // workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160 261227569Sphilip if (x == 0.0 && p <= 1) { 262227569Sphilip return "0"; 263227569Sphilip } 264227569Sphilip 265227569Sphilip return DoubleConversion.toPrecision(x, p); 266227569Sphilip } 267227569Sphilip 268227569Sphilip /** 269227569Sphilip * ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] ) 270227569Sphilip * 271227569Sphilip * @param self self reference 272227569Sphilip * @param radix radix to use for string conversion 273227569Sphilip * @return string representation of this Number in the given radix 274227569Sphilip */ 275227569Sphilip @Function(attributes = Attribute.NOT_ENUMERABLE) 276227569Sphilip public static String toString(final Object self, final Object radix) { 277227569Sphilip if (radix != UNDEFINED) { 278227569Sphilip final int intRadix = JSType.toInteger(radix); 279227569Sphilip if (intRadix != 10) { 280227569Sphilip if (intRadix < 2 || intRadix > 36) { 281227569Sphilip throw rangeError("invalid.radix"); 282227569Sphilip } 283227569Sphilip return JSType.toString(getNumberValue(self), intRadix); 284227569Sphilip } 285227569Sphilip } 286227569Sphilip 287227569Sphilip return JSType.toString(getNumberValue(self)); 288227569Sphilip } 289227569Sphilip 290227569Sphilip /** 291227569Sphilip * ECMA 15.7.4.3 Number.prototype.toLocaleString() 292227569Sphilip * 293227569Sphilip * @param self self reference 294227569Sphilip * @return localized string for this Number 295227569Sphilip */ 296227569Sphilip @Function(attributes = Attribute.NOT_ENUMERABLE) 297227569Sphilip public static String toLocaleString(final Object self) { 298227569Sphilip return JSType.toString(getNumberValue(self)); 299227569Sphilip } 300227569Sphilip 301227569Sphilip 302227569Sphilip /** 303227569Sphilip * ECMA 15.7.4.4 Number.prototype.valueOf ( ) 304227569Sphilip * 305227569Sphilip * @param self self reference 306227569Sphilip * @return number value for this Number 307227569Sphilip */ 308227569Sphilip @Function(attributes = Attribute.NOT_ENUMERABLE) 309227569Sphilip public static double valueOf(final Object self) { 310227569Sphilip return getNumberValue(self); 311227569Sphilip } 312227569Sphilip 313227569Sphilip /** 314227569Sphilip * Lookup the appropriate method for an invoke dynamic call. 315227569Sphilip * @param request The link request 316227569Sphilip * @param receiver receiver of call 317227569Sphilip * @return Link to be invoked at call site. 318227569Sphilip */ 319227569Sphilip public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) { 320227569Sphilip return PrimitiveLookup.lookupPrimitive(request, Number.class, new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER, PROTOFILTER); 321227569Sphilip } 322227569Sphilip 323227569Sphilip @SuppressWarnings("unused") 324227569Sphilip private static NativeNumber wrapFilter(final Object receiver) { 325227569Sphilip return new NativeNumber(((Number)receiver).doubleValue()); 326227569Sphilip } 327227569Sphilip 328227569Sphilip @SuppressWarnings("unused") 329227569Sphilip private static Object protoFilter(final Object object) { 330227569Sphilip return Global.instance().getNumberPrototype(); 331227569Sphilip } 332227569Sphilip 333227569Sphilip private static double getNumberValue(final Object self) { 334227569Sphilip if (self instanceof Number) { 335227569Sphilip return ((Number)self).doubleValue(); 336227569Sphilip } else if (self instanceof NativeNumber) { 337227569Sphilip return ((NativeNumber)self).getValue(); 338227569Sphilip } else if (self != null && self == Global.instance().getNumberPrototype()) { 339227569Sphilip return 0.0; 340227569Sphilip } else { 341227569Sphilip throw typeError("not.a.number", ScriptRuntime.safeToString(self)); 342227569Sphilip } 343227569Sphilip } 344227569Sphilip 345227569Sphilip // Exponent of Java "e" or "E" formatter is always 2 digits and zero 346227569Sphilip // padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain 347227569Sphilip // exact number of digits e+1, e+0, e+12 etc. Fix the exponent here. 348227569Sphilip // 349227569Sphilip // Additionally, if trimZeros is true, this cuts trailing zeros in the 350227569Sphilip // fraction part for calls to toExponential() with undefined fractionDigits 351227569Sphilip // argument. 352227569Sphilip private static String fixExponent(final String str, final boolean trimZeros) { 353227569Sphilip final int index = str.indexOf('e'); 354227569Sphilip if (index < 1) { 355227569Sphilip // no exponent, do nothing.. 356227569Sphilip return str; 357227569Sphilip } 358227569Sphilip 359227569Sphilip // check if character after e+ or e- is 0 360227569Sphilip final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2; 361227569Sphilip // check if there are any trailing zeroes we should remove 362227569Sphilip 363227569Sphilip int fractionOffset = index; 364227569Sphilip if (trimZeros) { 365227569Sphilip assert fractionOffset > 0; 366227569Sphilip char c = str.charAt(fractionOffset - 1); 367227569Sphilip while (fractionOffset > 1 && (c == '0' || c == '.')) { 368227569Sphilip c = str.charAt(--fractionOffset - 1); 369227569Sphilip } 370227569Sphilip 371227569Sphilip } 372227569Sphilip // if anything needs to be done compose a new string 373227569Sphilip if (fractionOffset < index || expPadding == 3) { 374227569Sphilip return str.substring(0, fractionOffset) 375227569Sphilip + str.substring(index, index + 2) 376227569Sphilip + str.substring(index + expPadding); 377227569Sphilip } 378227569Sphilip return str; 379227569Sphilip } 380227569Sphilip 381227569Sphilip private static MethodHandle findOwnMH(final String name, final MethodType type) { 382227569Sphilip return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type); 383227569Sphilip } 384227569Sphilip} 385227569Sphilip