/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects.number;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToFlatString;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToInt32;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToInteger;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToNumber;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.number.NumberObject.NumberCreate;
import org.mozilla.javascript.StringToNumber;
import com.github.anba.es6draft.parser.NumberParser;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.internal.Strings;
import com.github.anba.es6draft.runtime.types.Constructor;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinConstructor;
/**
* <h1>20 Numbers and Dates</h1><br>
* <h2>20.1 Number Objects</h2>
* <ul>
* <li>20.1.1 The Number Constructor
* <li>20.1.2 Properties of the Number Constructor
* </ul>
*/
public final class NumberConstructor extends BuiltinConstructor implements Initializable {
/**
* Constructs a new Number constructor function.
*
* @param realm
* the realm object
*/
public NumberConstructor(Realm realm) {
super(realm, "Number", 1);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
}
@Override
public NumberConstructor clone() {
return new NumberConstructor(getRealm());
}
/**
* 20.1.1.1 Number ( [ value ] )
*/
@Override
public Double call(ExecutionContext callerContext, Object thisValue, Object... args) {
ExecutionContext calleeContext = calleeContext();
/* step 1 (omitted) */
/* steps 1-4 */
return args.length > 0 ? ToNumber(calleeContext, args[0]) : 0d;
/* steps 6-8 (not applicable) */
}
/**
* 20.1.1.1 Number ( [ value ] )
*/
@Override
public NumberObject construct(ExecutionContext callerContext, Constructor newTarget,
Object... args) {
ExecutionContext calleeContext = calleeContext();
/* steps 1-3 */
double n = args.length > 0 ? ToNumber(calleeContext, args[0]) : 0d;
/* step 4 (not applicable) */
/* steps 5-8 */
return NumberCreate(calleeContext, n,
GetPrototypeFromConstructor(calleeContext, newTarget, Intrinsics.NumberPrototype));
}
/**
* 20.1.2 Properties of the Number Constructor
*/
public enum Properties {
;
@Prototype
public static final Intrinsics __proto__ = Intrinsics.FunctionPrototype;
@Value(name = "length", attributes = @Attributes(writable = false, enumerable = false,
configurable = true))
public static final int length = 1;
@Value(name = "name", attributes = @Attributes(writable = false, enumerable = false,
configurable = true))
public static final String name = "Number";
/**
* 20.1.2.15 Number.prototype
*/
@Value(name = "prototype", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Intrinsics prototype = Intrinsics.NumberPrototype;
/**
* 20.1.2.7 Number.MAX_VALUE
*/
@Value(name = "MAX_VALUE", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Double MAX_VALUE = Double.MAX_VALUE;
/**
* 20.1.2.11 Number.MIN_VALUE
*/
@Value(name = "MIN_VALUE", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Double MIN_VALUE = Double.MIN_VALUE;
/**
* 20.1.2.8 Number.NaN
*/
@Value(name = "NaN", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Double NaN = Double.NaN;
/**
* 20.1.2.9 Number.NEGATIVE_INFINITY
*/
@Value(name = "NEGATIVE_INFINITY", attributes = @Attributes(writable = false,
enumerable = false, configurable = false))
public static final Double NEGATIVE_INFINITY = Double.NEGATIVE_INFINITY;
/**
* 20.1.2.14 Number.POSITIVE_INFINITY
*/
@Value(name = "POSITIVE_INFINITY", attributes = @Attributes(writable = false,
enumerable = false, configurable = false))
public static final Double POSITIVE_INFINITY = Double.POSITIVE_INFINITY;
/**
* 20.1.2.1 Number.EPSILON
*/
@Value(name = "EPSILON", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Double EPSILON = Math.ulp(1.0);
/**
* 20.1.2.6 Number.MAX_SAFE_INTEGER
*/
@Value(name = "MAX_SAFE_INTEGER", attributes = @Attributes(writable = false,
enumerable = false, configurable = false))
public static final Double MAX_SAFE_INTEGER = 0x1F_FFFF_FFFF_FFFFp0;
/**
* 20.1.2.10 Number.MIN_SAFE_INTEGER
*/
@Value(name = "MIN_SAFE_INTEGER", attributes = @Attributes(writable = false,
enumerable = false, configurable = false))
public static final Double MIN_SAFE_INTEGER = -0x1F_FFFF_FFFF_FFFFp0;
/**
* 20.1.2.13 Number.parseInt (string, radix)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param string
* the string
* @param radix
* the radix value
* @return the parsed integer
*/
@Function(name = "parseInt", arity = 2)
public static Object parseInt(ExecutionContext cx, Object thisValue, Object string,
Object radix) {
/* steps 1-2 */
String inputString = ToFlatString(cx, string);
/* step 3 */
String s = Strings.trimLeft(inputString);
int len = s.length();
int index = 0;
/* steps 4-6 */
boolean isPos = true;
if (index < len && (s.charAt(index) == '+' || s.charAt(index) == '-')) {
isPos = s.charAt(index) == '+';
index += 1;
}
/* steps 7-8 */
int r = ToInt32(cx, radix);
/* step 9 */
boolean stripPrefix = true;
if (r != 0) {
/* step 10 */
if (r < 2 || r > 36) {
return Double.NaN;
}
stripPrefix = r == 16;
} else {
/* step 11 */
r = 10;
}
/* step 12 */
if (stripPrefix && index + 1 < len && s.charAt(index) == '0'
&& (s.charAt(index + 1) == 'x' || s.charAt(index + 1) == 'X')) {
r = 16;
index += 2;
}
/* steps 13-15 */
double number = StringToNumber.stringToNumber(s, index, r);
/* steps 16-18 */
return isPos ? number : -number;
}
/**
* 20.1.2.12 Number.parseFloat (string)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param string
* the string
* @return the parsed number
*/
@Function(name = "parseFloat", arity = 1)
public static Object parseFloat(ExecutionContext cx, Object thisValue, Object string) {
/* steps 1-2 */
String inputString = ToFlatString(cx, string);
/* step 3 */
String trimmedString = Strings.trimLeft(inputString);
/* step 4 */
if (trimmedString.isEmpty()) {
return Double.NaN;
}
/* steps 5-8 */
return readDecimalLiteralPrefix(trimmedString);
}
/**
* 20.1.2.4 Number.isNaN (number)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param number
* the number value
* @return {@code true} if the number is the NaN value
*/
@Function(name = "isNaN", arity = 1)
public static Object isNaN(ExecutionContext cx, Object thisValue, Object number) {
/* step 1 */
if (!Type.isNumber(number)) {
return false;
}
/* steps 2-3 */
return Double.isNaN(Type.numberValue(number));
}
/**
* 20.1.2.2 Number.isFinite (number)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param number
* the number value
* @return {@code true} if the argument is a finite number
*/
@Function(name = "isFinite", arity = 1)
public static Object isFinite(ExecutionContext cx, Object thisValue, Object number) {
/* step 1 */
if (!Type.isNumber(number)) {
return false;
}
double num = Type.numberValue(number);
/* steps 2-3 */
return !(Double.isInfinite(num) || Double.isNaN(num));
}
/**
* 20.1.2.3 Number.isInteger (number)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param number
* the number value
* @return {@code true} if the argument is an integer
*/
@Function(name = "isInteger", arity = 1)
public static Object isInteger(ExecutionContext cx, Object thisValue, Object number) {
/* step 1 */
if (!Type.isNumber(number)) {
return false;
}
double num = Type.numberValue(number);
/* step 2 */
if (Double.isNaN(num) || Double.isInfinite(num)) {
return false;
}
/* step 3 */
double integer = ToInteger(num);
/* steps 4-5 */
return integer == num;
}
/**
* 20.1.2.5 Number.isSafeInteger (number)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param number
* the number value
* @return {@code true} if the argument is a safe integer
*/
@Function(name = "isSafeInteger", arity = 1)
public static Object isSafeInteger(ExecutionContext cx, Object thisValue, Object number) {
/* step 1 */
if (!Type.isNumber(number)) {
return false;
}
double num = Type.numberValue(number);
/* step 2 */
if (Double.isNaN(num) || Double.isInfinite(num)) {
return false;
}
/* step 3 */
double integer = ToInteger(num);
/* step 4 */
if (integer != num) {
return false;
}
/* steps 5-6 */
return Math.abs(integer) <= 0x1F_FFFF_FFFF_FFFFp0;
}
}
private static double readDecimalLiteralPrefix(String s) {
final int Infinity_length = "Infinity".length();
final int end = s.length();
int index = 0;
int c = s.charAt(index++);
boolean isPos = true;
if (c == '+' || c == '-') {
if (index >= end)
return Double.NaN;
isPos = (c == '+');
c = s.charAt(index++);
}
if (c == 'I') {
// Infinity
if (index - 1 + Infinity_length <= end
&& s.regionMatches(index - 1, "Infinity", 0, Infinity_length)) {
return isPos ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
}
return Double.NaN;
}
boolean hasDot = false, hasExp = false;
prefix: {
if (c == '.') {
if (index >= end)
return Double.NaN;
char d = s.charAt(index);
if (!(d >= '0' && d <= '9')) {
return Double.NaN;
}
} else {
if (!(c >= '0' && c <= '9')) {
return Double.NaN;
}
do {
if (!(c >= '0' && c <= '9')) {
break prefix;
}
if (index >= end) {
break;
}
c = s.charAt(index++);
} while (c != '.' && c != 'e' && c != 'E');
}
if (c == '.') {
hasDot = true;
while (index < end) {
c = s.charAt(index++);
if (c == 'e' || c == 'E') {
break;
}
if (!(c >= '0' && c <= '9')) {
break prefix;
}
}
}
if (c == 'e' || c == 'E') {
if (index >= end)
break prefix;
int exp = index;
c = s.charAt(index++);
if (c == '+' || c == '-') {
if (index >= end) {
index = exp;
break prefix;
}
c = s.charAt(index++);
}
if (!(c >= '0' && c <= '9')) {
index = exp;
break prefix;
}
hasExp = true;
do {
if (!(c >= '0' && c <= '9')) {
break prefix;
}
if (index >= end) {
break;
}
c = s.charAt(index++);
} while (true);
}
if (index >= end) {
if (!(hasDot || hasExp)) {
return NumberParser.parseInteger(s);
}
return NumberParser.parseDecimal(s);
}
} // prefix
if (!(hasDot || hasExp)) {
return NumberParser.parseInteger(s, index - 1);
}
return NumberParser.parseDecimal(s, index - 1);
}
}