NumberPrototype.cpp [plain text]
#include "config.h"
#include "NumberPrototype.h"
#include "BigInteger.h"
#include "Error.h"
#include "JSCBuiltins.h"
#include "JSCInlines.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
#include "JSString.h"
#include "ParseInt.h"
#include "Uint16WithFraction.h"
#include <wtf/dtoa.h>
#include <wtf/Assertions.h>
#include <wtf/MathExtras.h>
#include <wtf/dtoa/double-conversion.h>
using DoubleToStringConverter = WTF::double_conversion::DoubleToStringConverter;
typedef WTF::double_conversion::StringBuilder DoubleConversionStringBuilder;
namespace JSC {
static EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState*);
static EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState*);
static EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState*);
static EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState*);
static EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState*);
}
#include "NumberPrototype.lut.h"
namespace JSC {
const ClassInfo NumberPrototype::s_info = { "Number", &NumberObject::s_info, &numberPrototypeTable, nullptr, CREATE_METHOD_TABLE(NumberPrototype) };
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(NumberPrototype);
NumberPrototype::NumberPrototype(VM& vm, Structure* structure)
: NumberObject(vm, structure)
{
}
void NumberPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
setInternalValue(vm, jsNumber(0));
JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, numberProtoFuncToString, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, NumberPrototypeToStringIntrinsic);
#if ENABLE(INTL)
JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("toLocaleString", numberPrototypeToLocaleStringCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
#endif // ENABLE(INTL)
ASSERT(inherits(vm, info()));
}
static ALWAYS_INLINE bool toThisNumber(VM& vm, JSValue thisValue, double& x)
{
if (thisValue.isInt32()) {
x = thisValue.asInt32();
return true;
}
if (thisValue.isDouble()) {
x = thisValue.asDouble();
return true;
}
if (auto* numberObject = jsDynamicCast<NumberObject*>(vm, thisValue)) {
x = numberObject->internalValue().asNumber();
return true;
}
return false;
}
static ALWAYS_INLINE bool getIntegerArgumentInRange(ExecState* exec, int low, int high, int& result, bool& isUndefined)
{
result = 0;
isUndefined = false;
JSValue argument0 = exec->argument(0);
if (argument0.isUndefined()) {
isUndefined = true;
return true;
}
double asDouble = argument0.toInteger(exec);
if (asDouble < low || asDouble > high)
return false;
result = static_cast<int>(asDouble);
return true;
}
typedef char RadixBuffer[2180];
static inline char* int52ToStringWithRadix(char* startOfResultString, int64_t int52Value, unsigned radix)
{
bool negative = false;
uint64_t positiveNumber = int52Value;
if (int52Value < 0) {
negative = true;
positiveNumber = -int52Value;
}
do {
uint64_t index = positiveNumber % radix;
ASSERT(index < sizeof(radixDigits));
*--startOfResultString = radixDigits[index];
positiveNumber /= radix;
} while (positiveNumber);
if (negative)
*--startOfResultString = '-';
return startOfResultString;
}
static char* toStringWithRadixInternal(RadixBuffer& buffer, double originalNumber, unsigned radix)
{
ASSERT(std::isfinite(originalNumber));
ASSERT(radix >= 2 && radix <= 36);
char* decimalPoint = buffer + sizeof(buffer) / 2;
char* startOfResultString = decimalPoint;
bool isNegative = originalNumber < 0;
double number = originalNumber;
if (std::signbit(originalNumber))
number = -originalNumber;
double integerPart = floor(number);
double fractionPart = number - integerPart;
if (!fractionPart) {
*decimalPoint = '\0';
if (integerPart < (static_cast<int64_t>(1) << (JSValue::numberOfInt52Bits - 1)))
return int52ToStringWithRadix(startOfResultString, static_cast<int64_t>(originalNumber), radix);
} else {
bool integerPartIsOdd = integerPart <= static_cast<double>(0x1FFFFFFFFFFFFFull) && static_cast<int64_t>(integerPart) & 1;
ASSERT(integerPartIsOdd == static_cast<bool>(fmod(integerPart, 2)));
bool isOddInOddRadix = integerPartIsOdd;
uint32_t digit = integerPartIsOdd;
*decimalPoint = '.';
Uint16WithFraction fraction(fractionPart);
bool needsRoundingUp = false;
char* endOfResultString = decimalPoint + 1;
double nextNumber = nextafter(number, std::numeric_limits<double>::infinity());
double lastNumber = nextafter(number, -std::numeric_limits<double>::infinity());
ASSERT(std::isfinite(nextNumber) && !std::signbit(nextNumber));
ASSERT(std::isfinite(lastNumber) && !std::signbit(lastNumber));
double deltaNextDouble = nextNumber - number;
double deltaLastDouble = number - lastNumber;
ASSERT(std::isfinite(deltaNextDouble) && !std::signbit(deltaNextDouble));
ASSERT(std::isfinite(deltaLastDouble) && !std::signbit(deltaLastDouble));
if (deltaNextDouble != deltaLastDouble) {
Uint16WithFraction halfDeltaNext(deltaNextDouble, 1);
Uint16WithFraction halfDeltaLast(deltaLastDouble, 1);
while (true) {
int dComparePoint5 = fraction.comparePoint5();
if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) {
if (fraction.sumGreaterThanOne(halfDeltaNext)) {
needsRoundingUp = true;
break;
}
} else {
if (fraction < halfDeltaLast)
break;
}
ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1));
fraction *= radix;
digit = fraction.floorAndSubtract();
*endOfResultString++ = radixDigits[digit];
if (digit & 1)
isOddInOddRadix = !isOddInOddRadix;
halfDeltaNext *= radix;
halfDeltaLast *= radix;
}
} else {
Uint16WithFraction halfDelta(deltaNextDouble, 1);
while (true) {
int dComparePoint5 = fraction.comparePoint5();
if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) {
if (fraction.sumGreaterThanOne(halfDelta)) {
needsRoundingUp = true;
break;
}
} else if (fraction < halfDelta)
break;
ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1));
fraction *= radix;
digit = fraction.floorAndSubtract();
if (digit & 1)
isOddInOddRadix = !isOddInOddRadix;
*endOfResultString++ = radixDigits[digit];
halfDelta *= radix;
}
}
if (needsRoundingUp) {
while (endOfResultString[-1] == radixDigits[radix - 1])
--endOfResultString;
if (endOfResultString[-1] == '9')
endOfResultString[-1] = 'a';
else if (endOfResultString[-1] != '.')
++endOfResultString[-1];
else {
--endOfResultString;
ASSERT((integerPart + 1) - integerPart == 1);
++integerPart;
}
} else {
while (endOfResultString[-1] == '0')
--endOfResultString;
}
*endOfResultString = '\0';
ASSERT(endOfResultString < buffer + sizeof(buffer));
}
BigInteger units(integerPart);
do {
ASSERT(buffer < startOfResultString);
uint32_t digit = units.divide(radix);
*--startOfResultString = radixDigits[digit];
} while (!!units);
if (isNegative)
*--startOfResultString = '-';
ASSERT(buffer <= startOfResultString);
return startOfResultString;
}
static String toStringWithRadixInternal(int32_t number, unsigned radix)
{
LChar buf[1 + 32]; LChar* end = std::end(buf);
LChar* p = end;
bool negative = false;
uint32_t positiveNumber = number;
if (number < 0) {
negative = true;
positiveNumber = static_cast<uint32_t>(-static_cast<int64_t>(number));
}
do {
uint32_t index = positiveNumber % radix;
ASSERT(index < sizeof(radixDigits));
*--p = static_cast<LChar>(radixDigits[index]);
positiveNumber /= radix;
} while (positiveNumber);
if (negative)
*--p = '-';
return String(p, static_cast<unsigned>(end - p));
}
String toStringWithRadix(double doubleValue, int32_t radix)
{
ASSERT(2 <= radix && radix <= 36);
int32_t integerValue = static_cast<int32_t>(doubleValue);
if (integerValue == doubleValue)
return toStringWithRadixInternal(integerValue, radix);
if (radix == 10 || !std::isfinite(doubleValue))
return String::numberToStringECMAScript(doubleValue);
RadixBuffer buffer;
return toStringWithRadixInternal(buffer, doubleValue, radix);
}
EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double x;
if (!toThisNumber(vm, exec->thisValue(), x))
return throwVMTypeError(exec, scope);
int decimalPlacesInExponent;
bool isUndefined;
bool inRange = getIntegerArgumentInRange(exec, 0, 20, decimalPlacesInExponent, isUndefined);
RETURN_IF_EXCEPTION(scope, { });
if (!std::isfinite(x))
return JSValue::encode(jsNontrivialString(exec, String::numberToStringECMAScript(x)));
if (!inRange)
return throwVMError(exec, scope, createRangeError(exec, "toExponential() argument must be between 0 and 20"_s));
char buffer[WTF::NumberToStringBufferLength];
DoubleConversionStringBuilder builder(buffer, WTF::NumberToStringBufferLength);
const DoubleToStringConverter& converter = DoubleToStringConverter::EcmaScriptConverter();
builder.Reset();
isUndefined
? converter.ToExponential(x, -1, &builder)
: converter.ToExponential(x, decimalPlacesInExponent, &builder);
return JSValue::encode(jsString(exec, String(builder.Finalize())));
}
EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double x;
if (!toThisNumber(vm, exec->thisValue(), x))
return throwVMTypeError(exec, scope);
int decimalPlaces;
bool isUndefined; bool inRange = getIntegerArgumentInRange(exec, 0, 20, decimalPlaces, isUndefined);
RETURN_IF_EXCEPTION(scope, { });
if (!inRange)
return throwVMError(exec, scope, createRangeError(exec, "toFixed() argument must be between 0 and 20"_s));
if (!(fabs(x) < 1e+21))
return JSValue::encode(jsString(exec, String::numberToStringECMAScript(x)));
ASSERT(std::isfinite(x));
NumberToStringBuffer buffer;
return JSValue::encode(jsString(exec, String(numberToFixedWidthString(x, decimalPlaces, buffer))));
}
EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double x;
if (!toThisNumber(vm, exec->thisValue(), x))
return throwVMTypeError(exec, scope);
int significantFigures;
bool isUndefined;
bool inRange = getIntegerArgumentInRange(exec, 1, 21, significantFigures, isUndefined);
RETURN_IF_EXCEPTION(scope, { });
if (isUndefined)
return JSValue::encode(jsString(exec, String::numberToStringECMAScript(x)));
if (!std::isfinite(x))
return JSValue::encode(jsNontrivialString(exec, String::numberToStringECMAScript(x)));
if (!inRange)
return throwVMError(exec, scope, createRangeError(exec, "toPrecision() argument must be between 1 and 21"_s));
NumberToStringBuffer buffer;
return JSValue::encode(jsString(exec, String(numberToFixedPrecisionString(x, significantFigures, buffer))));
}
static ALWAYS_INLINE JSString* int32ToStringInternal(VM& vm, int32_t value, int32_t radix)
{
ASSERT(!(radix < 2 || radix > 36));
if (static_cast<unsigned>(value) < static_cast<unsigned>(radix)) {
ASSERT(value <= 36);
ASSERT(value >= 0);
return vm.smallStrings.singleCharacterString(radixDigits[value]);
}
if (radix == 10)
return jsNontrivialString(&vm, vm.numericStrings.add(value));
return jsNontrivialString(&vm, toStringWithRadixInternal(value, radix));
}
static ALWAYS_INLINE JSString* numberToStringInternal(VM& vm, double doubleValue, int32_t radix)
{
ASSERT(!(radix < 2 || radix > 36));
int32_t integerValue = static_cast<int32_t>(doubleValue);
if (integerValue == doubleValue)
return int32ToStringInternal(vm, integerValue, radix);
if (radix == 10)
return jsString(&vm, vm.numericStrings.add(doubleValue));
if (!std::isfinite(doubleValue))
return jsNontrivialString(&vm, String::numberToStringECMAScript(doubleValue));
RadixBuffer buffer;
return jsString(&vm, toStringWithRadixInternal(buffer, doubleValue, radix));
}
JSString* int32ToString(VM& vm, int32_t value, int32_t radix)
{
return int32ToStringInternal(vm, value, radix);
}
JSString* int52ToString(VM& vm, int64_t value, int32_t radix)
{
ASSERT(!(radix < 2 || radix > 36));
if (static_cast<uint64_t>(value) < static_cast<uint64_t>(radix)) {
ASSERT(value <= 36);
ASSERT(value >= 0);
return vm.smallStrings.singleCharacterString(radixDigits[value]);
}
if (radix == 10)
return jsNontrivialString(&vm, vm.numericStrings.add(static_cast<double>(value)));
RadixBuffer buffer;
char* decimalPoint = buffer + sizeof(buffer) / 2;
char* startOfResultString = decimalPoint;
*decimalPoint = '\0';
return jsNontrivialString(&vm, int52ToStringWithRadix(startOfResultString, value, radix));
}
JSString* numberToString(VM& vm, double doubleValue, int32_t radix)
{
return numberToStringInternal(vm, doubleValue, radix);
}
EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState* state)
{
VM& vm = state->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double doubleValue;
if (!toThisNumber(vm, state->thisValue(), doubleValue))
return throwVMTypeError(state, scope);
auto radix = extractToStringRadixArgument(state, state->argument(0), scope);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
return JSValue::encode(numberToStringInternal(vm, doubleValue, radix));
}
EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double x;
if (!toThisNumber(vm, exec->thisValue(), x))
return throwVMTypeError(exec, scope);
return JSValue::encode(jsNumber(x).toString(exec));
}
EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState* exec)
{
VM& vm = exec->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double x;
JSValue thisValue = exec->thisValue();
if (!toThisNumber(vm, thisValue, x))
return throwVMTypeError(exec, scope, WTF::makeString("thisNumberValue called on incompatible ", asString(jsTypeStringForValue(exec, thisValue))->value(exec)));
return JSValue::encode(jsNumber(x));
}
int32_t extractToStringRadixArgument(ExecState* state, JSValue radixValue, ThrowScope& throwScope)
{
if (radixValue.isUndefined())
return 10;
if (radixValue.isInt32()) {
int32_t radix = radixValue.asInt32();
if (radix >= 2 && radix <= 36)
return radix;
} else {
double radixDouble = radixValue.toInteger(state);
RETURN_IF_EXCEPTION(throwScope, 0);
if (radixDouble >= 2 && radixDouble <= 36)
return static_cast<int32_t>(radixDouble);
}
throwRangeError(state, throwScope, "toString() radix argument must be between 2 and 36"_s);
return 0;
}
}