blob: b72080b858e1d894ad8a9192cdb212ec5daca0eb [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.objects;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.nashorn.internal.objects.annotations.Attribute;
import jdk.nashorn.internal.objects.annotations.Constructor;
import jdk.nashorn.internal.objects.annotations.Function;
import jdk.nashorn.internal.objects.annotations.Property;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
import jdk.nashorn.internal.objects.annotations.Where;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.linker.NashornGuards;
import jdk.nashorn.internal.runtime.linker.PrimitiveLookup;
/**
* ECMA 15.7 Number Objects.
*
*/
@ScriptClass("Number")
public final class NativeNumber extends ScriptObject {
/** Method handle to create an object wrapper for a primitive number. */
static final MethodHandle WRAPFILTER = findOwnMH("wrapFilter", MH.type(NativeNumber.class, Object.class));
/** Method handle to retrieve the Number prototype object. */
private static final MethodHandle PROTOFILTER = findOwnMH("protoFilter", MH.type(Object.class, Object.class));
/** ECMA 15.7.3.2 largest positive finite value */
@Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR)
public static final double MAX_VALUE = Double.MAX_VALUE;
/** ECMA 15.7.3.3 smallest positive finite value */
@Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR)
public static final double MIN_VALUE = Double.MIN_VALUE;
/** ECMA 15.7.3.4 NaN */
@Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR)
public static final double NaN = Double.NaN;
/** ECMA 15.7.3.5 negative infinity */
@Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR)
public static final double NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY;
/** ECMA 15.7.3.5 positive infinity */
@Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT, where = Where.CONSTRUCTOR)
public static final double POSITIVE_INFINITY = Double.POSITIVE_INFINITY;
private final double value;
// initialized by nasgen
private static PropertyMap $nasgenmap$;
private NativeNumber(final double value, final ScriptObject proto, final PropertyMap map) {
super(proto, map);
this.value = value;
}
NativeNumber(final double value, final Global global) {
this(value, global.getNumberPrototype(), $nasgenmap$);
}
private NativeNumber(final double value) {
this(value, Global.instance());
}
@Override
public String safeToString() {
return "[Number " + toString() + "]";
}
@Override
public String toString() {
return Double.toString(getValue());
}
/**
* Get the value of this Number
* @return a {@code double} representing the Number value
*/
public double getValue() {
return doubleValue();
}
/**
* Get the value of this Number
* @return a {@code double} representing the Number value
*/
public double doubleValue() {
return value;
}
@Override
public String getClassName() {
return "Number";
}
/**
* ECMA 15.7.2 - The Number constructor
*
* @param newObj is this Number instantiated with the new operator
* @param self self reference
* @param args value of number
* @return the Number instance (internally represented as a {@code NativeNumber})
*/
@Constructor(arity = 1)
public static Object constructor(final boolean newObj, final Object self, final Object... args) {
final double num = (args.length > 0) ? JSType.toNumber(args[0]) : 0.0;
return newObj? new NativeNumber(num) : num;
}
/**
* ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits)
*
* @param self self reference
* @param fractionDigits how many digits should be after the decimal point, 0 if undefined
*
* @return number in decimal fixed point notation
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toFixed(final Object self, final Object fractionDigits) {
return toFixed(self, JSType.toInteger(fractionDigits));
}
/**
* ECMA 15.7.4.5 Number.prototype.toFixed (fractionDigits) specialized for int fractionDigits
*
* @param self self reference
* @param fractionDigits how many digits should be after the decimal point, 0 if undefined
*
* @return number in decimal fixed point notation
*/
@SpecializedFunction
public static String toFixed(final Object self, final int fractionDigits) {
if (fractionDigits < 0 || fractionDigits > 20) {
throw rangeError("invalid.fraction.digits", "toFixed");
}
final double x = getNumberValue(self);
if (Double.isNaN(x)) {
return "NaN";
}
if (Math.abs(x) >= 1e21) {
return JSType.toString(x);
}
final NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
format.setMinimumFractionDigits(fractionDigits);
format.setMaximumFractionDigits(fractionDigits);
format.setGroupingUsed(false);
format.setRoundingMode(RoundingMode.HALF_UP);
return format.format(x);
}
/**
* ECMA 15.7.4.6 Number.prototype.toExponential (fractionDigits)
*
* @param self self reference
* @param fractionDigits how many digital should be after the significand's decimal point. If undefined, use as many as necessary to uniquely specify number.
*
* @return number in decimal exponential notation
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toExponential(final Object self, final Object fractionDigits) {
final double x = getNumberValue(self);
final boolean trimZeros = fractionDigits == UNDEFINED;
final int f = trimZeros ? 16 : JSType.toInteger(fractionDigits);
if (Double.isNaN(x)) {
return "NaN";
} else if (Double.isInfinite(x)) {
return x > 0? "Infinity" : "-Infinity";
}
if (fractionDigits != UNDEFINED && (f < 0 || f > 20)) {
throw rangeError("invalid.fraction.digits", "toExponential");
}
final String res = String.format(Locale.US, "%1." + f + "e", x);
return fixExponent(res, trimZeros);
}
/**
* ECMA 15.7.4.7 Number.prototype.toPrecision (precision)
*
* @param self self reference
* @param precision use {@code precision - 1} digits after the significand's decimal point or call {@link JSType#toString} if undefined
*
* @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toPrecision(final Object self, final Object precision) {
final double x = getNumberValue(self);
if (precision == UNDEFINED) {
return JSType.toString(x);
}
return (toPrecision(x, JSType.toInteger(precision)));
}
/**
* ECMA 15.7.4.7 Number.prototype.toPrecision (precision) specialized f
*
* @param self self reference
* @param precision use {@code precision - 1} digits after the significand's decimal point.
*
* @return number in decimal exponentiation notation or decimal fixed notation depending on {@code precision}
*/
@SpecializedFunction
public static String toPrecision(final Object self, final int precision) {
return toPrecision(getNumberValue(self), precision);
}
private static String toPrecision(final double x, final int p) {
if (Double.isNaN(x)) {
return "NaN";
} else if (Double.isInfinite(x)) {
return x > 0? "Infinity" : "-Infinity";
}
if (p < 1 || p > 21) {
throw rangeError("invalid.precision");
}
// workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6469160
if (x == 0.0 && p <= 1) {
return "0";
}
return fixExponent(String.format(Locale.US, "%." + p + "g", x), false);
}
/**
* ECMA 15.7.4.2 Number.prototype.toString ( [ radix ] )
*
* @param self self reference
* @param radix radix to use for string conversion
* @return string representation of this Number in the given radix
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toString(final Object self, final Object radix) {
if (radix != UNDEFINED) {
final int intRadix = JSType.toInteger(radix);
if (intRadix != 10) {
if (intRadix < 2 || intRadix > 36) {
throw rangeError("invalid.radix");
}
return JSType.toString(getNumberValue(self), intRadix);
}
}
return JSType.toString(getNumberValue(self));
}
/**
* ECMA 15.7.4.3 Number.prototype.toLocaleString()
*
* @param self self reference
* @return localized string for this Number
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static String toLocaleString(final Object self) {
return JSType.toString(getNumberValue(self));
}
/**
* ECMA 15.7.4.4 Number.prototype.valueOf ( )
*
* @param self self reference
* @return number value for this Number
*/
@Function(attributes = Attribute.NOT_ENUMERABLE)
public static double valueOf(final Object self) {
return getNumberValue(self);
}
/**
* Lookup the appropriate method for an invoke dynamic call.
* @param request The link request
* @param receiver receiver of call
* @return Link to be invoked at call site.
*/
public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) {
return PrimitiveLookup.lookupPrimitive(request, NashornGuards.getNumberGuard(), new NativeNumber(((Number)receiver).doubleValue()), WRAPFILTER, PROTOFILTER);
}
@SuppressWarnings("unused")
private static NativeNumber wrapFilter(final Object receiver) {
return new NativeNumber(((Number)receiver).doubleValue());
}
@SuppressWarnings("unused")
private static Object protoFilter(final Object object) {
return Global.instance().getNumberPrototype();
}
private static double getNumberValue(final Object self) {
if (self instanceof Number) {
return ((Number)self).doubleValue();
} else if (self instanceof NativeNumber) {
return ((NativeNumber)self).getValue();
} else if (self != null && self == Global.instance().getNumberPrototype()) {
return 0.0;
} else {
throw typeError("not.a.number", ScriptRuntime.safeToString(self));
}
}
// Exponent of Java "e" or "E" formatter is always 2 digits and zero
// padded if needed (e+01, e+00, e+12 etc.) JS expects exponent to contain
// exact number of digits e+1, e+0, e+12 etc. Fix the exponent here.
//
// Additionally, if trimZeros is true, this cuts trailing zeros in the
// fraction part for calls to toExponential() with undefined fractionDigits
// argument.
private static String fixExponent(final String str, final boolean trimZeros) {
final int index = str.indexOf('e');
if (index < 1) {
// no exponent, do nothing..
return str;
}
// check if character after e+ or e- is 0
final int expPadding = str.charAt(index + 2) == '0' ? 3 : 2;
// check if there are any trailing zeroes we should remove
int fractionOffset = index;
if (trimZeros) {
assert fractionOffset > 0;
char c = str.charAt(fractionOffset - 1);
while (fractionOffset > 1 && (c == '0' || c == '.')) {
c = str.charAt(--fractionOffset - 1);
}
}
// if anything needs to be done compose a new string
if (fractionOffset < index || expPadding == 3) {
return str.substring(0, fractionOffset)
+ str.substring(index, index + 2)
+ str.substring(index + expPadding);
}
return str;
}
private static MethodHandle findOwnMH(final String name, final MethodType type) {
return MH.findStatic(MethodHandles.lookup(), NativeNumber.class, name, type);
}
}