NumberPrototype.cpp   [plain text]


/*
 *  Copyright (C) 1999-2000,2003 Harri Porten (porten@kde.org)
 *  Copyright (C) 2007, 2008, 2011 Apple Inc. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 *  USA
 *
 */

#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;

// To avoid conflict with WTF::StringBuilder.
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) };

/* Source for NumberPrototype.lut.h
@begin numberPrototypeTable
  toLocaleString    numberProtoFuncToLocaleString   DontEnum|Function 0
  valueOf           numberProtoFuncValueOf          DontEnum|Function 0
  toFixed           numberProtoFuncToFixed          DontEnum|Function 1
  toExponential     numberProtoFuncToExponential    DontEnum|Function 1
  toPrecision       numberProtoFuncToPrecision      DontEnum|Function 1
@end
*/

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()));
}

// ------------------------------ Functions ---------------------------

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;
}

// The largest finite floating point number is 1.mantissa * 2^(0x7fe-0x3ff).
// Since 2^N in binary is a one bit followed by N zero bits. 1 * 2^3ff requires
// at most 1024 characters to the left of a decimal point, in base 2 (1025 if
// we include a minus sign). For the fraction, a value with an exponent of 0
// has up to 52 bits to the right of the decimal point. Each decrement of the
// exponent down to a minimum of -0x3fe adds an additional digit to the length
// of the fraction. As such the maximum fraction size is 1075 (1076 including
// a point). We pick a buffer size such that can simply place the point in the
// center of the buffer, and are guaranteed to have enough space in each direction
// fo any number of digits an IEEE number may require to represent.
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);

    // Position the decimal point at the center of the string, set
    // the startOfResultString pointer to point at the decimal point.
    char* decimalPoint = buffer + sizeof(buffer) / 2;
    char* startOfResultString = decimalPoint;

    // Extract the sign.
    bool isNegative = originalNumber < 0;
    double number = originalNumber;
    if (std::signbit(originalNumber))
        number = -originalNumber;
    double integerPart = floor(number);

    // Check if the value has a fractional part to convert.
    double fractionPart = number - integerPart;
    if (!fractionPart) {
        *decimalPoint = '\0';
        // We do not need to care the negative zero (-0) since it is also converted to "0" in all the radix.
        if (integerPart < (static_cast<int64_t>(1) << (JSValue::numberOfInt52Bits - 1)))
            return int52ToStringWithRadix(startOfResultString, static_cast<int64_t>(originalNumber), radix);
    } else {
        // We use this to test for odd values in odd radix bases.
        // Where the base is even, (e.g. 10), to determine whether a value is even we need only
        // consider the least significant digit. For example, 124 in base 10 is even, because '4'
        // is even. if the radix is odd, then the radix raised to an integer power is also odd.
        // E.g. in base 5, 124 represents (1 * 125 + 2 * 25 + 4 * 5). Since each digit in the value
        // is multiplied by an odd number, the result is even if the sum of all digits is even.
        //
        // For the integer portion of the result, we only need test whether the integer value is
        // even or odd. For each digit of the fraction added, we should invert our idea of whether
        // the number is odd if the new digit is odd.
        //
        // Also initialize digit to this value; for even radix values we only need track whether
        // the last individual digit was odd.
        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;

        // Write the decimal point now.
        *decimalPoint = '.';

        // Higher precision representation of the fractional part.
        Uint16WithFraction fraction(fractionPart);

        bool needsRoundingUp = false;
        char* endOfResultString = decimalPoint + 1;

        // Calculate the delta from the current number to the next & previous possible IEEE numbers.
        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));

        // We track the delta from the current value to the next, to track how many digits of the
        // fraction we need to write. For example, if the value we are converting is precisely
        // 1.2345, so far we have written the digits "1.23" to a string leaving a remainder of
        // 0.45, and we want to determine whether we can round off, or whether we need to keep
        // appending digits ('4'). We can stop adding digits provided that then next possible
        // lower IEEE value is further from 1.23 than the remainder we'd be rounding off (0.45),
        // which is to say, less than 1.2255. Put another way, the delta between the prior
        // possible value and this number must be more than 2x the remainder we'd be rounding off
        // (or more simply half the delta between numbers must be greater than the remainder).
        //
        // Similarly we need track the delta to the next possible value, to dertermine whether
        // to round up. In almost all cases (other than at exponent boundaries) the deltas to
        // prior and subsequent values are identical, so we don't need track then separately.
        if (deltaNextDouble != deltaLastDouble) {
            // Since the deltas are different track them separately. Pre-multiply by 0.5.
            Uint16WithFraction halfDeltaNext(deltaNextDouble, 1);
            Uint16WithFraction halfDeltaLast(deltaLastDouble, 1);

            while (true) {
                // examine the remainder to determine whether we should be considering rounding
                // up or down. If remainder is precisely 0.5 rounding is to even.
                int dComparePoint5 = fraction.comparePoint5();
                if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) {
                    // Check for rounding up; are we closer to the value we'd round off to than
                    // the next IEEE value would be?
                    if (fraction.sumGreaterThanOne(halfDeltaNext)) {
                        needsRoundingUp = true;
                        break;
                    }
                } else {
                    // Check for rounding down; are we closer to the value we'd round off to than
                    // the prior IEEE value would be?
                    if (fraction < halfDeltaLast)
                        break;
                }

                ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1));
                // Write a digit to the string.
                fraction *= radix;
                digit = fraction.floorAndSubtract();
                *endOfResultString++ = radixDigits[digit];
                // Keep track whether the portion written is currently even, if the radix is odd.
                if (digit & 1)
                    isOddInOddRadix = !isOddInOddRadix;

                // Shift the fractions by radix.
                halfDeltaNext *= radix;
                halfDeltaLast *= radix;
            }
        } else {
            // This code is identical to that above, except since deltaNextDouble != deltaLastDouble
            // we don't need to track these two values separately.
            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;
            }
        }

        // Check if the fraction needs rounding off (flag set in the loop writing digits, above).
        if (needsRoundingUp) {
            // Whilst the last digit is the maximum in the current radix, remove it.
            // e.g. rounding up the last digit in "12.3999" is the same as rounding up the
            // last digit in "12.3" - both round up to "12.4".
            while (endOfResultString[-1] == radixDigits[radix - 1])
                --endOfResultString;

            // Radix digits are sequential in ascii/unicode, except for '9' and 'a'.
            // E.g. the first 'if' case handles rounding 67.89 to 67.8a in base 16.
            // The 'else if' case handles rounding of all other digits.
            if (endOfResultString[-1] == '9')
                endOfResultString[-1] = 'a';
            else if (endOfResultString[-1] != '.')
                ++endOfResultString[-1];
            else {
                // One other possibility - there may be no digits to round up in the fraction
                // (or all may be been rounded off already), in which case we may need to
                // round into the integer portion of the number. Remove the decimal point.
                --endOfResultString;
                // In order to get here there must have been a non-zero fraction, in which case
                // there must be at least one bit of the value's mantissa not in use in the
                // integer part of the number. As such, adding to the integer part should not
                // be able to lose precision.
                ASSERT((integerPart + 1) - integerPart == 1);
                ++integerPart;
            }
        } else {
            // We only need to check for trailing zeros if the value does not get rounded up.
            while (endOfResultString[-1] == '0')
                --endOfResultString;
        }

        *endOfResultString = '\0';
        ASSERT(endOfResultString < buffer + sizeof(buffer));
    }

    BigInteger units(integerPart);

    // Always loop at least once, to emit at least '0'.
    do {
        ASSERT(buffer < startOfResultString);

        // Read a single digit and write it to the front of the string.
        // Divide by radix to remove one digit from the value.
        uint32_t digit = units.divide(radix);
        *--startOfResultString = radixDigits[digit];
    } while (!!units);

    // If the number is negative, prepend '-'.
    if (isNegative)
        *--startOfResultString = '-';
    ASSERT(buffer <= startOfResultString);

    return startOfResultString;
}

