blob: d6f0f7064a1a584ee4596478b1611a9ccb1f1b3f [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
#include "errstr.h"
#include "Library/EngineInterfaceObject.h"
#include "Library/IntlEngineInterfaceExtensionObject.h"
using namespace PlatformAgnostic;
namespace Js
{
DEFINE_RECYCLER_TRACKER_PERF_COUNTER(JavascriptNumber);
Var JavascriptNumber::ToVarNoCheck(double value, ScriptContext* scriptContext)
{
return JavascriptNumber::NewInlined(value, scriptContext);
}
Var JavascriptNumber::ToVarWithCheck(double value, ScriptContext* scriptContext)
{
#if FLOATVAR
if (IsNan(value))
{
value = JavascriptNumber::NaN;
}
#endif
return JavascriptNumber::NewInlined(value, scriptContext);
}
Var JavascriptNumber::ToVarInPlace(double value, ScriptContext* scriptContext, JavascriptNumber *result)
{
return InPlaceNew(value, scriptContext, result);
}
Var JavascriptNumber::ToVarInPlace(int64 value, ScriptContext* scriptContext, JavascriptNumber *result)
{
return InPlaceNew((double)value, scriptContext, result);
}
Var JavascriptNumber::ToVarMaybeInPlace(double value, ScriptContext* scriptContext, JavascriptNumber *result)
{
if (result)
{
return InPlaceNew(value, scriptContext, result);
}
return ToVarNoCheck(value, scriptContext);
}
Var JavascriptNumber::ToVarInPlace(int32 nValue, ScriptContext* scriptContext, Js::JavascriptNumber *result)
{
if (!TaggedInt::IsOverflow(nValue))
{
return TaggedInt::ToVarUnchecked(nValue);
}
return InPlaceNew(static_cast<double>(nValue), scriptContext, result);
}
Var JavascriptNumber::ToVarInPlace(uint32 nValue, ScriptContext* scriptContext, Js::JavascriptNumber *result)
{
if (!TaggedInt::IsOverflow(nValue))
{
return TaggedInt::ToVarUnchecked(nValue);
}
return InPlaceNew(static_cast<double>(nValue), scriptContext, result);
}
Var JavascriptNumber::ToVarIntCheck(double value,ScriptContext* scriptContext)
{
//
// Check if a well-known value:
// - This significantly cuts down on the below floating-point to integer conversions.
//
if (value == 0.0)
{
if(IsNegZero(value))
{
return scriptContext->GetLibrary()->GetNegativeZero();
}
return TaggedInt::ToVarUnchecked(0);
}
if (value == 1.0)
{
return TaggedInt::ToVarUnchecked(1);
}
//
// Check if number can be reduced back into a TaggedInt:
// - This avoids extra GC.
//
int nValue = (int) value;
double dblCheck = (double) nValue;
if ((dblCheck == value) && (!TaggedInt::IsOverflow(nValue)))
{
return TaggedInt::ToVarUnchecked(nValue);
}
return JavascriptNumber::NewInlined(value,scriptContext);
}
bool JavascriptNumber::TryGetInt32OrUInt32Value(const double value, int32 *const int32Value, bool *const isInt32)
{
Assert(int32Value);
Assert(isInt32);
if(value <= 0)
{
return *isInt32 = TryGetInt32Value(value, int32Value);
}
const uint32 i = static_cast<uint32>(value);
if(static_cast<double>(i) != value)
{
return false;
}
*int32Value = i;
*isInt32 = static_cast<int32>(i) >= 0;
return true;
}
bool JavascriptNumber::IsInt32(const double value)
{
int32 i;
return TryGetInt32Value(value, &i);
}
bool JavascriptNumber::IsInt32OrUInt32(const double value)
{
int32 i;
bool isInt32;
return TryGetInt32OrUInt32Value(value, &i, &isInt32);
}
bool JavascriptNumber::IsInt32_NoChecks(const Var number)
{
Assert(number);
Assert(Is(number));
return IsInt32(GetValue(number));
}
bool JavascriptNumber::IsInt32OrUInt32_NoChecks(const Var number)
{
Assert(number);
Assert(Is(number));
return IsInt32OrUInt32(GetValue(number));
}
int32 JavascriptNumber::GetNonzeroInt32Value_NoTaggedIntCheck(const Var object)
{
Assert(object);
Assert(!TaggedInt::Is(object));
int32 i;
return Is_NoTaggedIntCheck(object) && TryGetInt32Value(GetValue(object), &i) ? i : 0;
}
int32 JavascriptNumber::DirectPowIntInt(bool* isOverflow, int32 x, int32 y)
{
if (y < 0)
{
*isOverflow = true;
return 0;
}
uint32 uexp = static_cast<uint32>(y);
int32 result = 1;
while (true)
{
if ((uexp & 1) != 0)
{
if (Int32Math::Mul(result, x, &result))
{
*isOverflow = true;
break;
}
}
if ((uexp >>= 1) == 0)
{
*isOverflow = false;
break;
}
if (Int32Math::Mul(x, x, &x))
{
*isOverflow = true;
break;
}
}
return *isOverflow ? 0 : result;
}
double JavascriptNumber::DirectPowDoubleInt(double x, int32 y)
{
// For exponent in [-8, 8], aggregate the product according to binary representation
// of exponent. This acceleration may lead to significant deviation for larger exponent
if (y >= -8 && y <= 8)
{
uint32 uexp = static_cast<uint32>(y >= 0 ? y : -y);
for (double result = 1.0; ; x *= x)
{
if ((uexp & 1) != 0)
{
result *= x;
}
if ((uexp >>= 1) == 0)
{
return (y < 0 ? (1.0 / result) : result);
}
}
}
// always call pow(double, double) in C runtime which has a bug to process pow(double, int).
return ::pow(x, static_cast<double>(y));
}
#if _M_IX86
extern "C" double __cdecl __libm_sse2_pow(double, double);
static const double d1_0 = 1.0;
#if !ENABLE_NATIVE_CODEGEN
double JavascriptNumber::DirectPow(double x, double y)
{
return ::pow(x, y);
}
#else
#pragma warning(push)
// C4740: flow in or out of inline asm code suppresses global optimization
// It is fine to disable glot opt on this function which is mostly written in assembly
#pragma warning(disable:4740)
__declspec(naked)
double JavascriptNumber::DirectPow(double x, double y)
{
UNREFERENCED_PARAMETER(x);
UNREFERENCED_PARAMETER(y);
double savedX, savedY, result;
// This function is called directly from jitted, float-preferenced code.
// It looks for x and y in xmm0 and xmm1 and returns the result in xmm0.
// Check for pow(1, Infinity/NaN) and return NaN in that case;
// then check fast path of small integer exponent, otherwise,
// go to the fast CRT helper.
__asm {
// check y for 1.0
ucomisd xmm1, d1_0
jne pow_full
jp pow_full
ret
pow_full:
// Check y for non-finite value
pextrw eax, xmm1, 3
not eax
test eax, 0x7ff0
jne normal
// check for |x| == 1
movsd xmm2, xmm0
andpd xmm2, AbsDoubleCst
movsd xmm3, d1_0
ucomisd xmm2, xmm3
lahf
test ah, 68
jp normal
movsd xmm0, JavascriptNumber::k_Nan
ret
normal:
push ebp
mov ebp, esp // prepare stack frame for sub function call
sub esp, 0x40 // 4 variables, reserve 0x10 for 1
movsd savedX, xmm0
movsd savedY, xmm1
}
int intY;
if (TryGetInt32Value(savedY, &intY) && intY >= -8 && intY <= 8)
{
result = DirectPowDoubleInt(savedX, intY);
__asm {
movsd xmm0, result
}
}
else
{
__asm {
movsd xmm0, savedX
movsd xmm1, savedY
call dword ptr[__libm_sse2_pow]
}
}
__asm {
mov esp, ebp
pop ebp
ret
}
}
#pragma warning(pop)
#endif
#elif defined(_M_AMD64) || defined(_M_ARM32_OR_ARM64)
double JavascriptNumber::DirectPow(double x, double y)
{
if(y == 1.0)
{
return x;
}
// For AMD64/ARM calling convention already uses SSE2/VFP registers so we don't have to use assembler.
// We can't just use "if (0 == y)" because NaN compares
// equal to 0 according to our compilers.
int32 intY;
if (0 == NumberUtilities::LuLoDbl(y) && 0 == (NumberUtilities::LuHiDbl(y) & 0x7FFFFFFF))
{
// pow(x, 0) = 1 even if x is NaN.
return 1;
}
else if (1.0 == fabs(x) && !NumberUtilities::IsFinite(y))
{
// pow([+/-] 1, Infinity) = NaN according to javascript, but not for CRT pow.
return JavascriptNumber::NaN;
}
else if (TryGetInt32Value(y, &intY))
{
// check fast path
return DirectPowDoubleInt(x, intY);
}
return ::pow(x, y);
}
#else
double JavascriptNumber::DirectPow(double x, double y)
{
UNREFERENCED_PARAMETER(x);
UNREFERENCED_PARAMETER(y);
AssertMsg(0, "DirectPow NYI");
return 0;
}
#endif
Var JavascriptNumber::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
//
// Determine if called as a constructor or a function.
//
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr
|| JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch);
Var result;
if (args.Info.Count > 1)
{
if (TaggedInt::Is(args[1]) || JavascriptNumber::Is(args[1]))
{
result = args[1];
}
else if (JavascriptNumberObject::Is(args[1]))
{
result = JavascriptNumber::ToVarNoCheck(JavascriptNumberObject::FromVar(args[1])->GetValue(), scriptContext);
}
else
{
result = JavascriptNumber::ToVarNoCheck(JavascriptConversion::ToNumber(args[1], scriptContext), scriptContext);
}
}
else
{
result = TaggedInt::ToVarUnchecked(0);
}
if (callInfo.Flags & CallFlags_New)
{
JavascriptNumberObject* obj = scriptContext->GetLibrary()->CreateNumberObject(result);
result = obj;
}
return isCtorSuperCall ?
JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), RecyclableObject::FromVar(result), nullptr, scriptContext) :
result;
}
///----------------------------------------------------------------------------
/// ParseInt() returns an integer value dictated by the interpretation of the
/// given string argument according to the given radix argument, as described
/// in (ES6.0: S20.1.2.13).
///
/// Note: This is the same as the global parseInt() function as described in
/// (ES6.0: S18.2.5)
///
/// We actually reuse GlobalObject::EntryParseInt, so no implementation here.
///----------------------------------------------------------------------------
//Var JavascriptNumber::EntryParseInt(RecyclableObject* function, CallInfo callInfo, ...)
///----------------------------------------------------------------------------
/// ParseFloat() returns a Number value dictated by the interpretation of the
/// given string argument as a decimal literal, as described in
/// (ES6.0: S20.1.2.12).
///
/// Note: This is the same as the global parseFloat() function as described in
/// (ES6.0: S18.2.4)
///
/// We actually reuse GlobalObject::EntryParseFloat, so no implementation here.
///----------------------------------------------------------------------------
//Var JavascriptNumber::EntryParseFloat(RecyclableObject* function, CallInfo callInfo, ...)
///----------------------------------------------------------------------------
/// IsNaN() return true if the given value is a Number value and is NaN, as
/// described in (ES6.0: S20.1.2.4).
/// Note: This is the same as the global isNaN() function as described in
/// (ES6.0: S18.2.3) except that it does not coerce the argument to
/// Number, instead returns false if not already a Number.
///----------------------------------------------------------------------------
Var JavascriptNumber::EntryIsNaN(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Number_Constructor_isNaN);
if (args.Info.Count < 2 || !JavascriptOperators::IsAnyNumberValue(args[1]))
{
return scriptContext->GetLibrary()->GetFalse();
}
return JavascriptBoolean::ToVar(
JavascriptNumber::IsNan(JavascriptConversion::ToNumber(args[1],scriptContext)),
scriptContext);
}
///----------------------------------------------------------------------------
/// IsFinite() returns true if the given value is a Number value and is not
/// one of NaN, +Infinity, or -Infinity, as described in (ES6.0: S20.1.2.2).
/// Note: This is the same as the global isFinite() function as described in
/// (ES6.0: S18.2.2) except that it does not coerce the argument to
/// Number, instead returns false if not already a Number.
///----------------------------------------------------------------------------
Var JavascriptNumber::EntryIsFinite(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Number_Constructor_isFinite);
if (args.Info.Count < 2 || !JavascriptOperators::IsAnyNumberValue(args[1]))
{
return scriptContext->GetLibrary()->GetFalse();
}
return JavascriptBoolean::ToVar(
NumberUtilities::IsFinite(JavascriptConversion::ToNumber(args[1],scriptContext)),
scriptContext);
}
///----------------------------------------------------------------------------
/// IsInteger() returns true if the given value is a Number value and is an
/// integer, as described in (ES6.0: S20.1.2.3).
///----------------------------------------------------------------------------
Var JavascriptNumber::EntryIsInteger(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Number_Constructor_isInteger);
if (args.Info.Count < 2 || !JavascriptOperators::IsAnyNumberValue(args[1]))
{
return scriptContext->GetLibrary()->GetFalse();
}
double number = JavascriptConversion::ToNumber(args[1], scriptContext);
return JavascriptBoolean::ToVar(
number == JavascriptConversion::ToInteger(args[1], scriptContext) &&
NumberUtilities::IsFinite(number),
scriptContext);
}
///----------------------------------------------------------------------------
/// IsSafeInteger() returns true if the given value is a Number value and is an
/// integer, as described in (ES6.0: S20.1.2.5).
///----------------------------------------------------------------------------
Var JavascriptNumber::EntryIsSafeInteger(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Number_Constructor_isSafeInteger);
if (args.Info.Count < 2 || !JavascriptOperators::IsAnyNumberValue(args[1]))
{
return scriptContext->GetLibrary()->GetFalse();
}
double number = JavascriptConversion::ToNumber(args[1], scriptContext);
return JavascriptBoolean::ToVar(
number == JavascriptConversion::ToInteger(args[1], scriptContext) &&
number >= Js::Math::MIN_SAFE_INTEGER && number <= Js::Math::MAX_SAFE_INTEGER,
scriptContext);
}
Var JavascriptNumber::EntryToExponential(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toExponential"));
}
AssertMsg(args.Info.Count > 0, "negative arg count");
// spec implies ToExp is not generic. 'this' must be a number
double value;
if (!GetThisValue(args[0], &value))
{
if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
{
Var result;
if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryToExponential, args, &result))
{
return result;
}
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toExponential"));
}
JavascriptString * nanF;
if (nullptr != (nanF = ToStringNanOrInfinite(value, scriptContext)))
return nanF;
// If the Fraction param. is not present we have to output as many fractional digits as we can
int fractionDigits = -1;
if(args.Info.Count > 1)
{
//use the first arg as the fraction digits, ignore the rest.
Var aFractionDigits = args[1];
bool noRangeCheck = false;
// shortcut for tagged int's
if(TaggedInt::Is(aFractionDigits))
{
fractionDigits = TaggedInt::ToInt32(aFractionDigits);
}
else if(JavascriptOperators::GetTypeId(aFractionDigits) == TypeIds_Undefined)
{
// fraction undefined -> digits = -1, output as many fractional digits as we can
noRangeCheck = true;
}
else
{
fractionDigits = (int)JavascriptConversion::ToInteger(aFractionDigits, scriptContext);
}
if(!noRangeCheck && (fractionDigits < 0 || fractionDigits >20))
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_FractionOutOfRange);
}
}
return FormatDoubleToString(value, Js::NumberUtilities::FormatExponential, fractionDigits, scriptContext);
}
Var JavascriptNumber::EntryToFixed(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toFixed"));
}
AssertMsg(args.Info.Count > 0, "negative arg count");
// spec implies ToFixed is not generic. 'this' must be a number
double value;
if (!GetThisValue(args[0], &value))
{
if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
{
Var result;
if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryToFixed, args, &result))
{
return result;
}
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toFixed"));
}
int fractionDigits = 0;
bool isFractionDigitsInfinite = false;
if(args.Info.Count > 1)
{
//use the first arg as the fraction digits, ignore the rest.
Var aFractionDigits = args[1];
// shortcut for tagged int's
if(TaggedInt::Is(aFractionDigits))
{
fractionDigits = TaggedInt::ToInt32(aFractionDigits);
}
else if(JavascriptOperators::GetTypeId(aFractionDigits) == TypeIds_Undefined)
{
// fraction digits = 0
}
else
{
double fractionDigitsRaw = JavascriptConversion::ToInteger(aFractionDigits, scriptContext);
isFractionDigitsInfinite =
fractionDigitsRaw == JavascriptNumber::NEGATIVE_INFINITY ||
fractionDigitsRaw == JavascriptNumber::POSITIVE_INFINITY;
fractionDigits = (int)fractionDigitsRaw;
}
}
if (fractionDigits < 0 || fractionDigits > 20)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_FractionOutOfRange);
}
if(IsNan(value))
{
return ToStringNan(scriptContext);
}
if(value >= 1e21)
{
return ToStringRadix10(value, scriptContext);
}
return FormatDoubleToString(value, NumberUtilities::FormatFixed, fractionDigits, scriptContext);
}
Var JavascriptNumber::EntryToPrecision(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toPrecision"));
}
AssertMsg(args.Info.Count > 0, "negative arg count");
// spec implies ToPrec is not generic. 'this' must be a number
double value;
if (!GetThisValue(args[0], &value))
{
if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
{
Var result;
if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryToPrecision, args, &result))
{
return result;
}
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toPrecision"));
}
if(args.Info.Count < 2 || JavascriptOperators::GetTypeId(args[1]) == TypeIds_Undefined)
{
return JavascriptConversion::ToString(args[0], scriptContext);
}
int precision;
Var aPrecision = args[1];
if(TaggedInt::Is(aPrecision))
{
precision = TaggedInt::ToInt32(aPrecision);
}
else
{
precision = (int) JavascriptConversion::ToInt32(aPrecision, scriptContext);
}
JavascriptString * nanF;
if (nullptr != (nanF = ToStringNanOrInfinite(value, scriptContext)))
{
return nanF;
}
if(precision < 1 || precision > 21)
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_PrecisionOutOfRange);
}
return FormatDoubleToString(value, NumberUtilities::FormatPrecision, precision, scriptContext);
}
Var JavascriptNumber::EntryToLocaleString(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toLocaleString"));
}
return JavascriptNumber::ToLocaleStringIntl(args, callInfo, scriptContext);
}
JavascriptString* JavascriptNumber::ToLocaleStringIntl(Var* values, CallInfo callInfo, ScriptContext* scriptContext)
{
Assert(values);
ArgumentReader args(&callInfo, values);
return JavascriptNumber::ToLocaleStringIntl(args, callInfo, scriptContext);
}
JavascriptString* JavascriptNumber::ToLocaleStringIntl(ArgumentReader& args, CallInfo callInfo, ScriptContext* scriptContext)
{
Assert(scriptContext);
#ifdef ENABLE_INTL_OBJECT
if(CONFIG_FLAG(IntlBuiltIns) && scriptContext->IsIntlEnabled()){
EngineInterfaceObject* nativeEngineInterfaceObj = scriptContext->GetLibrary()->GetEngineInterfaceObject();
if (nativeEngineInterfaceObj)
{
IntlEngineInterfaceExtensionObject* intlExtensionObject = static_cast<IntlEngineInterfaceExtensionObject*>(nativeEngineInterfaceObj->GetEngineExtension(EngineInterfaceExtensionKind_Intl));
JavascriptFunction* func = intlExtensionObject->GetNumberToLocaleString();
if (func)
{
return JavascriptString::FromVar(func->CallFunction(args));
}
// Initialize Number.prototype.toLocaleString
scriptContext->GetLibrary()->InitializeIntlForNumberPrototype();
func = intlExtensionObject->GetNumberToLocaleString();
if (func)
{
return JavascriptString::FromVar(func->CallFunction(args));
}
}
}
#endif
double value;
if (!GetThisValue(args[0], &value))
{
if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
{
Var result;
if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryToLocaleString, args, &result))
{
return JavascriptString::FromVar(result);
}
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toLocaleString"));
}
return JavascriptNumber::ToLocaleString(value, scriptContext);
}
Var JavascriptNumber::EntryToString(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toString"));
}
// Optimize base 10 of TaggedInt numbers
if (TaggedInt::Is(args[0]) && (args.Info.Count == 1 || (TaggedInt::Is(args[1]) && TaggedInt::ToInt32(args[1]) == 10)))
{
return scriptContext->GetIntegerString(args[0]);
}
double value;
if (!GetThisValue(args[0], &value))
{
if (JavascriptOperators::GetTypeId(args[0]) == TypeIds_HostDispatch)
{
Var result;
if (RecyclableObject::FromVar(args[0])->InvokeBuiltInOperationRemotely(EntryToString, args, &result))
{
return result;
}
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.toString"));
}
int radix = 10;
if(args.Info.Count > 1)
{
//use the first arg as the radix, ignore the rest.
Var aRadix = args[1];
// shortcut for tagged int's
if(TaggedInt::Is(aRadix))
{
radix = TaggedInt::ToInt32(aRadix);
}
else if(JavascriptOperators::GetTypeId(aRadix) != TypeIds_Undefined)
{
radix = (int)JavascriptConversion::ToInteger(aRadix,scriptContext);
}
}
if(10 == radix)
{
return ToStringRadix10(value, scriptContext);
}
if( radix < 2 || radix >36 )
{
JavascriptError::ThrowRangeError(scriptContext, JSERR_FunctionArgument_Invalid, _u("Number.prototype.toString"));
}
return ToStringRadixHelper(value, radix, scriptContext);
}
Var JavascriptNumber::EntryValueOf(RecyclableObject* function, CallInfo callInfo, ...)
{
PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
ARGUMENTS(args, callInfo);
ScriptContext* scriptContext = function->GetScriptContext();
Var value = args[0];
Assert(!(callInfo.Flags & CallFlags_New));
if (args.Info.Count == 0)
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.valueOf"));
}
//avoid creation of a new Number
if (TaggedInt::Is(value) || JavascriptNumber::Is_NoTaggedIntCheck(value))
{
return value;
}
else if (JavascriptNumberObject::Is(value))
{
JavascriptNumberObject* obj = JavascriptNumberObject::FromVar(value);
return CrossSite::MarshalVar(scriptContext, obj->Unwrap());
}
else if (Js::JavascriptOperators::GetTypeId(value) == TypeIds_Int64Number)
{
return value;
}
else if (Js::JavascriptOperators::GetTypeId(value) == TypeIds_UInt64Number)
{
return value;
}
else
{
if (JavascriptOperators::GetTypeId(value) == TypeIds_HostDispatch)
{
Var result;
if (RecyclableObject::FromVar(value)->InvokeBuiltInOperationRemotely(EntryValueOf, args, &result))
{
return result;
}
}
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedNumber, _u("Number.prototype.valueOf"));
}
}
static const int bufSize = 256;
JavascriptString* JavascriptNumber::ToString(double value, ScriptContext* scriptContext)
{
char16 szBuffer[bufSize];
int cchWritten = swprintf_s(szBuffer, _countof(szBuffer), _u("%g"), value);
return JavascriptString::NewCopyBuffer(szBuffer, cchWritten, scriptContext);
}
JavascriptString* JavascriptNumber::ToStringNanOrInfiniteOrZero(double value, ScriptContext* scriptContext)
{
JavascriptString* nanF;
if (nullptr != (nanF = ToStringNanOrInfinite(value, scriptContext)))
{
return nanF;
}
if (IsZero(value))
{
return scriptContext->GetLibrary()->GetCharStringCache().GetStringForCharA('0');
}
return nullptr;
}
JavascriptString* JavascriptNumber::ToStringRadix10(double value, ScriptContext* scriptContext)
{
JavascriptString* string = ToStringNanOrInfiniteOrZero(value, scriptContext);
if (string != nullptr)
{
return string;
}
string = scriptContext->GetLastNumberToStringRadix10(value);
if (string == nullptr)
{
char16 szBuffer[bufSize];
if(!Js::NumberUtilities::FNonZeroFiniteDblToStr(value, szBuffer, bufSize))
{
Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
}
string = JavascriptString::NewCopySz(szBuffer, scriptContext);
scriptContext->SetLastNumberToStringRadix10(value, string);
}
return string;
}
JavascriptString* JavascriptNumber::ToStringRadixHelper(double value, int radix, ScriptContext* scriptContext)
{
Assert(radix != 10);
Assert(radix >= 2 && radix <= 36);
JavascriptString* string = ToStringNanOrInfiniteOrZero(value, scriptContext);
if (string != nullptr)
{
return string;
}
char16 szBuffer[bufSize];
if (!Js::NumberUtilities::FNonZeroFiniteDblToStr(value, radix, szBuffer, _countof(szBuffer)))
{
Js::JavascriptError::ThrowOutOfMemoryError(scriptContext);
}
return JavascriptString::NewCopySz(szBuffer, scriptContext);
}
BOOL JavascriptNumber::GetThisValue(Var aValue, double* pDouble)
{
TypeId typeId = JavascriptOperators::GetTypeId(aValue);
if (typeId == TypeIds_Null || typeId == TypeIds_Undefined)
{
return FALSE;
}
if (TaggedInt::Is(aValue))
{
*pDouble = TaggedInt::ToDouble(aValue);
return TRUE;
}
else if (Js::JavascriptOperators::GetTypeId(aValue) == TypeIds_Int64Number)
{
*pDouble = (double)JavascriptInt64Number::FromVar(aValue)->GetValue();
return TRUE;
}
else if (Js::JavascriptOperators::GetTypeId(aValue) == TypeIds_UInt64Number)
{
*pDouble = (double)JavascriptUInt64Number::FromVar(aValue)->GetValue();
return TRUE;
}
else if (JavascriptNumber::Is_NoTaggedIntCheck(aValue))
{
*pDouble = JavascriptNumber::GetValue(aValue);
return TRUE;
}
else if (JavascriptNumberObject::Is(aValue))
{
JavascriptNumberObject* obj = JavascriptNumberObject::FromVar(aValue);
*pDouble = obj->GetValue();
return TRUE;
}
else
{
return FALSE;
}
}
JavascriptString* JavascriptNumber::ToLocaleStringNanOrInfinite(double value, ScriptContext* scriptContext)
{
if (!NumberUtilities::IsFinite(value))
{
if (IsNan(value))
{
return ToStringNan(scriptContext);
}
BSTR bstr = nullptr;
if (IsPosInf(value))
{
bstr = BstrGetResourceString(IDS_INFINITY);
}
else
{
AssertMsg(IsNegInf(value), "bad handling of infinite number");
bstr = BstrGetResourceString(IDS_MINUSINFINITY);
}
if (bstr == nullptr)
{
Js::JavascriptError::ThrowTypeError(scriptContext, VBSERR_InternalError);
}
JavascriptString* str = JavascriptString::NewCopyBuffer(bstr, SysStringLen(bstr), scriptContext);
SysFreeString(bstr);
return str;
}
return nullptr;
}
JavascriptString* JavascriptNumber::ToLocaleString(double value, ScriptContext* scriptContext)
{
WCHAR szRes[bufSize];
WCHAR * pszRes = NULL;
WCHAR * pszToBeFreed = NULL;
size_t count;
if (!Js::NumberUtilities::IsFinite(value))
{
//
// +- Infinity : use the localized string
// NaN would be returned as NaN
//
return ToLocaleStringNanOrInfinite(value, scriptContext);
}
JavascriptString *result = nullptr;
JavascriptString *dblStr = JavascriptString::FromVar(FormatDoubleToString(value, NumberUtilities::FormatFixed, -1, scriptContext));
const char16* szValue = dblStr->GetSz();
const size_t szLength = dblStr->GetLength();
pszRes = szRes;
count = Numbers::Utility::NumberToDefaultLocaleString(szValue, szLength, pszRes, bufSize);
if( count == 0 )
{
return dblStr;
}
else
{
if( count > bufSize )
{
pszRes = pszToBeFreed = HeapNewArray(char16, count);
count = Numbers::Utility::NumberToDefaultLocaleString(szValue, szLength, pszRes, count);
if ( count == 0 )
{
AssertMsg(false, "GetNumberFormatEx failed");
JavascriptError::ThrowError(scriptContext, VBSERR_InternalError);
}
}
if ( count != 0 )
{
result = JavascriptString::NewCopySz(pszRes, scriptContext);
}
}
if ( pszToBeFreed )
{
HeapDeleteArray(count, pszToBeFreed);
}
return result;
}
Var JavascriptNumber::CloneToScriptContext(Var aValue, ScriptContext* requestContext)
{
return JavascriptNumber::New(JavascriptNumber::GetValue(aValue), requestContext);
}
#if !FLOATVAR
Var JavascriptNumber::BoxStackNumber(Var instance, ScriptContext* scriptContext)
{
if (ThreadContext::IsOnStack(instance) && JavascriptNumber::Is(instance))
{
return BoxStackInstance(JavascriptNumber::FromVar(instance), scriptContext);
}
else
{
return instance;
}
}
Var JavascriptNumber::BoxStackInstance(Var instance, ScriptContext* scriptContext)
{
Assert(ThreadContext::IsOnStack(instance));
double value = JavascriptNumber::FromVar(instance)->GetValue();
return JavascriptNumber::New(value, scriptContext);
}
JavascriptNumber * JavascriptNumber::NewUninitialized(Recycler * recycler)
{
return RecyclerNew(recycler, JavascriptNumber, VirtualTableInfoCtorValue);
}
#endif
}