From f5c47fe677d46172308bb74c420f0b3e82ec3705 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Wed, 27 Nov 2024 19:03:54 +0800 Subject: [PATCH] feat(numeric): add basic and floating point manipulation functions and the corresponding testing --- .../Source/Private/Testing/NumericTesting.cpp | 49 +++ Redcraft.Utility/Source/Public/Numeric/Bit.h | 2 +- Redcraft.Utility/Source/Public/Numeric/Math.h | 280 ++++++++++++++++++ .../Source/Public/Numeric/Numeric.h | 1 + .../Source/Public/Testing/NumericTesting.h | 1 + 5 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 Redcraft.Utility/Source/Public/Numeric/Math.h diff --git a/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp b/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp index 91074dc..286c7df 100644 --- a/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp @@ -13,6 +13,7 @@ void TestNumeric() { TestLiteral(); TestBit(); + TestMath(); } void TestLiteral() @@ -120,6 +121,54 @@ void TestBit() always_check(Math::RotateRight(0b00011101u8, -1) == 0b00111010u8); } +void TestMath() +{ + always_check(Math::Abs(-1) == 1); + always_check(Math::Abs( 0) == 0); + always_check(Math::Abs( 1) == 1); + + always_check(Math::Sign(-4) == -1); + always_check(Math::Sign( 0) == 0); + always_check(Math::Sign( 4) == 1); + + always_check(Math::Min(1, 2, 3, 4, 5) == 1); + always_check(Math::Min(5, 4, 3, 2, 1) == 1); + always_check(Math::Max(1, 2, 3, 4, 5) == 5); + always_check(Math::Max(5, 4, 3, 2, 1) == 5); + + always_check(Math::IsNearlyEqual(4.0, 4.0)); + + always_check(Math::IsNearlyZero(0.0)); + + always_check(Math::IsInfinity( TNumericLimits::Infinity())); + always_check(Math::IsInfinity(-TNumericLimits::Infinity())); + + always_check(Math::IsNaN( TNumericLimits::QuietNaN())); + always_check(Math::IsNaN(-TNumericLimits::QuietNaN())); + always_check(Math::IsNaN( TNumericLimits::SignalingNaN())); + always_check(Math::IsNaN(-TNumericLimits::SignalingNaN())); + + always_check(Math::IsNaN(Math::NaN(4u))); + + always_check(Math::IsNormal(1.0e4)); + always_check(Math::IsNormal(1.0e8)); + + always_check(!Math::IsNegative(+1.0)); + always_check(!Math::IsNegative(+0.0)); + always_check( Math::IsNegative(-0.0)); + always_check( Math::IsNegative(-1.0)); + + always_check(Math::Exponent(1.0) == 0); + always_check(Math::Exponent(2.0) == 1); + always_check(Math::Exponent(4.0) == 2); + + always_check(Math::NaNPayload(Math::NaN(4u)) == 4u); + + enum class ETest : uint16 { A = 65535 }; + + always_check(Math::NaNPayload(Math::NaN(ETest::A)) == ETest::A); +} + NAMESPACE_END(Testing) NAMESPACE_MODULE_END(Utility) diff --git a/Redcraft.Utility/Source/Public/Numeric/Bit.h b/Redcraft.Utility/Source/Public/Numeric/Bit.h index ed68a29..2170323 100644 --- a/Redcraft.Utility/Source/Public/Numeric/Bit.h +++ b/Redcraft.Utility/Source/Public/Numeric/Bit.h @@ -21,7 +21,7 @@ FORCEINLINE constexpr T BitCast(const U& Value) template FORCEINLINE constexpr T ByteSwap(T Value) { - static_assert(sizeof(T) <= 16, "ByteSwap only works with T up to 128 bits."); + static_assert(sizeof(T) <= 16, "ByteSwap only works with T up to 128 bits"); if constexpr (sizeof(T) == 1) return Value; diff --git a/Redcraft.Utility/Source/Public/Numeric/Math.h b/Redcraft.Utility/Source/Public/Numeric/Math.h new file mode 100644 index 0000000..5aea10d --- /dev/null +++ b/Redcraft.Utility/Source/Public/Numeric/Math.h @@ -0,0 +1,280 @@ +#pragma once + +#include "CoreTypes.h" +#include "Numeric/Bit.h" +#include "Numeric/Limits.h" +#include "TypeTraits/TypeTraits.h" +#include "Miscellaneous/AssertionMacros.h" + +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 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 +FORCEINLINE constexpr auto Div(T LHS, T RHS) +{ + checkf(RHS != 0, TEXT("Illegal divisor. It must not be zero.")); + + struct { T Quotient; T Remainder; } Result; + + Result.Quotient = LHS / RHS; + Result.Remainder = LHS % RHS; + + return Result; +} + +RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, Div) + +template +FORCEINLINE constexpr bool IsNearlyEqual(T LHS, T RHS, T Epsilon = TNumericLimits::Epsilon()) +{ + return Math::Abs(LHS - RHS) <= 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)); +} + +#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 diff --git a/Redcraft.Utility/Source/Public/Numeric/Numeric.h b/Redcraft.Utility/Source/Public/Numeric/Numeric.h index d2b2139..36a422c 100644 --- a/Redcraft.Utility/Source/Public/Numeric/Numeric.h +++ b/Redcraft.Utility/Source/Public/Numeric/Numeric.h @@ -5,3 +5,4 @@ #include "Numeric/Limits.h" #include "Numeric/Numbers.h" #include "Numeric/Bit.h" +#include "Numeric/Math.h" diff --git a/Redcraft.Utility/Source/Public/Testing/NumericTesting.h b/Redcraft.Utility/Source/Public/Testing/NumericTesting.h index 4f48614..a5e349d 100644 --- a/Redcraft.Utility/Source/Public/Testing/NumericTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/NumericTesting.h @@ -11,6 +11,7 @@ NAMESPACE_BEGIN(Testing) REDCRAFTUTILITY_API void TestNumeric(); REDCRAFTUTILITY_API void TestLiteral(); REDCRAFTUTILITY_API void TestBit(); +REDCRAFTUTILITY_API void TestMath(); NAMESPACE_END(Testing)