static String toStringWithRadixInternal(int32_t number, unsigned radix)
{
    LChar buf[1 + 32]; // Worst case is radix == 2, which gives us 32 digits + sign.
    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));
    }

    // Always loop at least once, to emit at least '0'.
    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);
}

// toExponential converts a number to a string, always formatting as an exponential.
// This method takes an optional argument specifying a number of *decimal places*
// to round the significand to (or, put another way, this method optionally rounds
// to argument-plus-one significant figures).
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);

    // Perform ToInteger on the argument before remaining steps.
    int decimalPlacesInExponent;
    bool isUndefined;
    bool inRange = getIntegerArgumentInRange(exec, 0, 20, decimalPlacesInExponent, isUndefined);
    RETURN_IF_EXCEPTION(scope, { });

    // Handle NaN and Infinity.
    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));

    // Round if the argument is not undefined, always format as exponential.
    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())));
}

// toFixed converts a number to a string, always formatting as an a decimal fraction.
// This method takes an argument specifying a number of decimal places to round the
// significand to. However when converting large values (1e+21 and above) this
// method will instead fallback to calling ToString. 
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);

    // Get the argument. 
    int decimalPlaces;
    bool isUndefined; // This is ignored; undefined treated as 0.
    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));

    // 15.7.4.5.7 states "If x >= 10^21, then let m = ToString(x)"
    // This also covers Ininity, and structure the check so that NaN
    // values are also handled by numberToString
    if (!(fabs(x) < 1e+21))
        return JSValue::encode(jsString(exec, String::numberToStringECMAScript(x)));

    // The check above will return false for NaN or Infinity, these will be
    // handled by numberToString.
    ASSERT(std::isfinite(x));

    NumberToStringBuffer buffer;
    return JSValue::encode(jsString(exec, String(numberToFixedWidthString(x, decimalPlaces, buffer))));
}

// toPrecision converts a number to a string, taking an argument specifying a
// number of significant figures to round the significand to. For positive
// exponent, all values that can be represented using a decimal fraction will
// be, e.g. when rounding to 3 s.f. any value up to 999 will be formated as a
// decimal, whilst 1000 is converted to the exponential representation 1.00e+3.
// For negative exponents values >= 1e-6 are formated as decimal fractions,
// with smaller values converted to exponential representation.
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);

    // Perform ToInteger on the argument before remaining steps.
    int significantFigures;
    bool isUndefined;
    bool inRange = getIntegerArgumentInRange(exec, 1, 21, significantFigures, isUndefined);
    RETURN_IF_EXCEPTION(scope, { });

    // To precision called with no argument is treated as ToString.
    if (isUndefined)
        return JSValue::encode(jsString(exec, String::numberToStringECMAScript(x)));

    // Handle NaN and Infinity.
    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));
    // A negative value casted to unsigned would be bigger than 36 (the max radix).
    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));
    // A negative value casted to unsigned would be bigger than 36 (the max radix).
    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)));

    // Position the decimal point at the center of the string, set
    // the startOfResultString pointer to point at the decimal point.
    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;
}

} // namespace JSC