#pragma once #include "CoreTypes.h" #include "Numeric/Bit.h" #include "Numeric/Limits.h" #include "Templates/Tuple.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 RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(Concept, Func) \ template requires (CCommonType) \ FORCEINLINE constexpr auto Func(T A, U B) \ { \ return Math::Func>(A, B); \ } #define RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(Concept, Func) \ template requires (CCommonType) \ FORCEINLINE constexpr auto Func(T A, U B, V C) \ { \ return Math::Func>(A, B, C); \ } template FORCEINLINE constexpr T IsWithin(T A, T MinValue, T MaxValue) { return A >= MinValue && A < MaxValue; } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, IsWithin) template FORCEINLINE constexpr T IsWithinInclusive(T A, T MinValue, T MaxValue) { return A >= MinValue && A <= MaxValue; } RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, IsWithinInclusive) template FORCEINLINE constexpr T Trunc(T A) { if constexpr (CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::trunc(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T TruncTo(U A) { if constexpr (CIntegral && CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::trunc(static_cast(A)); } else if constexpr (CIntegral) { return static_cast(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Ceil(T A) { if constexpr (CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::ceil(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T CeilTo(U A) { if constexpr (CIntegral && CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::ceil(static_cast(A)); } else if constexpr (CIntegral) { T I = Math::TruncTo(A); I += static_cast(I) < A; return I; } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Floor(T A) { if constexpr (CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::floor(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T FloorTo(U A) { if constexpr (CIntegral && CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::floor(static_cast(A)); } else if constexpr (CIntegral) { T I = Math::TruncTo(A); I -= static_cast(I) > A; return I; } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Round(T A) { if constexpr (CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::round(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T RoundTo(U A) { if constexpr (CIntegral && CIntegral) return A; else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::round(static_cast(A)); } else if constexpr (CIntegral) { return Math::FloorTo(A + static_cast(0.5)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Abs(T A) { return A < 0 ? -A : A; } template FORCEINLINE constexpr T Abs(T A) { return A; } template 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); } template requires (CCommonType) 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; } } template requires (CCommonType) 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; } } template requires (CCommonType) 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; } } template requires (CCommonType) 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 FORCEINLINE constexpr auto Div(T A, T B) { checkf(B != 0, TEXT("Illegal divisor. It must not be zero.")); struct { T Quotient; T Remainder; } Result; Result.Quotient = A / B; Result.Remainder = A % B; return Result; } template FORCEINLINE constexpr T DivAndCeil(T A, T B) { return (A + B - 1) / B; } template FORCEINLINE constexpr T DivAndFloor(T A, T B) { return A / B; } template 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, Div) template 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) template FORCEINLINE constexpr bool IsNearlyZero(T A, T Epsilon = TNumericLimits::Epsilon()) { return Math::Abs(A) <= Epsilon; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, IsNearlyZero) template FORCEINLINE constexpr T IsInfinity(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & Traits::ExponentMask) == Traits::ExponentMask && (IntegralValue & Traits::MantissaMask) == 0; } template FORCEINLINE constexpr T IsNaN(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & Traits::ExponentMask) == Traits::ExponentMask && (IntegralValue & Traits::MantissaMask) != 0; } template FORCEINLINE constexpr T IsNormal(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & Traits::ExponentMask) != 0 && (IntegralValue & Traits::ExponentMask) != Traits::ExponentMask; } template FORCEINLINE constexpr T IsDenorm(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & Traits::ExponentMask) == 0 && (IntegralValue & Traits::MantissaMask) != 0; } template FORCEINLINE constexpr bool IsNegative(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return (IntegralValue & Traits::SignMask) >> Traits::SignShift; } template FORCEINLINE constexpr uint Exponent(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return ((IntegralValue & Traits::ExponentMask) >> Traits::ExponentShift) - Traits::ExponentBias; } template FORCEINLINE constexpr T NaN(U Payload) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; checkf(Payload != 0, TEXT("Illegal payload. It must not be zero.")); checkf(Payload < (static_cast(1) << Traits::MantissaBits), TEXT("Illegal payload. It must be less than 2^MantissaBits.")); if (Payload == 0) return TNumericLimits::QuietNaN(); typename Traits::FIntegralT ValidPayload = Payload & Traits::MantissaMask; return Math::BitCast(ValidPayload | Traits::ExponentMask); } template FORCEINLINE constexpr T NaN(U Payload) { TUnderlyingType IntegralValue = static_cast>(Payload); return Math::NaN(IntegralValue); } template FORCEINLINE constexpr auto NaNPayload(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; auto IntegralValue = Math::BitCast(A); return IntegralValue & Traits::MantissaMask; } template FORCEINLINE constexpr auto NaNPayload(U A) { return static_cast(Math::NaNPayload(A)); } template FORCEINLINE constexpr T FMod(T A, T B) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::fmod(A, B); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, FMod) template FORCEINLINE constexpr T Remainder(T A, T B) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::remainder(A, B); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, Remainder) template FORCEINLINE constexpr auto RemQuo(T A, T B) { struct { int Quotient; T Remainder; } 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 FORCEINLINE constexpr auto ModF(T A) { struct { T IntegralPart; T FractionalPart; } 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; } template FORCEINLINE constexpr T Exp(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::exp(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Exp2(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::exp2(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T ExpMinus1(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::expm1(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Log(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::log(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Log2(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::log2(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Log10(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::log10(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Log1Plus(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::log1p(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Square(T A) { return A * A; } template FORCEINLINE constexpr T Cube(T A) { return A * A * A; } template 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; } template FORCEINLINE constexpr T Pow(T A, T B) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::pow(A, B); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, Pow) template 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(); } T X = A; while (true) { T Y = (X + A / X) / 2; if (Y >= X) return X; X = Y; } } template FORCEINLINE constexpr T Sqrt(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::sqrt(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr T Cbrt(T A) { if (A < 0) return -Math::Cbrt(-A); T X = A; while (true) { T Y = (X + A / (X * X)) / 2; if (Y >= X) return X; X = Y; } } template FORCEINLINE constexpr T Cbrt(T A) { if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::cbrt(A); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template requires (CCommonType) 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; } } template requires (CCommonType) FORCEINLINE constexpr auto SquaredSum(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return Math::Square(A); else { using FCommonT = TCommonType; FCommonT Sum = A + Math::SquaredSum(InOther...); return Sum; } } template requires (CCommonType) FORCEINLINE constexpr auto Avg(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; else { using FCommonT = TCommonType; FCommonT Sum = A + Math::Sum(InOther...); return Sum / (sizeof...(Ts) + 1); } } template FORCEINLINE constexpr T Hypot(T A) { return Math::Abs(A); } template FORCEINLINE constexpr auto Hypot(T A, U B) { using FCommonT = TCommonType; if constexpr (CIntegral) return static_cast(Math::Sqrt(Math::Square(A) + Math::Square(B))); else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::hypot(static_cast(A), static_cast(B)); } else static_assert(sizeof(FCommonT) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template FORCEINLINE constexpr auto Hypot(T A, U B, V C) { using FCommonT = TCommonType; if constexpr (CIntegral) return static_cast(Math::Sqrt(Math::SquaredSum(A, B, C))); else if constexpr (CSameAs || CSameAs) { return NAMESPACE_STD::hypot(static_cast(A), static_cast(B), static_cast(C)); } else static_assert(sizeof(FCommonT) == -1, "Unsupported floating point type."); return TNumericLimits::QuietNaN(); } template requires (CCommonType) FORCEINLINE constexpr auto Hypot(Ts... InOther) { return Math::Sqrt(Math::SquaredSum(InOther...)); } template 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) template 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) #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