diff --git a/Redcraft.Utility/Source/Public/Numeric/Math.h b/Redcraft.Utility/Source/Public/Numeric/Math.h index 5aea10d..e4b2bad 100644 --- a/Redcraft.Utility/Source/Public/Numeric/Math.h +++ b/Redcraft.Utility/Source/Public/Numeric/Math.h @@ -3,9 +3,12 @@ #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) @@ -86,6 +89,170 @@ NAMESPACE_PRIVATE_END 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) { @@ -138,25 +305,79 @@ FORCEINLINE constexpr auto Max(T A, Ts... InOther) } -template -FORCEINLINE constexpr auto Div(T LHS, T RHS) +template requires (CCommonType) +FORCEINLINE constexpr size_t MinIndex(T A, Ts... InOther) { - checkf(RHS != 0, TEXT("Illegal divisor. It must not be zero.")); + 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 = LHS / RHS; - Result.Remainder = LHS % RHS; + 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 LHS, T RHS, T Epsilon = TNumericLimits::Epsilon()) +FORCEINLINE constexpr bool IsNearlyEqual(T A, T B, T Epsilon = TNumericLimits::Epsilon()) { - return Math::Abs(LHS - RHS) <= Epsilon; + return Math::Abs(A - B) <= Epsilon; } RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, IsNearlyEqual) @@ -270,6 +491,430 @@ 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