//-------------------------------------------------------------------------------------------------------
// 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 "Language/JavascriptMathOperators.h"
#include "Math/CrtSSE2Math.h"

#if defined(_M_IX86) || defined(_M_X64)
#pragma intrinsic(_mm_round_sd)
#endif

void UCrtC99MathApis::Ensure()
{
    if (m_isInit)
    {
        return;
    }

    DelayLoadLibrary::EnsureFromSystemDirOnly();

    if (IsAvailable())
    {
        m_pfnlog2  = (PFNMathFn)GetFunction("log2");
        m_pfnlog1p = (PFNMathFn)GetFunction("log1p");
        m_pfnexpm1 = (PFNMathFn)GetFunction("expm1");
        m_pfnacosh = (PFNMathFn)GetFunction("acosh");
        m_pfnasinh = (PFNMathFn)GetFunction("asinh");
        m_pfnatanh = (PFNMathFn)GetFunction("atanh");
        m_pfntrunc = (PFNMathFn)GetFunction("trunc");
        m_pfncbrt  = (PFNMathFn)GetFunction("cbrt");

        if (m_pfnlog2 == nullptr ||
            m_pfnlog1p == nullptr ||
            m_pfnexpm1 == nullptr ||
            m_pfnacosh == nullptr ||
            m_pfnasinh == nullptr ||
            m_pfnatanh == nullptr ||
            m_pfntrunc == nullptr ||
            m_pfncbrt == nullptr)
        {
            // If any of the APIs fail to load then presume the entire module is bogus and free it
            FreeLibrary(m_hModule);
            m_hModule = nullptr;
        }
    }
}

namespace Js
{
    const double Math::PI = 3.1415926535897931;
    const double Math::E = 2.7182818284590451;
    const double Math::LN10 = 2.3025850929940459;
    const double Math::LN2 = 0.69314718055994529;
    const double Math::LOG2E = 1.4426950408889634;
    const double Math::LOG10E = 0.43429448190325182;
    const double Math::SQRT1_2 = 0.70710678118654757;
    const double Math::SQRT2 = 1.4142135623730951;
    const double Math::EPSILON = 2.2204460492503130808472633361816e-16;
    const double Math::MAX_SAFE_INTEGER = 9007199254740991.0;
    const double Math::MIN_SAFE_INTEGER = -9007199254740991.0;


    ///----------------------------------------------------------------------------
    /// Abs() returns the absolute value of the given number, as described in
    /// (ES5.0: S15.8.2.1).
    ///----------------------------------------------------------------------------

