NativeNumber.java revision 1551:f3b883bec2d0
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.math.RoundingMode;
37import java.text.NumberFormat;
38import java.util.Locale;
39import jdk.dynalink.linker.GuardedInvocation;
40import jdk.dynalink.linker.LinkRequest;
41import jdk.nashorn.internal.objects.annotations.Attribute;
42import jdk.nashorn.internal.objects.annotations.Constructor;
43import jdk.nashorn.internal.objects.annotations.Function;
44import jdk.nashorn.internal.objects.annotations.Property;
45import jdk.nashorn.internal.objects.annotations.ScriptClass;
46import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
47import jdk.nashorn.internal.objects.annotations.Where;
48import jdk.nashorn.internal.runtime.JSType;
49import jdk.nashorn.internal.runtime.PropertyMap;
50import jdk.nashorn.internal.runtime.ScriptObject;
51import jdk.nashorn.internal.runtime.ScriptRuntime;
52import jdk.nashorn.internal.runtime.doubleconv.DoubleConversion;
53import jdk.nashorn.internal.runtime.linker.PrimitiveLookup;
54
55/**
56 * ECMA 15.7 Number Objects.
57 *
58 */
59@ScriptClass("Number")
60public 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
92    private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) {
93        super(proto, map);
94        this.value = value;
95    }
96
97    NativeNumber(final double value, final Global global) {
98        this(value, global.getNumberPrototype(), $nasgenmap$);
99    }
100
101    private NativeNumber(final double value) {
102        this(value, Global.instance());
103    }
104
105
106    @Override
107    public String safeToString() {
108        return "[Number " + toString() + "]";
109    }
110
111    @Override
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     */
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     */
128    public double doubleValue() {
129        return value;
130    }
131
132    @Override
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)
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)
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
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        return DoubleConversion.toFixed(x, fractionDigits);
189    }
190
191    /**
192     * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits)
193     *
194     * @param self           self reference
195     * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number.
196     *
197     * @return number in decimal exponential notation
198     */
199    @Function(attributes = Attribute.NOT_ENUMERABLE)
200    public static String toExponential(final Object self, final Object fractionDigits) {
201        final double  x         = getNumberValue(self);
202        final boolean trimZeros = fractionDigits == UNDEFINED;
203        final int     f         = trimZeros ? 16 : JSType.toInteger(fractionDigits);
204
205        if (Double.isNaN(x)) {
206            return "NaN";
207        } else if (Double.isInfinite(x)) {
208            return x > 0? "Infinity" : "-Infinity";
209        }
210
211        if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) {
212            throw rangeError("invalid.fraction.digits", "toExponential");
213        }
214
215        final String res = String.format(Locale.US, "%1." + f + "e", x);
216        return fixExponent(res, trimZeros);
217    }
218
219    /**
220     * ECMA 15.7.4.7 Number.prototype.toPrecision (precision)
221     *
222     * @param self      self reference
223     * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link JSType#toString} if undefined
224     *
225     * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
226     */
227    @Function(attributes = Attribute.NOT_ENUMERABLE)
228    public static String toPrecision(final Object self, final Object precision) {
229        final double x = getNumberValue(self);
230        if (precision == UNDEFINED) {
231            return JSType.toString(x);
232        }
233        return (toPrecision(x, JSType.toInteger(precision)));
234    }
235
236    /**
237     * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) specialized f
238     *
239     * @param self      self reference
240     * @param precision use {@code precision - 1} digits after the significand's decimal point.
241     *
242     * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
243     */
244    @SpecializedFunction
245    public static String toPrecision(final Object self, final int precision) {
246        return toPrecision(getNumberValue(self), precision);
247    }
248
249    private static String toPrecision(final double x, final int p) {
250        if (Double.isNaN(x)) {
251            return "NaN";
252        } else if (Double.isInfinite(x)) {
253            return x > 0? "Infinity" : "-Infinity";
254        }
255
256        if (p < 1 || p > 21) {
257            throw rangeError("invalid.precision");
258        }
259
260        // workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160
261        if (x == 0.0 && p <= 1) {
262            return "0";
263        }
264
265        return DoubleConversion.toPrecision(x, p);
266    }
267
268    /**
269     * ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] )
270     *
271     * @param self  self reference
272     * @param radix radix to use for string conversion
273     * @return string representation of this Number in the given radix
274     */
275    @Function(attributes = Attribute.NOT_ENUMERABLE)
276    public static String toString(final Object self, final Object radix) {
277        if (radix != UNDEFINED) {
278            final int intRadix = JSType.toInteger(radix);
279            if (intRadix != 10) {
280                if (intRadix < 2 || intRadix > 36) {
281                    throw rangeError("invalid.radix");
282                }
283                return JSType.toString(getNumberValue(self), intRadix);
284            }
285        }
286
287        return JSType.toString(getNumberValue(self));
288    }
289
290    /**
291     * ECMA 15.7.4.3 Number.prototype.toLocaleString()
292     *
293     * @param self self reference
294     * @return localized string for this Number
295     */
296    @Function(attributes = Attribute.NOT_ENUMERABLE)
297    public static String toLocaleString(final Object self) {
298        return JSType.toString(getNumberValue(self));
299    }
300
301
302    /**
303     * ECMA 15.7.4.4 Number.prototype.valueOf ( )
304     *
305     * @param self self reference
306     * @return number value for this Number
307     */
308    @Function(attributes = Attribute.NOT_ENUMERABLE)
309    public static double valueOf(final Object self) {
310        return getNumberValue(self);
311    }
312
313    /**
314     * Lookup the appropriate method for an invoke dynamic call.
315     * @param request  The link request
316     * @param receiver receiver of call
317     * @return Link to be invoked at call site.
318     */
319    public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) {
320        return PrimitiveLookup.lookupPrimitive(request, Number.class, new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER, PROTOFILTER);
321    }
322
323    @SuppressWarnings("unused")
324    private static NativeNumber wrapFilter(final Object receiver) {
325        return new NativeNumber(((Number)receiver).doubleValue());
326    }
327
328    @SuppressWarnings("unused")
329    private static Object protoFilter(final Object object) {
330        return Global.instance().getNumberPrototype();
331    }
332
333    private static double getNumberValue(final Object self) {
334        if (self instanceof Number) {
335            return ((Number)self).doubleValue();
336        } else if (self instanceof NativeNumber) {
337            return ((NativeNumber)self).getValue();
338        } else if (self != null && self == Global.instance().getNumberPrototype()) {
339            return 0.0;
340        } else {
341            throw typeError("not.a.number", ScriptRuntime.safeToString(self));
342        }
343    }
344
345    // Exponent of Java "e" or "E" formatter is always 2 digits and zero
346    // padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain
347    // exact number of digits e+1, e+0, e+12 etc. Fix the exponent here.
348    //
349    // Additionally, if trimZeros is true, this cuts trailing zeros in the
350    // fraction part for calls to toExponential() with undefined fractionDigits
351    // argument.
352    private static String fixExponent(final String str, final boolean trimZeros) {
353        final int index = str.indexOf('e');
354        if (index < 1) {
355            // no exponent, do nothing..
356            return str;
357        }
358
359        // check if character after e+ or e- is 0
360        final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2;
361        // check if there are any trailing zeroes we should remove
362
363        int fractionOffset = index;
364        if (trimZeros) {
365            assert fractionOffset > 0;
366            char c = str.charAt(fractionOffset - 1);
367            while (fractionOffset > 1 && (c == '0' || c == '.')) {
368                c = str.charAt(--fractionOffset - 1);
369            }
370
371        }
372        // if anything needs to be done compose a new string
373        if (fractionOffset < index || expPadding == 3) {
374            return str.substring(0, fractionOffset)
375                    + str.substring(index, index + 2)
376                    + str.substring(index + expPadding);
377        }
378        return str;
379    }
380
381    private static MethodHandle findOwnMH(final String name, final MethodType type) {
382        return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type);
383    }
384}
385