feat(numeric): add basic and floating point manipulation functions and the corresponding testing

This commit is contained in:
Redstone1024 2024-11-27 19:03:54 +08:00
parent 84a41387ae
commit f5c47fe677
5 changed files with 332 additions and 1 deletions

View File

@ -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<float32>::Infinity()));
always_check(Math::IsInfinity(-TNumericLimits<float32>::Infinity()));
always_check(Math::IsNaN( TNumericLimits<float32>::QuietNaN()));
always_check(Math::IsNaN(-TNumericLimits<float32>::QuietNaN()));
always_check(Math::IsNaN( TNumericLimits<float32>::SignalingNaN()));
always_check(Math::IsNaN(-TNumericLimits<float32>::SignalingNaN()));
always_check(Math::IsNaN(Math::NaN<float32>(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<float32>(4u)) == 4u);
enum class ETest : uint16 { A = 65535 };
always_check(Math::NaNPayload<ETest>(Math::NaN<float32>(ETest::A)) == ETest::A);
}
NAMESPACE_END(Testing)
NAMESPACE_MODULE_END(Utility)

View File

@ -21,7 +21,7 @@ FORCEINLINE constexpr T BitCast(const U& Value)
template <CUnsignedIntegral T>
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;

View File

@ -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 <CFloatingPoint T>
struct TFloatingTypeTraits
{
static_assert(sizeof(T) == -1, "Unsupported floating point type.");
};
template <>
struct TFloatingTypeTraits<float>
{
// 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<double>
{
// 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 <Concept T, Concept U> requires (CCommonType<T, U>) \
FORCEINLINE constexpr auto Func(T A, U B) \
{ \
return Math::Func<TCommonType<T, U>>(A, B); \
}
#define RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(Concept, Func) \
template <Concept T, Concept U, Concept V> requires (CCommonType<T, U, V>) \
FORCEINLINE constexpr auto Func(T A, U B, V C) \
{ \
return Math::Func<TCommonType<T, U, V>>(A, B, C); \
}
template <CSigned T>
FORCEINLINE constexpr T Abs(T A)
{
return A < 0 ? -A : A;
}
template <CUnsigned T>
FORCEINLINE constexpr T Abs(T A)
{
return A;
}
template <CArithmetic T>
FORCEINLINE constexpr T Sign(T A)
{
if (A == static_cast<T>(0)) return static_cast<T>( 0);
if (A < static_cast<T>(0)) return static_cast<T>(-1);
return static_cast<T>(1);
}
template <CArithmetic T, CArithmetic... Ts> requires (CCommonType<T, Ts...>)
FORCEINLINE constexpr auto Min(T A, Ts... InOther)
{
if constexpr (sizeof...(Ts) == 0) return A;
else
{
using FCommonT = TCommonType<T, Ts...>;
FCommonT B = Math::Min(InOther...);
return A < B ? A : B;
}
}
template <CArithmetic T, CArithmetic... Ts> requires (CCommonType<T, Ts...>)
FORCEINLINE constexpr auto Max(T A, Ts... InOther)
{
if constexpr (sizeof...(Ts) == 0) return A;
else
{
using FCommonT = TCommonType<T, Ts...>;
FCommonT B = Math::Max(InOther...);
return A > B ? A : B;
}
}
template <CIntegral T>
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 <CArithmetic T>
FORCEINLINE constexpr bool IsNearlyEqual(T LHS, T RHS, T Epsilon = TNumericLimits<T>::Epsilon())
{
return Math::Abs<T>(LHS - RHS) <= Epsilon;
}
RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, IsNearlyEqual)
RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(CArithmetic, IsNearlyEqual)
template <CArithmetic T>
FORCEINLINE constexpr bool IsNearlyZero(T A, T Epsilon = TNumericLimits<T>::Epsilon())
{
return Math::Abs<T>(A) <= Epsilon;
}
RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, IsNearlyZero)
template <CFloatingPoint T>
FORCEINLINE constexpr T IsInfinity(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return (IntegralValue & Traits::ExponentMask) == Traits::ExponentMask && (IntegralValue & Traits::MantissaMask) == 0;
}
template <CFloatingPoint T>
FORCEINLINE constexpr T IsNaN(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return (IntegralValue & Traits::ExponentMask) == Traits::ExponentMask && (IntegralValue & Traits::MantissaMask) != 0;
}
template <CFloatingPoint T>
FORCEINLINE constexpr T IsNormal(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return (IntegralValue & Traits::ExponentMask) != 0 && (IntegralValue & Traits::ExponentMask) != Traits::ExponentMask;
}
template <CFloatingPoint T>
FORCEINLINE constexpr T IsDenorm(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return (IntegralValue & Traits::ExponentMask) == 0 && (IntegralValue & Traits::MantissaMask) != 0;
}
template <CFloatingPoint T>
FORCEINLINE constexpr bool IsNegative(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return (IntegralValue & Traits::SignMask) >> Traits::SignShift;
}
template <CFloatingPoint T>
FORCEINLINE constexpr uint Exponent(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return ((IntegralValue & Traits::ExponentMask) >> Traits::ExponentShift) - Traits::ExponentBias;
}
template <CFloatingPoint T, CUnsignedIntegral U>
FORCEINLINE constexpr T NaN(U Payload)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
checkf(Payload != 0, TEXT("Illegal payload. It must not be zero."));
checkf(Payload < (static_cast<typename Traits::FIntegralT>(1) << Traits::MantissaBits), TEXT("Illegal payload. It must be less than 2^MantissaBits."));
if (Payload == 0) return TNumericLimits<T>::QuietNaN();
typename Traits::FIntegralT ValidPayload = Payload & Traits::MantissaMask;
return Math::BitCast<T>(ValidPayload | Traits::ExponentMask);
}
template <CFloatingPoint T, CEnum U>
FORCEINLINE constexpr T NaN(U Payload)
{
TUnderlyingType<U> IntegralValue = static_cast<TUnderlyingType<U>>(Payload);
return Math::NaN<T>(IntegralValue);
}
template <CFloatingPoint T>
FORCEINLINE constexpr auto NaNPayload(T A)
{
using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits<T>;
auto IntegralValue = Math::BitCast<typename Traits::FIntegralT>(A);
return IntegralValue & Traits::MantissaMask;
}
template <CEnum T, CFloatingPoint U>
FORCEINLINE constexpr auto NaNPayload(U A)
{
return static_cast<T>(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

View File

@ -5,3 +5,4 @@
#include "Numeric/Limits.h"
#include "Numeric/Numbers.h"
#include "Numeric/Bit.h"
#include "Numeric/Math.h"

View File

@ -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)