* Copyright (c) 2021-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/builtins/builtins_number.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/js_function.h"
#include "ecmascript/js_primitive_ref.h"
#ifdef ARK_SUPPORT_INTL
#include "ecmascript/js_number_format.h"
#else
#ifndef ARK_NOT_SUPPORT_INTL_GLOBAL
#include "ecmascript/intl/global_intl_helper.h"
#endif
#endif
namespace panda::ecmascript::builtins {
using NumberHelper = base::NumberHelper;
JSTaggedValue BuiltinsNumber::NumberConstructor(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, Constructor);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
JSTaggedNumber numberValue(0);
if (argv->GetArgsNumber() > 0) {
JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
if (!value->IsNumber()) {
JSHandle<JSTaggedValue> numericVal = JSTaggedValue::ToNumeric(thread, value);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (numericVal->IsBigInt()) {
JSHandle<BigInt> bigNumericVal(numericVal);
numberValue = BigInt::BigIntToNumber(bigNumericVal);
} else {
numberValue = JSTaggedNumber(numericVal.GetTaggedValue());
}
} else {
numberValue = JSTaggedNumber(value.GetTaggedValue());
}
}
if (newTarget->IsUndefined()) {
return numberValue;
}
JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
JSHandle<JSObject> result =
thread->GetEcmaVM()->GetFactory()->NewJSObjectByConstructor(JSHandle<JSFunction>::Cast(constructor), newTarget);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSPrimitiveRef::Cast(*result)->SetValue(thread, numberValue);
return result.GetTaggedValue();
}
JSTaggedValue BuiltinsNumber::IsFinite(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, IsFinite);
JSTaggedValue msg = GetCallArg(argv, 0).GetTaggedValue();
if (NumberHelper::IsFinite(msg)) {
return GetTaggedBoolean(true);
}
return GetTaggedBoolean(false);
}
JSTaggedValue BuiltinsNumber::IsInteger(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, IsInteger);
JSThread *thread = argv->GetThread();
JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
bool result = false;
if (NumberHelper::IsFinite(msg.GetTaggedValue())) {
[[maybe_unused]] EcmaHandleScope handleScope(thread);
double value = JSTaggedNumber(msg.GetTaggedValue()).GetNumber();
JSTaggedNumber number = JSTaggedValue::ToInteger(thread, msg);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
result = (value == number.GetNumber());
}
return GetTaggedBoolean(result);
}
JSTaggedValue BuiltinsNumber::IsNaN(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, IsNaN);
JSTaggedValue msg = GetCallArg(argv, 0).GetTaggedValue();
if (NumberHelper::IsNaN(msg)) {
return GetTaggedBoolean(true);
}
return GetTaggedBoolean(false);
}
JSTaggedValue BuiltinsNumber::IsSafeInteger(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, IsSafeInteger);
JSThread *thread = argv->GetThread();
JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
bool result = false;
if (NumberHelper::IsFinite(msg.GetTaggedValue())) {
[[maybe_unused]] EcmaHandleScope handleScope(thread);
double value = JSTaggedNumber(msg.GetTaggedValue()).GetNumber();
JSTaggedNumber number = JSTaggedValue::ToInteger(thread, msg);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
result = (value == number.GetNumber()) && std::abs(value) <= base::MAX_SAFE_INTEGER;
}
return GetTaggedBoolean(result);
}
JSTaggedValue BuiltinsNumber::ParseFloat(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, ParseFloat);
JSThread *thread = argv->GetThread();
JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
if (msg->IsUndefined()) {
return GetTaggedDouble(base::NAN_VALUE);
}
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<EcmaString> numberString = JSTaggedValue::ToString(thread, msg);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
CVector<uint8_t> buf;
common::Span<const uint8_t> str = EcmaStringAccessor(numberString).ToUtf8Span(thread, buf);
if (NumberHelper::IsEmptyString(str.begin(), str.end())) {
return BuiltinsBase::GetTaggedDouble(base::NAN_VALUE);
}
double result = NumberHelper::StringToDouble(str.begin(), str.end(), 0, base::IGNORE_TRAILING);
return GetTaggedDouble(result);
}
JSTaggedValue BuiltinsNumber::ParseInt(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), Number, ParseInt);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> msg = GetCallArg(argv, 0);
JSHandle<JSTaggedValue> arg2 = GetCallArg(argv, 1);
int32_t radix = 0;
JSHandle<EcmaString> numberString = JSTaggedValue::ToString(thread, msg);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!arg2->IsUndefined()) {
radix = JSTaggedValue::ToInt32(thread, arg2);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
return NumberHelper::StringToNumber(thread, *numberString, radix);
}
JSTaggedValue BuiltinsNumber::ToExponential(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, Number, ToExponential);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSTaggedNumber value = ThisNumberValue(thread, argv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> digits = GetCallArg(argv, 0);
JSTaggedNumber digitInt = JSTaggedValue::ToInteger(thread, digits);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
double values = value.GetNumber();
if (std::isnan(values)) {
return GetTaggedString(thread, "NaN");
}
if (!std::isfinite(values)) {
if (values < 0) {
return GetTaggedString(thread, "-Infinity");
}
return GetTaggedString(thread, "Infinity");
}
double fraction = digitInt.GetNumber();
if (digits->IsUndefined()) {
fraction = 0;
} else {
if (fraction < base::MIN_FRACTION || fraction > base::MAX_FRACTION) {
THROW_RANGE_ERROR_AND_RETURN(thread, "fraction must be 0 to 100", JSTaggedValue::Exception());
}
fraction++;
}
return NumberHelper::DoubleToExponential(thread, values, static_cast<int>(fraction));
}
JSTaggedValue BuiltinsNumber::ToFixed(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, Number, ToFixed);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSTaggedNumber value = ThisNumberValue(thread, argv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> digitArgv = GetCallArg(argv, 0);
JSTaggedNumber digitInt = JSTaggedValue::ToInteger(thread, digitArgv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (digitArgv->IsUndefined()) {
digitInt = JSTaggedNumber(0);
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
double digit = digitInt.GetNumber();
if (digit < base::MIN_FRACTION || digit > base::MAX_FRACTION) {
THROW_RANGE_ERROR_AND_RETURN(thread, "fraction must be 0 to 100", JSTaggedValue::Exception());
}
double valueNumber = value.GetNumber();
if (std::isnan(valueNumber)) {
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
return globalConst->GetNanCapitalString();
}
if (!std::isfinite(valueNumber)) {
if (valueNumber < 0) {
return GetTaggedString(thread, "-Infinity");
}
return GetTaggedString(thread, "Infinity");
}
const double FIRST_NO_FIXED = 1e21;
if (std::abs(valueNumber) >= FIRST_NO_FIXED) {
return value.ToString(thread).GetTaggedValue();
}
return NumberHelper::DoubleToFixedString(thread, valueNumber, static_cast<int>(digit));
}
JSTaggedValue BuiltinsNumber::ToLocaleString(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, Number, ToLocaleString);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
[[maybe_unused]] JSHandle<JSTaggedValue> x(thread, ThisNumberValue(thread, argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
JSHandle<JSTaggedValue> options = GetCallArg(argv, 1);
[[maybe_unused]] bool cacheable = (locales->IsUndefined() || locales->IsString()) && options->IsUndefined();
#ifdef ARK_SUPPORT_INTL
if (cacheable) {
auto numberFormatter = JSNumberFormat::GetCachedIcuNumberFormatter(thread, locales);
if (numberFormatter != nullptr) {
JSHandle<JSTaggedValue> result = JSNumberFormat::FormatNumeric(thread, numberFormatter, x.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return result.GetTaggedValue();
}
}
EcmaVM *ecmaVm = thread->GetEcmaVM();
JSHandle<JSFunction> ctor(ecmaVm->GetGlobalEnv()->GetNumberFormatFunction());
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(ctor);
JSHandle<JSNumberFormat> numberFormat = JSHandle<JSNumberFormat>::Cast(obj);
JSNumberFormat::InitializeNumberFormat(thread, numberFormat, locales, options, cacheable);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (cacheable) {
auto numberFormatter = JSNumberFormat::GetCachedIcuNumberFormatter(thread, locales);
ASSERT(numberFormatter != nullptr);
JSHandle<JSTaggedValue> result = JSNumberFormat::FormatNumeric(thread, numberFormatter, x.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return result.GetTaggedValue();
}
JSHandle<JSTaggedValue> result = JSNumberFormat::FormatNumeric(thread, numberFormat, x.GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return result.GetTaggedValue();
#else
#ifdef ARK_NOT_SUPPORT_INTL_GLOBAL
ARK_SUPPORT_INTL_RETURN_JSVALUE(thread, "LocaleCompare");
#else
intl::GlobalIntlHelper gh(thread, intl::GlobalFormatterType::NumberFormatter);
auto numberFormatter = gh.GetGlobalObject<intl::GlobalNumberFormat>(thread,
locales, options, intl::GlobalFormatterType::NumberFormatter, cacheable);
if (numberFormatter == nullptr) {
LOG_ECMA(ERROR) << "BuiltinsNumber:numberFormatter is nullptr";
}
ASSERT(numberFormatter != nullptr);
std::string result = numberFormatter->Format(x->GetDouble());
EcmaVM *ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle returnValue = factory->NewFromStdString(result);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return returnValue.GetTaggedValue();
#endif
#endif
}
JSTaggedValue BuiltinsNumber::ToPrecision(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, Number, ToPrecision);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSTaggedNumber value = ThisNumberValue(thread, argv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> digitArgv = GetCallArg(argv, 0);
if (digitArgv->IsUndefined()) {
return value.ToString(thread).GetTaggedValue();
}
JSTaggedNumber digitInt = JSTaggedValue::ToInteger(thread, digitArgv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
double valueNumber = value.GetNumber();
if (std::isnan(valueNumber)) {
return GetTaggedString(thread, "NaN");
}
if (!std::isfinite(valueNumber)) {
if (valueNumber < 0) {
return GetTaggedString(thread, "-Infinity");
}
return GetTaggedString(thread, "Infinity");
}
double digit = digitInt.GetNumber();
if (digit < base::MIN_FRACTION + 1 || digit > base::MAX_FRACTION) {
THROW_RANGE_ERROR_AND_RETURN(thread, "fraction must be 1 to 100", JSTaggedValue::Exception());
}
return NumberHelper::DoubleToPrecisionString(thread, valueNumber, static_cast<int>(digit));
}
JSTaggedValue BuiltinsNumber::ToString(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, Number, ToString);
JSTaggedNumber value = ThisNumberValue(thread, argv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
double radix = base::DECIMAL;
JSHandle<JSTaggedValue> radixValue = GetCallArg(argv, 0);
if (radixValue->IsInt()) {
radix = radixValue->GetInt();
} else if (!radixValue->IsUndefined()) {
JSTaggedNumber radixNumber = JSTaggedValue::ToInteger(thread, radixValue);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
radix = radixNumber.GetNumber();
}
if (radix < base::MIN_RADIX || radix > base::MAX_RADIX) {
THROW_RANGE_ERROR_AND_RETURN(thread, "radix must be 2 to 36", JSTaggedValue::Exception());
}
if (radix == base::DECIMAL) {
JSHandle<NumberToStringResultCache> cacheTable(thread->GetGlobalEnv()->GetNumberToStringResultCache());
int entry = cacheTable->GetNumberHash(value);
JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, entry, value);
if (cacheResult != JSTaggedValue::Undefined()) {
return cacheResult;
}
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<EcmaString> resultJSHandle = value.ToString(thread);
cacheTable->SetCachedResult(thread, entry, value, resultJSHandle);
return resultJSHandle.GetTaggedValue();
}
[[maybe_unused]] EcmaHandleScope handleScope(thread);
if (value.IsInt()) {
return NumberHelper::Int32ToString(thread, value.GetInt(), radix);
}
double valueNumber = value.GetNumber();
if (std::isnan(valueNumber)) {
return GetTaggedString(thread, "NaN");
}
if (!std::isfinite(valueNumber)) {
if (valueNumber < 0) {
return GetTaggedString(thread, "-Infinity");
}
return GetTaggedString(thread, "Infinity");
}
return NumberHelper::DoubleToString(thread, valueNumber, static_cast<int>(radix));
}
JSTaggedValue BuiltinsNumber::ValueOf(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, Number, ValueOf);
JSTaggedValue x = ThisNumberValue(thread, argv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return x;
}
JSTaggedNumber BuiltinsNumber::ThisNumberValue(JSThread *thread, EcmaRuntimeCallInfo *argv)
{
BUILTINS_API_TRACE(thread, Number, ThisNumberValue);
JSHandle<JSTaggedValue> value = GetThis(argv);
if (value->IsNumber()) {
return JSTaggedNumber(value.GetTaggedValue());
}
if (value->IsJSPrimitiveRef()) {
JSTaggedValue primitive = JSPrimitiveRef::Cast(value->GetTaggedObject())->GetValue(thread);
if (primitive.IsNumber()) {
return JSTaggedNumber(primitive);
}
}
[[maybe_unused]] EcmaHandleScope handleScope(thread);
THROW_TYPE_ERROR_AND_RETURN(thread, "not number type", JSTaggedNumber::Exception());
}
JSTaggedValue NumberToStringResultCache::CreateCacheTable(const JSThread *thread)
{
int length = INITIAL_CACHE_NUMBER * ENTRY_SIZE;
auto table = static_cast<NumberToStringResultCache*>(
*thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined()));
return JSTaggedValue(table);
}
JSTaggedValue NumberToStringResultCache::FindCachedResult(const JSThread *thread, int entry, JSTaggedValue &target)
{
uint32_t index = static_cast<uint32_t>(entry * ENTRY_SIZE);
JSTaggedValue entryNumber = Get(thread, index + NUMBER_INDEX);
if (entryNumber == target) {
return Get(thread, index + RESULT_INDEX);
}
return JSTaggedValue::Undefined();
}
void NumberToStringResultCache::SetCachedResult(const JSThread *thread, int entry, JSTaggedValue &number,
JSHandle<EcmaString> &result)
{
uint32_t index = static_cast<uint32_t>(entry * ENTRY_SIZE);
Set(thread, index + NUMBER_INDEX, number);
Set(thread, index + RESULT_INDEX, result.GetTaggedValue());
}
}