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