NativeNumber.java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
/**
* This class implements the Number native object.
*
* <p>See ECMA 15.7.
*
* @author Norris Boyd
*/
final class NativeNumber extends ScriptableObject {
private static final long serialVersionUID = 3504516769741512101L;
/**
* @see <a
* href="https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer">20.1.2.6
* Number.MAX_SAFE_INTEGER</a>
*/
public static final double MAX_SAFE_INTEGER = 9007199254740991.0; // Math.pow(2, 53) - 1
private static final String CLASS_NAME = "Number";
private static final int MAX_PRECISION = 100;
private static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
private static final double EPSILON = 2.220446049250313e-16;
private final double doubleValue;
static void init(Scriptable scope, boolean sealed) {
LambdaConstructor constructor =
new LambdaConstructor(
scope,
CLASS_NAME,
1,
NativeNumber::js_constructorFunc,
NativeNumber::js_constructor);
constructor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
constructor.setPrototypeScriptable(new NativeNumber(0.0));
final int propAttr = DONTENUM | PERMANENT | READONLY;
constructor.defineProperty("NaN", ScriptRuntime.NaNobj, propAttr);
constructor.defineProperty(
"POSITIVE_INFINITY", ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY), propAttr);
constructor.defineProperty(
"NEGATIVE_INFINITY", ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY), propAttr);
constructor.defineProperty(
"MAX_VALUE", ScriptRuntime.wrapNumber(Double.MAX_VALUE), propAttr);
constructor.defineProperty(
"MIN_VALUE", ScriptRuntime.wrapNumber(Double.MIN_VALUE), propAttr);
constructor.defineProperty(
"MAX_SAFE_INTEGER", ScriptRuntime.wrapNumber(MAX_SAFE_INTEGER), propAttr);
constructor.defineProperty(
"MIN_SAFE_INTEGER", ScriptRuntime.wrapNumber(MIN_SAFE_INTEGER), propAttr);
constructor.defineProperty("EPSILON", ScriptRuntime.wrapNumber(EPSILON), propAttr);
constructor.defineConstructorMethod(
scope, "isFinite", 1, NativeNumber::js_isFinite, DONTENUM, DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope, "isNaN", 1, NativeNumber::js_isNaN, DONTENUM, DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope, "isInteger", 1, NativeNumber::js_isInteger, DONTENUM, DONTENUM | READONLY);
constructor.defineConstructorMethod(
scope,
"isSafeInteger",
1,
NativeNumber::js_isSafeInteger,
DONTENUM,
DONTENUM | READONLY);
Object parseFloat = ScriptRuntime.getTopLevelProp(constructor, "parseFloat");
if (parseFloat instanceof Function) {
constructor.defineProperty("parseFloat", parseFloat, DONTENUM);
}
Object parseInt = ScriptRuntime.getTopLevelProp(constructor, "parseInt");
if (parseInt instanceof Function) {
constructor.defineProperty("parseInt", parseInt, DONTENUM);
}
constructor.definePrototypeMethod(
scope, "toString", 1, NativeNumber::js_toString, DONTENUM, DONTENUM | READONLY);
// Alias toLocaleString to toString
constructor.definePrototypeMethod(
scope,
"toLocaleString",
0,
NativeNumber::js_toString,
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope, "toSource", 0, NativeNumber::js_toSource, DONTENUM, DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope,
"valueOf",
0,
(Context lcx, Scriptable lscope, Scriptable thisObj, Object[] args) ->
toSelf(thisObj).doubleValue,
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope, "toFixed", 1, NativeNumber::js_toFixed, DONTENUM, DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope,
"toExponential",
1,
NativeNumber::js_toExponential,
DONTENUM,
DONTENUM | READONLY);
constructor.definePrototypeMethod(
scope,
"toPrecision",
1,
NativeNumber::js_toPrecision,
DONTENUM,
DONTENUM | READONLY);
ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, DONTENUM);
if (sealed) {
constructor.sealObject();
((ScriptableObject) constructor.getPrototypeProperty()).sealObject();
}
}
NativeNumber(double number) {
doubleValue = number;
}
@Override
public String getClassName() {
return CLASS_NAME;
}
private static Scriptable js_constructor(Context cx, Scriptable scope, Object[] args) {
double val = (args.length > 0) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0;
return new NativeNumber(val);
}
private static Object js_constructorFunc(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return (args.length > 0) ? ScriptRuntime.toNumeric(args[0]).doubleValue() : 0.0;
}
private static Object js_toFixed(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
int precisionMin = cx.version < Context.VERSION_ES6 ? -20 : 0;
double value = toSelf(thisObj).doubleValue;
return num_to(value, args, DToA.DTOSTR_FIXED, DToA.DTOSTR_FIXED, precisionMin, 0);
}
private static Object js_toExponential(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
double value = toSelf(thisObj).doubleValue;
// Handle special values before range check
if (Double.isNaN(value)) {
return "NaN";
}
if (Double.isInfinite(value)) {
if (value >= 0) {
return "Infinity";
}
return "-Infinity";
}
// General case
return num_to(value, args, DToA.DTOSTR_STANDARD_EXPONENTIAL, DToA.DTOSTR_EXPONENTIAL, 0, 1);
}
private static Object js_toPrecision(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
double value = toSelf(thisObj).doubleValue;
// Undefined precision, fall back to ToString()
if (args.length == 0 || Undefined.isUndefined(args[0])) {
return ScriptRuntime.numberToString(value, 10);
}
// Handle special values before range check
if (Double.isNaN(value)) {
return "NaN";
}
if (Double.isInfinite(value)) {
if (value >= 0) {
return "Infinity";
}
return "-Infinity";
}
return num_to(value, args, DToA.DTOSTR_STANDARD, DToA.DTOSTR_PRECISION, 1, 0);
}
private static NativeNumber toSelf(Scriptable thisObj) {
return LambdaConstructor.convertThisObject(thisObj, NativeNumber.class);
}
private static Object js_toString(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
int base =
(args.length == 0 || Undefined.isUndefined(args[0]))
? 10
: ScriptRuntime.toInt32(args[0]);
return ScriptRuntime.numberToString(toSelf(thisObj).doubleValue, base);
}
private static Object js_toSource(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return "(new Number(" + ScriptRuntime.toString(toSelf(thisObj).doubleValue) + "))";
}
private static Number argToNumber(Object[] args) {
if (args.length > 0) {
if (args[0] instanceof Number) {
return (Number) args[0];
}
}
return null;
}
@Override
public String toString() {
return ScriptRuntime.numberToString(doubleValue, 10);
}
private static String num_to(
double val,
Object[] args,
int zeroArgMode,
int oneArgMode,
int precisionMin,
int precisionOffset) {
int precision;
if (args.length == 0) {
precision = 0;
oneArgMode = zeroArgMode;
} else {
/* We allow a larger range of precision than
ECMA requires; this is permitted by ECMA. */
double p = ScriptRuntime.toInteger(args[0]);
if (p < precisionMin || p > MAX_PRECISION) {
String msg =
ScriptRuntime.getMessageById(
"msg.bad.precision", ScriptRuntime.toString(args[0]));
throw ScriptRuntime.rangeError(msg);
}
precision = ScriptRuntime.toInt32(p);
}
StringBuilder sb = new StringBuilder();
DToA.JS_dtostr(sb, oneArgMode, precision + precisionOffset, val);
return sb.toString();
}
private static Object js_isFinite(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Number n = argToNumber(args);
return n == null ? Boolean.FALSE : isFinite(n);
}
static Object isFinite(Object val) {
double nd = ScriptRuntime.toNumber(val);
return ScriptRuntime.wrapBoolean(!Double.isInfinite(nd) && !Double.isNaN(nd));
}
private static Object js_isNaN(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Number val = argToNumber(args);
if (val == null) {
return false;
}
if (val instanceof Double) {
return ((Double) val).isNaN();
}
double d = val.doubleValue();
return Double.isNaN(d);
}
private static Object js_isInteger(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Number val = argToNumber(args);
if (val == null) {
return false;
}
if (val instanceof Double) {
return isDoubleInteger((Double) val);
}
return isDoubleInteger(val.doubleValue());
}
private static Object js_isSafeInteger(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Number val = argToNumber(args);
if (val == null) {
return false;
}
if (val instanceof Double) {
return isDoubleSafeInteger((Double) val);
}
return isDoubleSafeInteger(val.doubleValue());
}
private static boolean isDoubleInteger(Double d) {
return !d.isInfinite() && !d.isNaN() && (Math.floor(d) == d);
}
private static boolean isDoubleInteger(double d) {
return !Double.isInfinite(d) && !Double.isNaN(d) && (Math.floor(d) == d);
}
private static boolean isDoubleSafeInteger(Double d) {
return isDoubleInteger(d) && (d <= MAX_SAFE_INTEGER) && (d >= MIN_SAFE_INTEGER);
}
private static boolean isDoubleSafeInteger(double d) {
return isDoubleInteger(d) && (d <= MAX_SAFE_INTEGER) && (d >= MIN_SAFE_INTEGER);
}
}