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