    Var Math::Abs(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 >= 2)
        {
            Var arg = args[1];

            if (TaggedInt::Is(arg))
            {
#if defined(_M_X64_OR_ARM64)
                __int64 result = ::_abs64(TaggedInt::ToInt32(arg));
#else
                __int32 result = ::abs(TaggedInt::ToInt32(arg));
#endif
                return JavascriptNumber::ToVar(result, scriptContext);
            }

            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Abs(x);
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Abs(double x)
    {
        // ::fabs if linked from UCRT changes FPU ctrl word for NaN input
        if (NumberUtilities::IsNan(x))
        {
            // canonicalize to 0xFFF8000..., so we can tag correctly on x64.
            return NumberConstants::NaN;
        }
        return ::fabs(x);
    }

    ///----------------------------------------------------------------------------
    /// Acos() returns the arc-cosine of the given angle in radians, as described
    /// in (ES5.0: S15.8.2.2).
    ///----------------------------------------------------------------------------

    Var Math::Acos(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Acos(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Acos(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_acos]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::acos(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Asin() returns the arc-sine of the given angle in radians, as described in
    /// (ES5.0: S15.8.2.3).
    ///----------------------------------------------------------------------------

    Var Math::Asin(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Asin(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Asin(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_asin]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::asin(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Atan() returns the arc-tangent of the given angle in radians, as described
    /// in (ES5.0: S15.8.2.4).
    ///----------------------------------------------------------------------------

    Var Math::Atan(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Atan(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Atan(double x)
    {
        double result;
#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_atan]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::atan(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Atan2() returns the arc-tangent of the angle described by the given x and y
    /// lengths, as described in (ES5.0: S15.8.2.5).
    ///----------------------------------------------------------------------------

    Var Math::Atan2(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));

        if (args.Info.Count >= 3)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);
            double y = JavascriptConversion::ToNumber(args[2], scriptContext);
            double result = Math::Atan2(x,y);
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Atan2( double x, double y )
    {
        double result = 0;
        enum
        {
            kntP0,
            kntP1,
            kntPInf,
            kntN0,
            kntN1,
            kntNInf,
            kntLim
        };

        int nt1, nt2;

        if( NumberUtilities::IsFinite( x ) )
        {
            nt1 = ( 0 == x ) ? kntP0 : kntP1;
        }
        else if( JavascriptNumber::IsNan( x ) )
        {
            result = x;
            goto LDone;
        }
        else
        {
            nt1 = kntPInf;
        }

        // TODO (abchatra): Remove old macros LuHiDbl and LuLoDbl.
        if( NumberUtilities::LuHiDbl( x ) & 0x80000000 )
        {
            nt1 += kntN0;
        }

        if( NumberUtilities::IsFinite( y ) )
        {
            nt2 = ( 0 == y ) ? kntP0 : kntP1;
        }
        else if( JavascriptNumber::IsNan( y ) )
        {
            result = y;
            goto LDone;
        }
        else
        {
            nt2 = kntPInf;
        }

        if( NumberUtilities::LuHiDbl( y ) & 0x80000000 )
        {
            nt2 += kntN0;
        }

        // There are a bunch of cases that the CRT atan2 doesn't do correctly.
#define NTP(a, b) ((a) * kntLim + (b))
        switch( NTP( nt1, nt2 ) )
        {
        default:
#if 0
            // This Atan2 gives slightly different results.  Disable as it doesn't appear to
            // be an important perf scenario.
            if( AutoSystemInfo::Data.SSE2Available() )
            {
                _asm {
                    movsd xmm0, x
                        movsd xmm1, y
                        call dword ptr[__libm_sse2_atan2]
                        movsd result, xmm0
                }
            }
            else
#endif
            {
                result = ::atan2( x, y );
            }
            break;

        case NTP( kntPInf, kntPInf ):
            // +PI / 4 (+0.7853981633974483) : some compilers have problems with
            // long decimal constants!
            NumberUtilities::LuHiDbl( result ) = 0x3FE921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;
        case NTP( kntPInf, kntNInf ):
            // +3 * PI / 4 (+2.356194490192345)
            NumberUtilities::LuHiDbl( result ) = 0x4002D97C;
            NumberUtilities::LuLoDbl( result ) = 0x7F3321D2;
            break;
        case NTP( kntNInf, kntPInf ):
            // -PI / 4 (-0.7853981633974483)
            NumberUtilities::LuHiDbl( result ) = 0xBFE921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;
        case NTP( kntNInf, kntNInf ):
            // -3 * PI / 4 (-2.356194490192345)
            NumberUtilities::LuHiDbl( result ) = 0xC002D97C;
            NumberUtilities::LuLoDbl( result ) = 0x7F3321D2;
            break;

        case NTP( kntP0, kntP0 ):
            // +0
            result = 0;
            break;
        case NTP( kntP0, kntN0 ):
            // +PI (+3.141592653589793)
            NumberUtilities::LuHiDbl( result ) = 0x400921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;
        case NTP( kntN1, kntPInf ):
        case NTP( kntN0, kntPInf ):
        case NTP( kntN0, kntP1 ):
        case NTP( kntN0, kntP0 ):
            // -0
            NumberUtilities::LuHiDbl( result ) = 0x80000000;
            NumberUtilities::LuLoDbl( result ) = 0;
            break;
        case NTP( kntN0, kntN0 ):
            // -PI (-3.141592653589793)
            NumberUtilities::LuHiDbl( result ) = 0xC00921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;

        case NTP( kntPInf, kntN1 ):
        case NTP( kntPInf, kntN0 ):
            // +PI / 2 (+1.5707963267948966)
            NumberUtilities::LuHiDbl( result ) = 0x3FF921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;
        case NTP( kntNInf, kntP1 ):
        case NTP( kntNInf, kntP0 ):
            // -PI / 2 (-1.5707963267948966)
            NumberUtilities::LuHiDbl( result ) = 0xBFF921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;
        case NTP( kntN1, kntNInf ):
        case NTP( kntN0, kntNInf ):
        case NTP( kntN0, kntN1 ):
            // -PI (-3.141592653589793)
            NumberUtilities::LuHiDbl( result ) = 0xC00921FB;
            NumberUtilities::LuLoDbl( result ) = 0x54442D18;
            break;
        }
LDone:
        return result;
    }

    ///----------------------------------------------------------------------------
    /// Ceil() returns the smallest (closest to -Inf) number value that is not less
    /// than x and is equal to an integer, as described in (ES5.0: S15.8.2.6).
    ///----------------------------------------------------------------------------

#pragma warning(push)
#pragma warning(disable:4700)    // uninitialized local variable 'output' used,  for call to _mm_ceil_sd

    Var Math::Ceil(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));

        if (args.Info.Count >= 2)
        {
            Var input = args[1];

            if (TaggedInt::Is(input))
            {
                return input;
            }

            double x = JavascriptConversion::ToNumber(args[1], scriptContext);
#if defined(_M_ARM32_OR_ARM64)
            if (Js::JavascriptNumber::IsNan(x))
            {
                return scriptContext->GetLibrary()->GetNaN();
            }
#endif

#if defined(_M_IX86) || defined(_M_X64)
            if (AutoSystemInfo::Data.SSE4_1Available())
            {
                __m128d input, output;
                input = _mm_load_sd(&x);
#pragma prefast(suppress:6001, "Signature of _mm_ceil_sd intrinsic confuses prefast, output in the parameter list is not used, it is the dst of the intrinsic")
                output = _mm_ceil_sd(output, input);
                int intResult = _mm_cvtsd_si32(output);

                if (TaggedInt::IsOverflow(intResult) || intResult == 0 || intResult == 0x80000000)
                {
                    double dblResult;
                    _mm_store_sd(&dblResult, output);

                    if (intResult == 0 && !JavascriptNumber::IsNegZero(dblResult))
                    {
                        return JavascriptNumber::ToVar(0, scriptContext);
                    }

                    Assert(dblResult == ::ceil(x) || Js::JavascriptNumber::IsNan(dblResult));

                    return JavascriptNumber::ToVarNoCheck(dblResult, scriptContext);
                }
                else
                {
                    return JavascriptNumber::ToVar(intResult, scriptContext);
                }
            }
            else
#endif
            {
                double result = ::ceil(x);

                intptr_t intResult = (intptr_t)result;

                if (TaggedInt::IsOverflow(intResult) || JavascriptNumber::IsNegZero(result))
                {
                    return JavascriptNumber::ToVarNoCheck(result, scriptContext);
                }
                else
                {
                    return JavascriptNumber::ToVar(intResult, scriptContext);
                }
            }
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }
#pragma warning(pop)

    ///----------------------------------------------------------------------------
    /// Cos() returns the cosine of the given angle in radians, as described in
    /// (ES5.0: S15.8.2.7).
    ///----------------------------------------------------------------------------

    Var Math::Cos(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Cos(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Cos(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_cos]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::cos(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Exp() returns e^x, as described in (ES5.0: S15.8.2.8).
    ///----------------------------------------------------------------------------

    Var Math::Exp(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Exp(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Exp(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_exp]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::exp(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Floor() returns the greatest (closest to +Inf) number value that is not
    /// greater than x and is equal to an integer, as described in
    /// (ES5.0: S15.8.2.9).
    ///----------------------------------------------------------------------------

    Var Math::Floor(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));

        if (args.Info.Count >= 2)
        {
            Var input = args[1];

            if (TaggedInt::Is(input))
            {
                return input;
            }

            double x = JavascriptConversion::ToNumber(input, scriptContext);
#if defined(_M_ARM32_OR_ARM64)
            if (Js::JavascriptNumber::IsNan(x))
            {
                return scriptContext->GetLibrary()->GetNaN();
            }
#endif
            return Math::FloorDouble(x, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

#pragma warning(push)
#pragma warning(disable:4700)  // // uninitialized local variable 'output' used, for call to _mm_floor_sd
    Var __inline Math::FloorDouble(double d, ScriptContext *scriptContext)
    {
#if defined(_M_IX86) || defined(_M_X64)
        if (AutoSystemInfo::Data.SSE4_1Available())
        {
            __m128d input, output;

            int intResult;

            input = _mm_load_sd(&d);
            if (d >= 0.0)
            {
                output = input;
            }
            else
            {
#pragma prefast(suppress:6001, "Signature of _mm_floor_sd intrinsic confuses prefast, output in the parameter list is not used, it is the dst of the intrinsic")
                output = _mm_floor_sd(output, input);
            }
            intResult = _mm_cvttsd_si32(output);

            if (TaggedInt::IsOverflow(intResult) || intResult == 0x80000000 || JavascriptNumber::IsNegZero(d))
            {
                double dblResult;
                if (d >= 0.0)
                {
                    output = _mm_floor_sd(output, input);
                }
                _mm_store_sd(&dblResult, output);

                Assert(dblResult == ::floor(d) || Js::JavascriptNumber::IsNan(dblResult));

                return JavascriptNumber::ToVarNoCheck(dblResult, scriptContext);
            }
            else
            {
                Assert(intResult == (int)::floor(d));
                return JavascriptNumber::ToVar(intResult, scriptContext);
            }
        }
        else
#endif
        {
            intptr_t intResult;

            if (d >= 0.0)
            {
                intResult = (intptr_t)d;
            }
            else
            {
                d = ::floor(d);
                intResult = (intptr_t)d;
            }

            if (TaggedInt::IsOverflow(intResult) || JavascriptNumber::IsNegZero(d))
            {
                return JavascriptNumber::ToVarNoCheck(::floor(d), scriptContext);
            }
            else
            {
                return JavascriptNumber::ToVar(intResult, scriptContext);
            }
        }
    }
#pragma warning(pop)
    ///----------------------------------------------------------------------------
    /// Log() returns the natural logarithm of x, as described in
    /// (ES5.0: S15.8.2.10).
    ///----------------------------------------------------------------------------

    Var Math::Log(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Log(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Log(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_log]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::log(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Max() returns the maximum of a series of numbers, as described in
    /// (ES5.0: S15.8.2.11):
    /// - Arg:0 = "this"
    /// - Arg:1-n = Values to compare
    ///----------------------------------------------------------------------------

    Var Math::Max(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));

        if (args.Info.Count <= 1)
        {
            return scriptContext->GetLibrary()->GetNegativeInfinite();
        }
        else if (args.Info.Count == 2)
        {
            double result = JavascriptConversion::ToNumber(args[1], scriptContext);
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else if (args.Info.Count == 3)
        {
            if (TaggedInt::Is(args[1]) && TaggedInt::Is(args[2]))
            {
                return TaggedInt::ToVarUnchecked(max(TaggedInt::ToInt32(args[1]), TaggedInt::ToInt32(args[2])));
            }
        }

        double current = JavascriptConversion::ToNumber(args[1], scriptContext);
        if(JavascriptNumber::IsNan(current))
        {
            return scriptContext->GetLibrary()->GetNaN();
        }

        for (uint idxArg = 2; idxArg < args.Info.Count; idxArg++)
        {
            double compare = JavascriptConversion::ToNumber(args[idxArg], scriptContext);
            if(JavascriptNumber::IsNan(compare))
            {
                return scriptContext->GetLibrary()->GetNaN();
            }
            if((JavascriptNumber::IsNegZero(current) && compare == 0) ||
                current < compare )
            {
                current = compare;
            }
        }

        return JavascriptNumber::ToVarNoCheck(current, scriptContext);
    }


    ///----------------------------------------------------------------------------
    /// Min() returns the minimum of a series of numbers, as described in
    /// (ES5.0: S15.8.2.12):
    /// - Arg:0 = "this"
    /// - Arg:1-n = Values to compare
    ///----------------------------------------------------------------------------

    Var Math::Min(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));

        if (args.Info.Count <= 1)
        {
            return scriptContext->GetLibrary()->GetPositiveInfinite();
        }
        else if (args.Info.Count == 2)
        {
            double result = JavascriptConversion::ToNumber(args[1], scriptContext);
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else if (args.Info.Count == 3)
        {
            if (TaggedInt::Is(args[1]) && TaggedInt::Is(args[2]))
            {
                return TaggedInt::ToVarUnchecked(min(TaggedInt::ToInt32(args[1]), TaggedInt::ToInt32(args[2])));
            }
        }

        double current = JavascriptConversion::ToNumber(args[1], scriptContext);
        if(JavascriptNumber::IsNan(current))
        {
            return scriptContext->GetLibrary()->GetNaN();
        }

        for (uint idxArg = 2; idxArg < args.Info.Count; idxArg++)
        {
            double compare = JavascriptConversion::ToNumber(args[idxArg], scriptContext);
            if(JavascriptNumber::IsNan(compare))
            {
                return scriptContext->GetLibrary()->GetNaN();
            }
            if((JavascriptNumber::IsNegZero(compare) && current == 0) ||
                current > compare )
            {
                current = compare;
            }
        }

        return JavascriptNumber::ToVarNoCheck(current, scriptContext);
    }


    ///----------------------------------------------------------------------------
    /// Pow() returns x ^ y, as described in (ES5.0: S15.8.2.13).
    ///----------------------------------------------------------------------------

    Var Math::Pow(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));

        if (args.Info.Count >= 3)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);
            double y = JavascriptConversion::ToNumber(args[2], scriptContext);
            double result = Math::Pow( x, y );
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Pow( double x, double y )
    {
        double result = 0;

#if defined(_M_IX86)

        // We can't just use "if (0 == y)" because NaN compares
        // equal to 0 according to our compilers.
        if( 0 == NumberUtilities::LuLoDbl( y ) && 0 == ( NumberUtilities::LuHiDbl( y ) & 0x7FFFFFFF ) )
        {
            // Result is 1 even if x is NaN.
            result = 1;
        }
        else if( 1.0 == Math::Abs( x ) && !NumberUtilities::IsFinite( y ) )
        {
            result = JavascriptNumber::NaN;
        }
        else
        {
            if( AutoSystemInfo::Data.SSE2Available() )
            {
                _asm {
                    movsd xmm0, x
                        movsd xmm1, y
                        call dword ptr[__libm_sse2_pow]
                        movsd result, xmm0
                }
            }
            else
            {
                result = ::pow( x, y );
            }
        }
#else
        result = JavascriptNumber::DirectPow( x, y );
#endif
        return result;
    }



    ///----------------------------------------------------------------------------
    /// Random() returns a random number 0 <= n < 1.0, as described in
    /// (ES5.0: S15.8.2.14).
    ///----------------------------------------------------------------------------

    Var Math::Random(RecyclableObject* function, CallInfo callInfo, ...)
    {
        PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

        AssertMsg(callInfo.Count > 0, "Should always have implicit 'this'");
        ScriptContext* scriptContext = function->GetScriptContext();

        Assert(!(callInfo.Flags & CallFlags_New));

        double res = JavascriptMath::Random(scriptContext);

        return JavascriptNumber::ToVarNoCheck(res, scriptContext);
    }


    ///----------------------------------------------------------------------------
    /// Round() returns the number value that is closest to x and is equal to an
    /// integer, as described in (ES5.0: S15.8.2.15).
    ///----------------------------------------------------------------------------

    Var Math::Round(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));

        if (args.Info.Count >= 2)
        {
            Var input = args[1];

            if (TaggedInt::Is(input))
            {
                return input;
            }

            double x = JavascriptConversion::ToNumber(args[1], scriptContext);
            if(JavascriptNumber::IsNan(x))
            {
                return scriptContext->GetLibrary()->GetNaN();
            }

            // for doubles, if x >= 2^52 or <= -2^52, x must be an integer, and adding 0.5 will overflow the
            // integer to the next one. Therefore, we directly return x.
            if (x == 0.0 || !NumberUtilities::IsFinite(x) || x >= 4503599627370496.0 || x <= -4503599627370496.0)
            {
                // 0.0 catches the -0 case...
                return JavascriptNumber::ToVarNoCheck(x, scriptContext);
            }

            if (x > 0 && x < 0.5) {
                return JavascriptNumber::ToVarNoCheck((double)Js::JavascriptNumber::k_Zero, scriptContext);
            }

            if(x < 0 && x >= -0.5)
            {
                return scriptContext->GetLibrary()->GetNegativeZero();
            }

            return Math::FloorDouble(x+0.5, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Sin() returns the sine of the given angle in radians, as described in
    /// (ES5.0: S15.8.2.16).
    ///----------------------------------------------------------------------------

    Var Math::Sin(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Sin(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Sin(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_sin]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::sin(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Sqrt() returns the square-root of the given number, as described in
    /// (ES5.0: S15.8.2.17).
    ///----------------------------------------------------------------------------

    Var Math::Sqrt(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));

        if (args.Info.Count >= 2)
        {
            Var arg = args[1];
            double x = JavascriptConversion::ToNumber(arg, scriptContext);
            double result = ::sqrt(x);
            if (TaggedInt::Is(arg))
            {
                return JavascriptNumber::ToVarIntCheck(result, scriptContext);
            }
            else
            {
                return JavascriptNumber::ToVarNoCheck(result, scriptContext);
            }
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Tan() returns the tangent of the given angle, in radians, as described in
    /// (ES5.0: S15.8.2.18).
    ///----------------------------------------------------------------------------

    Var Math::Tan(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));

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);
            double result = Math::Tan( x );
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Tan( double x )
    {
        double result = 0;
#if defined(_M_IX86)
        if( AutoSystemInfo::Data.SSE2Available() )
        {
            _asm {
                movsd xmm0, x
                    call dword ptr[__libm_sse2_tan]
                    movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::tan( x );
        }
        return result;
    }


    ///----------------------------------------------------------------------------
    /// Log10() returns the base 10 logarithm of the given number, as described in
    /// (ES6.0: S20.2.2.21).
    ///----------------------------------------------------------------------------
    Var Math::Log10(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(Log10Count);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = Math::Log10(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Log10(double x)
    {
        double result;

#if defined(_M_IX86)
        if (AutoSystemInfo::Data.SSE2Available())
        {
            _asm {
                movsd xmm0, x
                call dword ptr [__libm_sse2_log10]
                movsd result, xmm0
            }
        }
        else
#endif
        {
            result = ::log10(x);
        }

        return result;
    }

    ///----------------------------------------------------------------------------
    /// Log2() returns the base 2 logarithm of the given number, as described in
    /// (ES6.0: S20.2.2.22).
    ///----------------------------------------------------------------------------
    Var Math::Log2(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(Log2Count);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);
            double result = Math::Log2(x, scriptContext);
            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    double Math::Log2(double x, ScriptContext* scriptContext)
    {
#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
        return ::log2( x );
#else
        // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
        UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
        return ucrtC99MathApis->IsAvailable() ?
            ucrtC99MathApis->log2(x) :
            Math::Log( x ) / Math::LN2;
#endif
    }


    ///----------------------------------------------------------------------------
    /// Log1p() returns the natural logarithm of one plus the given number, as
    /// described in (ES6.0: S20.2.2.20).
    ///----------------------------------------------------------------------------
    Var Math::Log1p(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(Log1pCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::log1p(x);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            double result = ucrtC99MathApis->IsAvailable() ?
                ucrtC99MathApis->log1p(x):
                (JavascriptNumber::IsNegZero(x)) ? x : Math::Log(1.0 + x);
#endif

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Expm1() returns the result of subtracting one from the exponential
    /// function of the given number, as described in (ES6.0: S20.2.2.14).
    ///----------------------------------------------------------------------------
    Var Math::Expm1(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(Expm1Count);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::expm1(x);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            double result = ucrtC99MathApis->IsAvailable() ?
                ucrtC99MathApis->expm1(x) :
                (JavascriptNumber::IsNegZero(x)) ? x : Math::Exp(x) - 1.0;
#endif

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Cosh() returns the hyperbolic cosine of the given number, as described in
    /// (ES6.0: S20.2.2.12).
    ///----------------------------------------------------------------------------
    Var Math::Cosh(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(CoshCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = ::cosh(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Sinh() returns the hyperbolic sine of the given number, as described in
    /// (ES6.0: S20.2.2.30).
    ///----------------------------------------------------------------------------
    Var Math::Sinh(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(SinhCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = ::sinh(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Tanh() returns the hyperbolic tangent of the given number, as described in
    /// (ES6.0: S20.2.2.33).
    ///----------------------------------------------------------------------------
    Var Math::Tanh(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(TanhCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            double result = ::tanh(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Acosh() returns the inverse hyperbolic cosine of the given number, as
    /// described in (ES6.0: S20.2.2.3).
    ///----------------------------------------------------------------------------
    Var Math::Acosh(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(AcoshCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::acosh(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            if (ucrtC99MathApis->IsAvailable())
            {
                return JavascriptNumber::ToVarNoCheck(ucrtC99MathApis->acosh(x), scriptContext);
            }
            else if (x >= 1.0)
            {
                // Can be smarter about large values of x, e.g. as x -> Infinity, sqrt(x^2 - 1) -> x
                // Therefore for large x, log(x+x) is sufficient, but how to decide what a large x is?
                // Also ln(x+x) = ln 2x = ln 2 + ln x
                double result = (x == 1.0) ? 0.0 : Math::Log(x + ::sqrt(x*x - 1.0));

                return JavascriptNumber::ToVarNoCheck(result, scriptContext);
            }
            else
            {
                return scriptContext->GetLibrary()->GetNaN();
            }
#endif
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Asinh() returns the inverse hyperbolic sine of the given number, as
    /// described in (ES6.0: S20.2.2.5).
    ///----------------------------------------------------------------------------
    Var Math::Asinh(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(AsinhCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::asinh(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            if (ucrtC99MathApis->IsAvailable())
            {
                return JavascriptNumber::ToVarNoCheck(ucrtC99MathApis->asinh(x), scriptContext);
            }
            else
            {
                double result = JavascriptNumber::IsNegZero(x) ? x :
                    JavascriptNumber::IsPosInf(x) ? JavascriptNumber::POSITIVE_INFINITY :
                    JavascriptNumber::IsNegInf(x) ? JavascriptNumber::NEGATIVE_INFINITY :
                    Math::Log(x + ::sqrt(x*x + 1.0));

                return JavascriptNumber::ToVarNoCheck(result, scriptContext);
            }
#endif
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Atanh() returns the inverse hyperbolic tangent of the given number, as
    /// described in (ES6.0: S20.2.2.7).
    ///----------------------------------------------------------------------------
    Var Math::Atanh(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(AtanhCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::atanh(x);

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            if (ucrtC99MathApis->IsAvailable())
            {
                return JavascriptNumber::ToVarNoCheck(ucrtC99MathApis->atanh(x), scriptContext);
            }
            else if (Math::Abs(x) < 1.0)
            {
                double result = (JavascriptNumber::IsNegZero(x)) ? x : Math::Log((1.0 + x) / (1.0 - x)) / 2.0;

                return JavascriptNumber::ToVarNoCheck(result, scriptContext);
            }
            else
            {
                if (x == -1.0)
                {
                    return scriptContext->GetLibrary()->GetNegativeInfinite();
                }
                else if (x == 1.0)
                {
                    return scriptContext->GetLibrary()->GetPositiveInfinite();
                }

                return scriptContext->GetLibrary()->GetNaN();
            }
#endif
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Hypot() returns the square root of the sum of squares of the two or three
    /// given numbers, as described in (ES6.0: S20.2.2.17).
    ///----------------------------------------------------------------------------
    Var Math::Hypot(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(HypotCount);

        // ES6 20.2.2.18 Math.hypot(value1, value2, ...values)
        // If no arguments are passed, the result is +0.
        // If any argument is +∞, the result is +∞.
        // If any argument is -∞, the result is +∞.
        // If no argument is +∞ or -∞, and any argument is NaN, the result is NaN.
        // If all arguments are either +0 or -0, the result is +0.

        double result = JavascriptNumber::k_Zero; // If there are no arguments return value is positive zero.

        if (args.Info.Count == 2)
        {
            // Special case for one argument
            double x1 = JavascriptConversion::ToNumber(args[1], scriptContext);

            if (JavascriptNumber::IsPosInf(x1) || JavascriptNumber::IsNegInf(x1))
            {
                result = JavascriptNumber::POSITIVE_INFINITY;
            }
            else
            {
                result = Math::Abs(x1);
            }
        }
        else if (args.Info.Count == 3)
        {
            // CRT hypot call
            double x1 = JavascriptConversion::ToNumber(args[1], scriptContext);
            double x2 = JavascriptConversion::ToNumber(args[2], scriptContext);

            if (JavascriptNumber::IsPosInf(x1) || JavascriptNumber::IsNegInf(x1) ||
                JavascriptNumber::IsPosInf(x2) || JavascriptNumber::IsNegInf(x2))
            {
                result = JavascriptNumber::POSITIVE_INFINITY;
            }
            else if (JavascriptNumber::IsNan(x1) || JavascriptNumber::IsNan(x2))
            {
                result = JavascriptNumber::NaN;
            }
            else
            {
                result = ::hypot(x1, x2);
            }
        }
        else if (args.Info.Count > 3)
        {
            // Uncommon case of more than 2 arguments for hypot
            result = Math::HypotHelper(args, scriptContext);
        }

        return JavascriptNumber::ToVarNoCheck(result, scriptContext);
    }

    double Math::HypotHelper(Arguments args, ScriptContext *scriptContext)
    {
        // CRT does not have a multiple version of hypot, so we implement it here ourselves.
        bool foundNaN = false;
        double scale = 0;
        double sum = 0;

        //Ignore first argument which is this pointer
        for (uint counter = 1; counter < args.Info.Count; counter++)
        {
            double doubleVal = JavascriptConversion::ToNumber(args[counter], scriptContext);

            if (JavascriptNumber::IsPosInf(doubleVal) || JavascriptNumber::IsNegInf(doubleVal))
            {
                return JavascriptNumber::POSITIVE_INFINITY;
            }

            if (!foundNaN)
            {
                if (JavascriptNumber::IsNan(doubleVal))
                {
                    //Even though we found NaN, we still need to validate none of the other arguments are +∞ or -∞
                    foundNaN = true;
                }
                else
                {
                    doubleVal = Math::Abs(doubleVal);
                    if (scale < doubleVal)
                    {
                        sum = sum * (scale / doubleVal) * (scale / doubleVal) + 1; /* scale/scale === 1*/
                        //change the scale to new max value
                        scale = doubleVal;
                    }
                    else if (scale != 0)
                    {
                        sum += (doubleVal / scale) * (doubleVal / scale);
                    }
                }
            }
        }

        if (foundNaN)
        {
            return JavascriptNumber::NaN;
        }

        return scale * ::sqrt(sum);
    }

    ///----------------------------------------------------------------------------
    /// Trunc() returns the integral part of the given number, removing any
    /// fractional digits, as described in (ES6.0: S20.2.2.34).
    ///----------------------------------------------------------------------------
    Var Math::Trunc(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(TruncCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::trunc(x);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            double result = ucrtC99MathApis->IsAvailable() ?
                ucrtC99MathApis->trunc(x) :
                (x < 0.0) ? ::ceil(x) : ::floor(x);
#endif

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Sign() returns the sign of the given number, indicating whether it is
    /// positive, negative, or zero, as described in (ES6.0: S20.2.2.28).
    ///----------------------------------------------------------------------------
    Var Math::Sign(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(SignCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

            if (JavascriptNumber::IsNan(x))
            {
                return scriptContext->GetLibrary()->GetNaN();
            }
            else if (JavascriptNumber::IsNegZero(x))
            {
                return scriptContext->GetLibrary()->GetNegativeZero();
            }
            else
            {
                return TaggedInt::ToVarUnchecked(x == 0.0 ? 0 : x < 0.0 ? -1 : 1);
            }
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Cbrt() returns the cube root of the given number, as described in
    /// (ES6.0: S20.2.2.9).
    ///----------------------------------------------------------------------------
    Var Math::Cbrt(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(CbrtCount);

        if (args.Info.Count >= 2)
        {
            double x = JavascriptConversion::ToNumber(args[1], scriptContext);

#ifdef WHEN_UCRT_IS_LINKED_IN_BUILD
            double result = ::cbrt(x);
#else
            // TODO: THE FALLBACK IS NOT ACCURATE; Universal CRT is available on Threshold so we should never fallback but ideally we would link at build time to these APIs instead of loading them at runtime
            UCrtC99MathApis* ucrtC99MathApis = scriptContext->GetThreadContext()->GetUCrtC99MathApis();
            if (ucrtC99MathApis->IsAvailable())
            {
                return JavascriptNumber::ToVarNoCheck(ucrtC99MathApis->cbrt(x), scriptContext);
            }

            bool isNeg = x < 0.0;

            if (isNeg)
            {
                x = -x;
            }

            double result = (x == 0.0) ? x : Math::Exp(Math::Log(x) / 3.0);

            if (isNeg)
            {
                result = -result;
            }
#endif

            return JavascriptNumber::ToVarNoCheck(result, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }

    ///----------------------------------------------------------------------------
    /// Imul() returns the 32-bit integer multiplication of two given numbers, as
    /// described in (ES6.0: S20.2.2.18).
    ///----------------------------------------------------------------------------
    Var Math::Imul(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(ImulCount);

        if (args.Info.Count >= 3)
        {
            int32 x = JavascriptConversion::ToInt32(args[1], scriptContext);
            int32 y = JavascriptConversion::ToInt32(args[2], scriptContext);

            int64 int64Result = (int64)x * (int64)y;
            int32 result = (int32)int64Result;

            return JavascriptNumber::ToVar(result, scriptContext);
        }
        else
        {
            // The arguments, if left unspecified default to undefined, and ToUint32(undefined) produces +0.
            // Therefore we return +0, not NaN here like the other Math functions.
            return TaggedInt::ToVarUnchecked(0);
        }
    }

    ///----------------------------------------------------------------------------
    /// Clz32() returns the leading number of zero bits of ToUint32(x), as
    /// described in (ES6.0: S20.2.2.11).
    ///----------------------------------------------------------------------------
    Var Math::Clz32(RecyclableObject* function, CallInfo callInfo, ...)
    {
        PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);

        ARGUMENTS(args, callInfo);
        ScriptContext* scriptContext = function->GetScriptContext();
        Var value = args.Info.Count > 1 ? args[1] : scriptContext->GetLibrary()->GetUndefined();

        Assert(!(callInfo.Flags & CallFlags_New));
        CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(Clz32Count);

        uint32 uint32value = JavascriptConversion::ToUInt32(value, scriptContext);
        DWORD index;

        if (!_BitScanReverse(&index, uint32value))
        {
            return TaggedInt::ToVarUnchecked(32);
        }
        return TaggedInt::ToVarUnchecked(31 - index);
    }

    Var Math::Fround(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(FroundCount);

        if (args.Info.Count >= 2)
        {
            float x = (float) JavascriptConversion::ToNumber(args[1], scriptContext);

            return JavascriptNumber::ToVarNoCheck((double) x, scriptContext);
        }
        else
        {
            return scriptContext->GetLibrary()->GetNaN();
        }
    }
} // namespace Js
