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 
NativeNumber(final double value, final ScriptObject proto, final PropertyMap map)91     private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) {
92         super(proto, map);
93         this.value = value;
94     }
95 
NativeNumber(final double value, final Global global)96     NativeNumber(final double value, final Global global) {
97         this(value, global.getNumberPrototype(), $nasgenmap$);
98     }
99 
NativeNumber(final double value)100     private NativeNumber(final double value) {
101         this(value, Global.instance());
102     }
103 
104 
105     @Override
safeToString()106     public String safeToString() {
107         return "[Number " + toString() + "]";
108     }
109 
110     @Override
toString()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      */
getValue()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      */
doubleValue()127     public double doubleValue() {
128         return value;
129     }
130 
131     @Override
getClassName()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)
constructor(final boolean newObj, final Object self, final Object... args)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)
toFixed(final Object self, final Object fractionDigits)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
toFixed(final Object self, final int fractionDigits)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)
toExponential(final Object self, final Object fractionDigits)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)
toPrecision(final Object self, final Object precision)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
toPrecision(final Object self, final int precision)244     public static String toPrecision(final Object self, final int precision) {
245         return toPrecision(getNumberValue(self), precision);
246     }
247 
toPrecision(final double x, final int p)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)
toString(final Object self, final Object radix)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)
toLocaleString(final Object self)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)
valueOf(final Object self)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      */
lookupPrimitive(final LinkRequest request, final Object receiver)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")
wrapFilter(final Object receiver)323     private static NativeNumber wrapFilter(final Object receiver) {
324         return new NativeNumber(((Number)receiver).doubleValue());
325     }
326 
327     @SuppressWarnings("unused")
protoFilter(final Object object)328     private static Object protoFilter(final Object object) {
329         return Global.instance().getNumberPrototype();
330     }
331 
getNumberValue(final Object self)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.
fixExponent(final String str, final boolean trimZeros)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 
findOwnMH(final String name, final MethodType type)380     private static MethodHandle findOwnMH(final String name, final MethodType type) {
381         return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type);
382     }
383 }
384