#pragma once #include "CoreTypes.h" #include "Numeric/Bit.h" #include "Numeric/Limits.h" #include "Numeric/Numbers.h" #include "Templates/Tuple.h" #include "Templates/Utility.h" #include "TypeTraits/TypeTraits.h" #include "Miscellaneous/AssertionMacros.h" #include NAMESPACE_REDCRAFT_BEGIN NAMESPACE_MODULE_BEGIN(Redcraft) NAMESPACE_MODULE_BEGIN(Utility) NAMESPACE_BEGIN(Math) NAMESPACE_PRIVATE_BEGIN template struct TFloatingTypeTraits { static_assert(sizeof(T) == -1, "Unsupported floating point type."); }; template <> struct TFloatingTypeTraits { // IEEE-754 single precision floating point format. // SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM using FIntegralT = uint32; static constexpr int SignBits = 1; static constexpr int ExponentBits = 8; static constexpr int MantissaBits = 23; static_assert(SignBits + ExponentBits + MantissaBits == sizeof(float) * 8); static constexpr int ExponentBias = 127; static constexpr int SignShift = 31; static constexpr int ExponentShift = 23; static constexpr int MantissaShift = 0; static constexpr FIntegralT SignMask = 0x80000000; static constexpr FIntegralT ExponentMask = 0x7F800000; static constexpr FIntegralT MantissaMask = 0x007FFFFF; }; template <> struct TFloatingTypeTraits { // IEEE-754 double precision floating point format. // SEEEEEEE EEEEMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM using FIntegralT = uint64; static constexpr int SignBits = 1; static constexpr int ExponentBits = 11; static constexpr int MantissaBits = 52; static_assert(SignBits + ExponentBits + MantissaBits == sizeof(double) * 8); static constexpr int ExponentBias = 1023; static constexpr int SignShift = 63; static constexpr int ExponentShift = 52; static constexpr int MantissaShift = 0; static constexpr FIntegralT SignMask = 0x8000000000000000; static constexpr FIntegralT ExponentMask = 0x7FF0000000000000; static constexpr FIntegralT MantissaMask = 0x000FFFFFFFFFFFFF; }; NAMESPACE_PRIVATE_END #define FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(Func) \ { \ if constexpr (CSameAs || CSameAs) \ { \ return NAMESPACE_STD::Func(A); \ } \ \ else static_assert(sizeof(T) == -1, "Unsupported floating point type."); \ \ return TNumericLimits::QuietNaN(); \ } #define FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(Func) \ { \ if constexpr (CSameAs || CSameAs) \ { \ return NAMESPACE_STD::Func(A, B); \ } \ \ else static_assert(sizeof(T) == -1, "Unsupported floating point type."); \ \ return TNumericLimits::QuietNaN(); \ } #define RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(Concept, Func) \ template requires (CCommonType) \ NODISCARD FORCEINLINE constexpr auto Func(T A, U B) \ { \ using FCommonT = TCommonType; \ \ return Math::Func( \ static_cast(A), \ static_cast(B) \ ); \ } #define RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(Concept, Func) \ template requires (CCommonType) \ NODISCARD FORCEINLINE constexpr auto Func(T A, U B, V C) \ { \ using FCommonT = TCommonType; \ \ return Math::Func( \ static_cast(A), \ static_cast(B), \ static_cast(C) \ ); \ } /** @return true if the given value is within a range ['MinValue', 'MaxValue'), false otherwise. */ template NODISCARD FORCEINLINE constexpr T IsWithin(T A, T MinValue, T MaxValue) { return A >= MinValue && A < MaxValue; } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, IsWithin) /** @return true if the given value is within a range ['MinValue', 'MaxValue'], false otherwise. */ template NODISCARD FORCEINLINE constexpr T IsWithinInclusive(T A, T MinValue, T MaxValue) { return A >= MinValue && A <= MaxValue; } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, IsWithinInclusive) /** @return The nearest integer not greater in magnitude than the given value. */ template NODISCARD FORCEINLINE constexpr T Trunc(T A) { if constexpr (CIntegral) return A; FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(trunc) } /** @return The nearest integer not greater in magnitude than the given value. */ template NODISCARD FORCEINLINE constexpr T TruncTo(U A) { if constexpr (CIntegral) { if constexpr (!CIntegral) { return static_cast(A); } else return A; } else if constexpr (CSameAs || CSameAs) { return Math::Trunc(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } /** @return The nearest integer not less than the given value. */ template NODISCARD FORCEINLINE constexpr T Ceil(T A) { if constexpr (CIntegral) return A; FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(ceil) } /** @return The nearest integer not less than the given value. */ template NODISCARD FORCEINLINE constexpr T CeilTo(U A) { if constexpr (CIntegral) { if constexpr (!CIntegral) { T I = Math::TruncTo(A); I += static_cast(I) < A; return I; } else return A; } else if constexpr (CSameAs || CSameAs) { return Math::Ceil(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } /** @return The nearest integer not greater than the given value. */ template NODISCARD FORCEINLINE constexpr T Floor(T A) { if constexpr (CIntegral) return A; FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(floor) } /** @return The nearest integer not greater than the given value. */ template NODISCARD FORCEINLINE constexpr T FloorTo(U A) { if constexpr (CIntegral) { if constexpr (!CIntegral) { T I = Math::TruncTo(A); I -= static_cast(I) > A; return I; } else return A; } else if constexpr (CSameAs || CSameAs) { return Math::Floor(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } /** @return The nearest integer to the given value, rounding away from zero in halfway cases. */ template NODISCARD FORCEINLINE constexpr T Round(T A) { if constexpr (CIntegral) return A; FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(round) } /** @return The nearest integer to the given value, rounding away from zero in halfway cases. */ template NODISCARD FORCEINLINE constexpr T RoundTo(U A) { if constexpr (CIntegral) { if constexpr (!CIntegral) { return Math::FloorTo(A + static_cast(0.5)); } else return A; } else if constexpr (CSameAs || CSameAs) { return Math::Round(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } /** @return The absolute value of the given value. */ template NODISCARD FORCEINLINE constexpr T Abs(T A) { return A < 0 ? -A : A; } /** @return The absolute value of the given value. */ template NODISCARD FORCEINLINE constexpr T Abs(T A) { return A; } /** @return 0 if the given value is zero, -1 if it is negative, and 1 if it is positive. */ template NODISCARD FORCEINLINE constexpr T Sign(T A) { if (A == static_cast(0)) return static_cast( 0); if (A < static_cast(0)) return static_cast(-1); return static_cast(1); } /** @return The minimum value of the given values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr auto Min(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; else { using FCommonT = TCommonType; FCommonT B = Math::Min(InOther...); return A < B ? A : B; } } /** @return The maximum value of the given values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr auto Max(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; else { using FCommonT = TCommonType; FCommonT B = Math::Max(InOther...); return A > B ? A : B; } } /** @return The index of the minimum value of the given values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr size_t MinIndex(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return 0; else { size_t Index = Math::MinIndex(InOther...); bool bFlag; ForwardAsTuple(InOther...).Visit([&bFlag, A](auto B) { bFlag = A < B; }, Index); return bFlag ? 0 : Index + 1; } } /** @return The index of the maximum value of the given values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr size_t MaxIndex(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return 0; else { size_t Index = Math::MaxIndex(InOther...); bool bFlag; ForwardAsTuple(InOther...).Visit([&bFlag, A](auto B) { bFlag = A > B; }, Index); return bFlag ? 0 : Index + 1; } } template struct TDiv { T Quotient; T Remainder; }; /** @return The quotient and remainder of the division of the given values. */ template NODISCARD FORCEINLINE constexpr Math::TDiv Div(T A, T B) { checkf(B != 0, TEXT("Illegal divisor. It must not be zero.")); Math::TDiv Result; Result.Quotient = A / B; Result.Remainder = A % B; return Result; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, Div) /** @return The quotient of the division of the given values and rounds up. */ template NODISCARD FORCEINLINE constexpr T DivAndCeil(T A, T B) { return A >= 0 ? (A + B - 1) / B : A / B; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, DivAndCeil) /** @return The quotient of the division of the given values and rounds down. */ template NODISCARD FORCEINLINE constexpr T DivAndFloor(T A, T B) { return A >= 0 ? A / B : (A - B + 1) / B; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, DivAndFloor) /** @return The quotient of the division of the given values and rounds to nearest. */ template NODISCARD FORCEINLINE constexpr T DivAndRound(T A, T B) { return A >= 0 ? (A + B / 2 ) / B : (A - B / 2 + 1) / B; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, DivAndRound) /** @return true if the given values are nearly equal, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsNearlyEqual(T A, T B, T Epsilon = TNumericLimits::Epsilon()) { return Math::Abs(A - B) <= Epsilon; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, IsNearlyEqual) RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, IsNearlyEqual) /** @return true if the given value is nearly zero, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsNearlyZero(T A, T Epsilon = TNumericLimits::Epsilon()) { return Math::Abs(A) <= Epsilon; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, IsNearlyZero) /** @return true if the given value is infinity, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsInfinity(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & FTraits::ExponentMask) == FTraits::ExponentMask && (IntegralValue & FTraits::MantissaMask) == 0; } /** @return true if the given value is NaN, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsNaN(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & FTraits::ExponentMask) == FTraits::ExponentMask && (IntegralValue & FTraits::MantissaMask) != 0; } /** @return true if the given value is normal, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsNormal(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & FTraits::ExponentMask) != 0 && (IntegralValue & FTraits::ExponentMask) != FTraits::ExponentMask; } /** @return true if the given value is subnormal, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsDenorm(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & FTraits::ExponentMask) == 0 && (IntegralValue & FTraits::MantissaMask) != 0; } /** @return true if the given value is negative, even -0.0, false otherwise. */ template NODISCARD FORCEINLINE constexpr bool IsNegative(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & FTraits::SignMask) >> FTraits::SignShift; } /** @return The exponent of the given value. */ template NODISCARD FORCEINLINE constexpr uint Exponent(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return ((IntegralValue & FTraits::ExponentMask) >> FTraits::ExponentShift) - FTraits::ExponentBias; } /** @return The NaN value with the given payload. */ template NODISCARD FORCEINLINE constexpr T NaN(U Payload) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; checkf(Payload != 0, TEXT("Illegal payload. It must not be zero.")); checkf(Payload < (static_cast(1) << FTraits::MantissaBits), TEXT("Illegal payload. It must be less than 2^MantissaBits.")); if (Payload == 0) return TNumericLimits::QuietNaN(); typename FTraits::FIntegralT ValidPayload = Payload & FTraits::MantissaMask; return Math::BitCast(ValidPayload | FTraits::ExponentMask); } /** @return The NaN value with the given payload. */ template NODISCARD FORCEINLINE constexpr T NaN(U Payload) { TUnderlyingType IntegralValue = static_cast>(Payload); return Math::NaN(IntegralValue); } /** @return The NaN payload of the given value. */ template NODISCARD FORCEINLINE constexpr auto NaNPayload(T A) { using FTraits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return IntegralValue & FTraits::MantissaMask; } /** @return The NaN payload of the given value. */ template NODISCARD FORCEINLINE constexpr auto NaNPayload(U A) { return static_cast(Math::NaNPayload(A)); } /** @return The remainder of the floating point division operation. */ template NODISCARD FORCEINLINE constexpr T FMod(T A, T B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(fmod) RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, FMod) /** @return The signed remainder of the floating point division operation. */ template NODISCARD FORCEINLINE constexpr T Remainder(T A, T B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(remainder) RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, Remainder) template struct TRemQuo { int Quotient; T Remainder; }; /** @return The signed remainder and the three last bits of the division operation. */ template NODISCARD FORCEINLINE constexpr Math::TRemQuo RemQuo(T A, T B) { Math::TRemQuo Result; if constexpr (CSameAs || CSameAs) { Result.Remainder = NAMESPACE_STD::remquo(A, B, &Result.Quotient); return Result; } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return Result; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, RemQuo) template struct TModF { T IntegralPart; T FractionalPart; }; /** @return The integral and fractional parts of the given value. */ template NODISCARD FORCEINLINE constexpr Math::TModF ModF(T A) { Math::TModF Result; if constexpr (CSameAs || CSameAs) { Result.FractionalPart = NAMESPACE_STD::modf(A, &Result.IntegralPart); return Result; } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return Result; } /** @return The e raised to the given power. */ template NODISCARD FORCEINLINE constexpr T Exp(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(exp) /** @return The 2 raised to the given power. */ template NODISCARD FORCEINLINE constexpr T Exp2(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(exp2) /** @return The e raised to the given power, minus one. */ template NODISCARD FORCEINLINE constexpr T ExpMinus1(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(expm1) /** @return The natural logarithm of the given value. */ template NODISCARD FORCEINLINE constexpr T Log(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log) /** @return The base-2 logarithm of the given value. */ template NODISCARD FORCEINLINE constexpr T Log2(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log2) /** @return The base-10 logarithm of the given value. */ template NODISCARD FORCEINLINE constexpr T Log10(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log10) /** @return The natural logarithm of one plus the given value. */ template NODISCARD FORCEINLINE constexpr T Log1Plus(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log1p) /** @return The square of the given values. */ template NODISCARD FORCEINLINE constexpr T Square(T A) { return A * A; } /** @return The cube of the given values. */ template NODISCARD FORCEINLINE constexpr T Cube(T A) { return A * A * A; } /** @return The 'A' raised to the power of 'B'. */ template NODISCARD FORCEINLINE constexpr T Pow(T A, T B) { if (B < 0) { checkf(false, TEXT("Illegal exponent. It must be greater than or equal to zero for integral.")); return TNumericLimits::QuietNaN(); } T Result = 1; while (B != 0) { if (B & 1) Result *= A; A *= A; B >>= 1; } return Result; } /** @return The 'A' raised to the power of 'B'. */ template NODISCARD FORCEINLINE constexpr T Pow(T A, T B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(pow) RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, Pow) /** @return The square root of the given value. */ template NODISCARD FORCEINLINE constexpr T Sqrt(T A) { if (A < 0) { checkf(false, TEXT("Illegal argument. It must be greater than or equal to zero.")); return TNumericLimits::QuietNaN(); } if (A == 0) return 0; T X = A; while (true) { T Y = (X + A / X) / 2; if (Y >= X) return X; X = Y; } } /** @return The square root of the given value. */ template NODISCARD FORCEINLINE constexpr T Sqrt(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(sqrt) /** @return The cube root of the given value. */ template NODISCARD FORCEINLINE constexpr T Cbrt(T A) { if (A < 0) return -Math::Cbrt(-A); if (A == 0) return 0; T X = A; while (true) { T Y = (X + A / (X * X)) / 2; if (Y >= X) return X; X = Y; } } /** @return The cube root of the given value. */ template NODISCARD FORCEINLINE constexpr T Cbrt(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(cbrt) /** @return The sum of the given value. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr auto Sum(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; else { using FCommonT = TCommonType; FCommonT Sum = A + Math::Sum(InOther...); return Sum; } } /** @return The sum of the squared values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr auto SquaredSum(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return Math::Square(A); else { using FCommonT = TCommonType; FCommonT Sum = Math::Square(A) + Math::SquaredSum(InOther...); return Sum; } } /** @return The average of the given values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr auto Avg(T A, Ts... InOther) { using FSize = TConditional>>; using FCommonT = TCommonType; constexpr FSize Count = sizeof...(Ts) + 1; if constexpr (Count == 1) return static_cast(A); else if constexpr (Count == 2) { FCommonT Array[] = { A, InOther... }; if (Array[1] < Array[0]) Swap(Array[0], Array[1]); return static_cast(Array[0] + (Array[1] - Array[0]) / 2); } else if constexpr (CIntegral) { Math::TDiv Temp[] = { Math::Div(static_cast(A ), static_cast(Count)), Math::Div(static_cast(InOther), static_cast(Count))... }; FCommonT Quotient = 0; FCommonT Remainder = 0; for (FSize I = 0; I != Count; ++I) { Quotient += Temp[I].Quotient; Remainder += Temp[I].Remainder; } Quotient += Remainder / Count; return Quotient; } else { FCommonT Sum = A + Math::Sum(InOther...); return static_cast(Sum / Count); } } /** @return The square root of the sum of the squares of the given values. */ template requires (CCommonType) NODISCARD FORCEINLINE constexpr auto Hypot(T A, Ts... InOther) { using FCommonT = TCommonType; constexpr size_t Count = sizeof...(Ts) + 1; if constexpr (Count == 1) return Math::Abs(A); else if constexpr (Count == 2) { FCommonT Array[] = { A, InOther... }; if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::hypot(static_cast(Array[0]), static_cast(Array[1])); } else return static_cast(Math::Sqrt(Math::Square(Array[0]) + Math::Square(Array[1]))); } else if constexpr (Count == 3) { FCommonT Array[] = { A, InOther... }; if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::hypot(static_cast(Array[0]), static_cast(Array[1]), static_cast(Array[2])); } else return Math::Sqrt(Math::SquaredSum(A, InOther...)); } else return Math::Sqrt(Math::SquaredSum(A, InOther...)); } /** @return The sine of the given value. */ template NODISCARD FORCEINLINE constexpr T Sin(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(sin) /** @return The cosine of the given value. */ template NODISCARD FORCEINLINE constexpr T Cos(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(cos) /** @return The tangent of the given value. */ template NODISCARD FORCEINLINE constexpr T Tan(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(tan) /** @return The arc sine of the given value. */ template NODISCARD FORCEINLINE constexpr T Asin(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(asin) /** @return The arc cosine of the given value. */ template NODISCARD FORCEINLINE constexpr T Acos(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(acos) /** @return The arc tangent of the given value. */ template NODISCARD FORCEINLINE constexpr T Atan(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(atan) /** @return The arc tangent of 'A' / 'B'. */ template NODISCARD FORCEINLINE constexpr T Atan2(T A, T B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(atan2) RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, Atan2) /** @return The hyperbolic sine of the given value. */ template NODISCARD FORCEINLINE constexpr T Sinh(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(sinh) /** @return The hyperbolic cosine of the given value. */ template NODISCARD FORCEINLINE constexpr T Cosh(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(cosh) /** @return The hyperbolic tangent of the given value. */ template NODISCARD FORCEINLINE constexpr T Tanh(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(tanh) /** @return The hyperbolic arc sine of the given value. */ template NODISCARD FORCEINLINE constexpr T Asinh(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(asinh) /** @return The hyperbolic arc cosine of the given value. */ template NODISCARD FORCEINLINE constexpr T Acosh(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(acosh) /** @return The hyperbolic arc tangent of the given value. */ template NODISCARD FORCEINLINE constexpr T Atanh(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(atanh) /** @return The error function of the given value. */ template NODISCARD FORCEINLINE constexpr T Erf(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(erf) /** @return The complementary error function of the given value. */ template NODISCARD FORCEINLINE constexpr T Erfc(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(erfc) /** @return The gamma function of the given value. */ template NODISCARD FORCEINLINE constexpr T Gamma(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(tgamma) /** @return The natural logarithm of the gamma function of the given value. */ template NODISCARD FORCEINLINE constexpr T LogGamma(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(lgamma) /** @return The value of 'A' is multiplied by 2 raised to the power of 'B'. */ template NODISCARD FORCEINLINE constexpr T LdExp(T A, int B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(ldexp) /** @return The degrees of the given radian value. */ template NODISCARD FORCEINLINE constexpr T RadiansToDegrees(T A) { return A * (static_cast(180) / Math::TNumbers::Pi); } /** @return The radians of the given degree value. */ template NODISCARD FORCEINLINE constexpr T DegreesToRadians(T A) { return A * (Math::TNumbers::Pi / static_cast(180)); } /** @return The greatest common divisor of the given values. */ template NODISCARD FORCEINLINE constexpr T GCD(T A, T B) { using FUnsignedT = TMakeUnsigned; FUnsignedT C = Math::Abs(A); FUnsignedT D = Math::Abs(B); if (C == 0) return D; if (D == 0) return C; uint Shift = Math::CountRightZero(C | D); C >>= Math::CountRightZero(C); do { D >>= Math::CountRightZero(D); if (C > D) Swap(C, D); D -= C; } while (D != 0); return C << Shift; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, GCD) /** @return The least common multiple of the given values. */ template NODISCARD FORCEINLINE constexpr T LCM(T A, T B) { A = Math::Abs(A); B = Math::Abs(B); if (A == 0 || B == 0) return 0; return A / Math::GCD(A, B) * B; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, LCM) /** @return The value of 'A' is clamped to the range ['MinValue', 'MaxValue']. */ template NODISCARD FORCEINLINE constexpr T Clamp(T A, T MinValue, T MaxValue) { return Math::Min(Math::Max(A, MinValue), MaxValue); } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, Clamp) /** @return The value of 'A' is clamped to the range ['MinValue', 'MaxValue'], but it wraps around the range when exceeded. */ template NODISCARD FORCEINLINE constexpr T WrappingClamp(T A, T MinValue, T MaxValue) { if (MinValue > MaxValue) { checkf(false, TEXT("Illegal range. MinValue must be less than or equal to MaxValue.")); return TNumericLimits::QuietNaN(); } if (MinValue == MaxValue) return MinValue; if constexpr (CSameAs) return A; else if constexpr (CIntegral) { using FUnsignedT = TMakeUnsigned; FUnsignedT Range = MaxValue - MinValue; if (A < MinValue) { FUnsignedT Modulo = static_cast(MinValue - A) % Range; return Modulo != 0 ? MaxValue - Modulo : MinValue; } if (A > MaxValue) { FUnsignedT Modulo = static_cast(A - MaxValue) % Range; return Modulo != 0 ? MinValue + Modulo : MaxValue; } return A; } else if constexpr (CSameAs || CSameAs) { T Range = MaxValue - MinValue; if (A < MinValue) return MaxValue - Math::FMod(MinValue - A, Range); if (A > MaxValue) return MinValue + Math::FMod(A - MaxValue, Range); return A; } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, WrappingClamp) /** @return The linear interpolation of the given values. */ template NODISCARD FORCEINLINE constexpr T Lerp(T A, T B, T Alpha) { return A + Alpha * (B - A); } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, Lerp) /** @return The stable linear interpolation of the given values. */ template NODISCARD FORCEINLINE constexpr T LerpStable(T A, T B, T Alpha) { return A * (static_cast(1) - Alpha) + B * Alpha; } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, LerpStable) #undef FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS #undef FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS #undef RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS #undef RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS NAMESPACE_END(Math) NAMESPACE_MODULE_END(Utility) NAMESPACE_MODULE_END(Redcraft) NAMESPACE_REDCRAFT_END