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.runtime.ECMAErrors.rangeError;
29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt;
31 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsLong;
32 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
33 import static jdk.nashorn.internal.lookup.Lookup.MH;
34
35 import java.lang.invoke.MethodHandle;
36 import java.lang.invoke.MethodHandles;
37 import java.lang.invoke.MethodType;
38 import java.text.NumberFormat;
39 import java.util.Locale;
40 import jdk.internal.dynalink.linker.GuardedInvocation;
41 import jdk.internal.dynalink.linker.LinkRequest;
42 import jdk.nashorn.internal.objects.annotations.Attribute;
43 import jdk.nashorn.internal.objects.annotations.Constructor;
44 import jdk.nashorn.internal.objects.annotations.Function;
45 import jdk.nashorn.internal.objects.annotations.Property;
46 import jdk.nashorn.internal.objects.annotations.ScriptClass;
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.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 private 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 private final boolean isInt;
88 private final boolean isLong;
89
90 // initialized by nasgen
91 private static PropertyMap $nasgenmap$;
92
93 private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) {
94 super(proto, map);
95 this.value = value;
96 this.isInt = isRepresentableAsInt(value);
97 this.isLong = isRepresentableAsLong(value);
98 }
99
100 NativeNumber(final double value, final Global global) {
101 this(value, global.getNumberPrototype(), $nasgenmap$);
102 }
103
104 private NativeNumber(final double value) {
105 this(value, Global.instance());
106 }
107
108
109 @Override
110 public String safeToString() {
111 return "[Number " + toString() + "]";
112 }
113
114 @Override
115 public String toString() {
116 return Double.toString(getValue());
117 }
118
119 /**
120 * Get the value of this Number
121 * @return a {@code double} representing the Number value
122 */
123 public double getValue() {
124 return doubleValue();
125 }
126
127 /**
128 * Get the value of this Number
129 * @return a {@code double} representing the Number value
130 */
131 public double doubleValue() {
132 return value;
133 }
134
135 /**
136 * Get the value of this Number as a {@code int}
137 * @return an {@code int} representing the Number value
138 * @throws ClassCastException If number is not representable as an {@code int}
139 */
140 public int intValue() throws ClassCastException {
141 if (isInt) {
142 return (int)value;
143 }
144 throw new ClassCastException();
145 }
146
147 /**
148 * Get the value of this Number as a {@code long}
149 * @return a {@code long} representing the Number value
150 * @throws ClassCastException If number is not representable as an {@code long}
151 */
152 public long longValue() throws ClassCastException {
153 if (isLong) {
154 return (long)value;
155 }
156 throw new ClassCastException();
157 }
158
159 @Override
160 public String getClassName() {
161 return "Number";
162 }
163
164 /**
165 * ECMA 15.7.2 - The Number constructor
166 *
167 * @param newObj is this Number instantiated with the new operator
168 * @param self self reference
169 * @param args value of number
170 * @return the Number instance (internally represented as a {@code NativeNumber})
171 */
172 @Constructor(arity = 1)
173 public static Object constructor(final boolean newObj, final Object self, final Object... args) {
174 final double num = (args.length > 0) ? JSType.toNumber(args[0]) : 0.0;
175
176 return newObj? new NativeNumber(num) : num;
177 }
178
179 /**
180 * ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits)
181 *
182 * @param self self reference
183 * @param fractionDigits how many digits should be after the decimal point, 0 if undefined
184 *
185 * @return number in decimal fixed point notation
186 */
187 @Function(attributes = Attribute.NOT_ENUMERABLE)
188 public static Object toFixed(final Object self, final Object fractionDigits) {
189 final int f = JSType.toInteger(fractionDigits);
190 if (f < 0 || f > 20) {
191 throw rangeError("invalid.fraction.digits", "toFixed");
192 }
193
194 final double x = getNumberValue(self);
195 if (Double.isNaN(x)) {
196 return "NaN";
197 }
198
199 if (Math.abs(x) >= 1e21) {
200 return JSType.toString(x);
201 }
202
203 final NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
204 format.setMinimumFractionDigits(f);
205 format.setMaximumFractionDigits(f);
206 format.setGroupingUsed(false);
207
208 return format.format(x);
209 }
210
211 /**
212 * ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits)
213 *
214 * @param self self reference
215 * @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number.
216 *
217 * @return number in decimal exponential notation
218 */
219 @Function(attributes = Attribute.NOT_ENUMERABLE)
220 public static Object toExponential(final Object self, final Object fractionDigits) {
221 final double x = getNumberValue(self);
222 final boolean trimZeros = fractionDigits == UNDEFINED;
223 final int f = trimZeros ? 16 : JSType.toInteger(fractionDigits);
224
225 if (Double.isNaN(x)) {
226 return "NaN";
227 } else if (Double.isInfinite(x)) {
228 return x > 0? "Infinity" : "-Infinity";
229 }
230
231 if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) {
232 throw rangeError("invalid.fraction.digits", "toExponential");
233 }
234
235 final String res = String.format(Locale.US, "%1." + f + "e", x);
236 return fixExponent(res, trimZeros);
237 }
238
239 /**
240 * ECMA 15.7.4.7 Number.prototype.toPrecision (precision)
241 *
242 * @param self self reference
243 * @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link NativeDate#toString} if undefined
244 *
245 * @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
246 */
247 @Function(attributes = Attribute.NOT_ENUMERABLE)
248 public static Object toPrecision(final Object self, final Object precision) {
249 final double x = getNumberValue(self);
250 if (precision == UNDEFINED) {
251 return JSType.toString(x);
252 }
253
254 final int p = JSType.toInteger(precision);
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 Object 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 Object 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 boxed number value for this Number
312 */
313 @Function(attributes = Attribute.NOT_ENUMERABLE)
314 public static Object 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 }
--- EOF ---