NativeNumber.java revision 1417:97bb1c7b0f50
1178361Ssam/*
2178361Ssam * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3178361Ssam * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4178361Ssam *
5178361Ssam * This code is free software; you can redistribute it and/or modify it
6178361Ssam * under the terms of the GNU General Public License version 2 only, as
7178361Ssam * published by the Free Software Foundation.  Oracle designates this
8178361Ssam * particular file as subject to the "Classpath" exception as provided
9178361Ssam * by Oracle in the LICENSE file that accompanied this code.
10178361Ssam *
11178361Ssam * This code is distributed in the hope that it will be useful, but WITHOUT
12178361Ssam * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13178361Ssam * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14178361Ssam * version 2 for more details (a copy is included in the LICENSE file that
15178361Ssam * accompanied this code).
16178361Ssam *
17178361Ssam * You should have received a copy of the GNU General Public License version
18178361Ssam * 2 along with this work; if not, write to the Free Software Foundation,
19178361Ssam * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20178361Ssam *
21178361Ssam * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22178361Ssam * or visit www.oracle.com if you need additional information or have any
23178361Ssam * questions.
24178361Ssam */
25178361Ssam
26178361Ssampackage jdk.nashorn.internal.objects;
27178361Ssam
28178361Ssamimport static jdk.nashorn.internal.lookup.Lookup.MH;
29178361Ssamimport static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
30178361Ssamimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
31178361Ssamimport static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
32178361Ssam
33178361Ssamimport java.lang.invoke.MethodHandle;
34178361Ssamimport java.lang.invoke.MethodHandles;
35178361Ssamimport java.lang.invoke.MethodType;
36178361Ssamimport java.math.RoundingMode;
37178361Ssamimport java.text.NumberFormat;
38178361Ssamimport java.util.Locale;
39178361Ssamimport jdk.internal.dynalink.linker.GuardedInvocation;
40178361Ssamimport jdk.internal.dynalink.linker.LinkRequest;
41178361Ssamimport jdk.nashorn.internal.objects.annotations.Attribute;
42178361Ssamimport jdk.nashorn.internal.objects.annotations.Constructor;
43178361Ssamimport jdk.nashorn.internal.objects.annotations.Function;
44178361Ssamimport jdk.nashorn.internal.objects.annotations.Property;
45178361Ssamimport jdk.nashorn.internal.objects.annotations.ScriptClass;
46178361Ssamimport jdk.nashorn.internal.objects.annotations.SpecializedFunction;
47178361Ssamimport jdk.nashorn.internal.objects.annotations.Where;
48178361Ssamimport jdk.nashorn.internal.runtime.JSType;
49178361Ssamimport jdk.nashorn.internal.runtime.PropertyMap;
50178361Ssamimport jdk.nashorn.internal.runtime.ScriptObject;
51178361Ssamimport jdk.nashorn.internal.runtime.ScriptRuntime;
52178361Ssamimport jdk.nashorn.internal.runtime.linker.PrimitiveLookup;
53178361Ssam
54/**
55 * ECMA 15.7 Number Objects.
56 *
57 */
58@ScriptClass("Number")
59public 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
91    private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) {
92        super(proto, map);
93        this.value = value;
94    }
95
96    NativeNumber(final double value, final Global global) {
97        this(value, global.getNumberPrototype(), $nasgenmap$);
98    }
99
100    private NativeNumber(final double value) {
101        this(value, Global.instance());
102    }
103
104
105    @Override
106    public String safeToString() {
107        return "[Number " + toString() + "]";
108    }
109
110    @Override
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     */
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     */
127    public double doubleValue() {
128        return value;
129    }
130
131    @Override
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)
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)
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
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        final NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
188        format.setMinimumFractionDigits(fractionDigits);
189        format.setMaximumFractionDigits(fractionDigits);
190        format.setGroupingUsed(false);
191        format.setRoundingMode(RoundingMode.HALF_UP);
192
193        return format.format(x);
194    }
195
196    /**
197     * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits)
198     *
199     * @param self           self reference
200     * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number.
201     *
202     * @return number in decimal exponential notation
203     */
204    @Function(attributes = Attribute.NOT_ENUMERABLE)
205    public static String toExponential(final Object self, final Object fractionDigits) {
206        final double  x         = getNumberValue(self);
207        final boolean trimZeros = fractionDigits == UNDEFINED;
208        final int     f         = trimZeros ? 16 : JSType.toInteger(fractionDigits);
209
210        if (Double.isNaN(x)) {
211            return "NaN";
212        } else if (Double.isInfinite(x)) {
213            return x > 0? "Infinity" : "-Infinity";
214        }
215
216        if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) {
217            throw rangeError("invalid.fraction.digits", "toExponential");
218        }
219
220        final String res = String.format(Locale.US, "%1." + f + "e", x);
221        return fixExponent(res, trimZeros);
222    }
223
224    /**
225     * ECMA 15.7.4.7 Number.prototype.toPrecision (precision)
226     *
227     * @param self      self reference
228     * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link JSType#toString} if undefined
229     *
230     * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
231     */
232    @Function(attributes = Attribute.NOT_ENUMERABLE)
233    public static String toPrecision(final Object self, final Object precision) {
234        final double x = getNumberValue(self);
235        if (precision == UNDEFINED) {
236            return JSType.toString(x);
237        }
238        return (toPrecision(x, JSType.toInteger(precision)));
239    }
240
241    /**
242     * ECMA 15.7.4.7 Number.prototype.toPrecision (precision) specialized f
243     *
244     * @param self      self reference
245     * @param precision use {@code precision - 1} digits after the significand's decimal point.
246     *
247     * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
248     */
249    @SpecializedFunction
250    public static String toPrecision(final Object self, final int precision) {
251        return toPrecision(getNumberValue(self), precision);
252    }
253
254    private static String toPrecision(final double x, final int p) {
255        if (Double.isNaN(x)) {
256            return "NaN";
257        } else if (Double.isInfinite(x)) {
258            return x > 0? "Infinity" : "-Infinity";
259        }
260
261        if (p < 1 || p > 21) {
262            throw rangeError("invalid.precision");
263        }
264
265        // workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160
266        if (x == 0.0 && p <= 1) {
267            return "0";
268        }
269
270        return fixExponent(String.format(Locale.US, "%." + p + "g", x), false);
271    }
272
273    /**
274     * ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] )
275     *
276     * @param self  self reference
277     * @param radix radix to use for string conversion
278     * @return string representation of this Number in the given radix
279     */
280    @Function(attributes = Attribute.NOT_ENUMERABLE)
281    public static String toString(final Object self, final Object radix) {
282        if (radix != UNDEFINED) {
283            final int intRadix = JSType.toInteger(radix);
284            if (intRadix != 10) {
285                if (intRadix < 2 || intRadix > 36) {
286                    throw rangeError("invalid.radix");
287                }
288                return JSType.toString(getNumberValue(self), intRadix);
289            }
290        }
291
292        return JSType.toString(getNumberValue(self));
293    }
294
295    /**
296     * ECMA 15.7.4.3 Number.prototype.toLocaleString()
297     *
298     * @param self self reference
299     * @return localized string for this Number
300     */
301    @Function(attributes = Attribute.NOT_ENUMERABLE)
302    public static String toLocaleString(final Object self) {
303        return JSType.toString(getNumberValue(self));
304    }
305
306
307    /**
308     * ECMA 15.7.4.4 Number.prototype.valueOf ( )
309     *
310     * @param self self reference
311     * @return number value for this Number
312     */
313    @Function(attributes = Attribute.NOT_ENUMERABLE)
314    public static double valueOf(final Object self) {
315        return getNumberValue(self);
316    }
317
318    /**
319     * Lookup the appropriate method for an invoke dynamic call.
320     * @param request  The link request
321     * @param receiver receiver of call
322     * @return Link to be invoked at call site.
323     */
324    public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) {
325        return PrimitiveLookup.lookupPrimitive(request, Number.class, new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER, PROTOFILTER);
326    }
327
328    @SuppressWarnings("unused")
329    private static NativeNumber wrapFilter(final Object receiver) {
330        return new NativeNumber(((Number)receiver).doubleValue());
331    }
332
333    @SuppressWarnings("unused")
334    private static Object protoFilter(final Object object) {
335        return Global.instance().getNumberPrototype();
336    }
337
338    private static double getNumberValue(final Object self) {
339        if (self instanceof Number) {
340            return ((Number)self).doubleValue();
341        } else if (self instanceof NativeNumber) {
342            return ((NativeNumber)self).getValue();
343        } else if (self != null && self == Global.instance().getNumberPrototype()) {
344            return 0.0;
345        } else {
346            throw typeError("not.a.number", ScriptRuntime.safeToString(self));
347        }
348    }
349
350    // Exponent of Java "e" or "E" formatter is always 2 digits and zero
351    // padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain
352    // exact number of digits e+1, e+0, e+12 etc. Fix the exponent here.
353    //
354    // Additionally, if trimZeros is true, this cuts trailing zeros in the
355    // fraction part for calls to toExponential() with undefined fractionDigits
356    // argument.
357    private static String fixExponent(final String str, final boolean trimZeros) {
358        final int index = str.indexOf('e');
359        if (index < 1) {
360            // no exponent, do nothing..
361            return str;
362        }
363
364        // check if character after e+ or e- is 0
365        final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2;
366        // check if there are any trailing zeroes we should remove
367
368        int fractionOffset = index;
369        if (trimZeros) {
370            assert fractionOffset > 0;
371            char c = str.charAt(fractionOffset - 1);
372            while (fractionOffset > 1 && (c == '0' || c == '.')) {
373                c = str.charAt(--fractionOffset - 1);
374            }
375
376        }
377        // if anything needs to be done compose a new string
378        if (fractionOffset < index || expPadding == 3) {
379            return str.substring(0, fractionOffset)
380                    + str.substring(index, index + 2)
381                    + str.substring(index + expPadding);
382        }
383        return str;
384    }
385
386    private static MethodHandle findOwnMH(final String name, final MethodType type) {
387        return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type);
388    }
389}
390