From c96995ea92369f3b0dc5e52c481e010a3489cbcc Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Tue, 3 Dec 2024 21:24:34 +0800 Subject: [PATCH] feat(numeric): add remaining standard math functions and the corresponding testing --- .../Source/Private/Testing/NumericTesting.cpp | 437 +++++++++++ Redcraft.Utility/Source/Public/Numeric/Math.h | 736 +++++++++++------- 2 files changed, 880 insertions(+), 293 deletions(-) diff --git a/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp b/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp index 286c7df..4b443a2 100644 --- a/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/NumericTesting.cpp @@ -123,6 +123,94 @@ void TestBit() void TestMath() { + always_check( Math::IsWithin(0, 0, 1)); + always_check(!Math::IsWithin(1, 0, 1)); + always_check(!Math::IsWithin(2, 0, 1)); + + always_check( Math::IsWithinInclusive(0, 0, 1)); + always_check( Math::IsWithinInclusive(1, 0, 1)); + always_check(!Math::IsWithinInclusive(2, 0, 1)); + + always_check(Math::IsNearlyEqual(Math::Trunc(2.00), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Trunc(2.25), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Trunc(2.75), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Trunc(3.00), 3.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Trunc(-2.00), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Trunc(-2.25), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Trunc(-2.75), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Trunc(-3.00), -3.0, 1e-8)); + + always_check(Math::TruncTo(2.00) == 2); + always_check(Math::TruncTo(2.25) == 2); + always_check(Math::TruncTo(2.75) == 2); + always_check(Math::TruncTo(3.00) == 3); + + always_check(Math::TruncTo(-2.00) == -2); + always_check(Math::TruncTo(-2.25) == -2); + always_check(Math::TruncTo(-2.75) == -2); + always_check(Math::TruncTo(-3.00) == -3); + + always_check(Math::IsNearlyEqual(Math::Ceil(2.00), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Ceil(2.25), 3.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Ceil(2.75), 3.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Ceil(3.00), 3.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Ceil(-2.00), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Ceil(-2.25), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Ceil(-2.75), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Ceil(-3.00), -3.0, 1e-8)); + + always_check(Math::CeilTo(2.00) == 2); + always_check(Math::CeilTo(2.25) == 3); + always_check(Math::CeilTo(2.75) == 3); + always_check(Math::CeilTo(3.00) == 3); + + always_check(Math::CeilTo(-2.00) == -2); + always_check(Math::CeilTo(-2.25) == -2); + always_check(Math::CeilTo(-2.75) == -2); + always_check(Math::CeilTo(-3.00) == -3); + + always_check(Math::IsNearlyEqual(Math::Floor(2.00), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Floor(2.25), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Floor(2.75), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Floor(3.00), 3.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Floor(-2.00), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Floor(-2.25), -3.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Floor(-2.75), -3.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Floor(-3.00), -3.0, 1e-8)); + + always_check(Math::FloorTo(2.00) == 2); + always_check(Math::FloorTo(2.25) == 2); + always_check(Math::FloorTo(2.75) == 2); + always_check(Math::FloorTo(3.00) == 3); + + always_check(Math::FloorTo(-2.00) == -2); + always_check(Math::FloorTo(-2.25) == -3); + always_check(Math::FloorTo(-2.75) == -3); + always_check(Math::FloorTo(-3.00) == -3); + + always_check(Math::IsNearlyEqual(Math::Round(2.00), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Round(2.25), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Round(2.75), 3.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Round(3.00), 3.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Round(-2.00), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Round(-2.25), -2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Round(-2.75), -3.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Round(-3.00), -3.0, 1e-8)); + + always_check(Math::RoundTo(2.00) == 2); + always_check(Math::RoundTo(2.25) == 2); + always_check(Math::RoundTo(2.75) == 3); + always_check(Math::RoundTo(3.00) == 3); + + always_check(Math::RoundTo(-2.00) == -2); + always_check(Math::RoundTo(-2.25) == -2); + always_check(Math::RoundTo(-2.75) == -3); + always_check(Math::RoundTo(-3.00) == -3); + always_check(Math::Abs(-1) == 1); always_check(Math::Abs( 0) == 0); always_check(Math::Abs( 1) == 1); @@ -136,6 +224,50 @@ void TestMath() always_check(Math::Max(1, 2, 3, 4, 5) == 5); always_check(Math::Max(5, 4, 3, 2, 1) == 5); + always_check(Math::MinIndex(1, 2, 3, 4, 5) == 0); + always_check(Math::MinIndex(5, 4, 3, 2, 1) == 4); + always_check(Math::MaxIndex(1, 2, 3, 4, 5) == 4); + always_check(Math::MaxIndex(5, 4, 3, 2, 1) == 0); + + always_check(Math::Div( 5, 2).Quotient == 2); + always_check(Math::Div( 5, 2).Remainder == 1); + always_check(Math::Div( 5, -2).Quotient == -2); + always_check(Math::Div( 5, -2).Remainder == 1); + always_check(Math::Div(-5, 2).Quotient == -2); + always_check(Math::Div(-5, 2).Remainder == -1); + always_check(Math::Div(-5, -2).Quotient == 2); + always_check(Math::Div(-5, -2).Remainder == -1); + + always_check(Math::DivAndCeil(4 + 0, 4) == 1); + always_check(Math::DivAndCeil(4 + 1, 4) == 2); + always_check(Math::DivAndCeil(4 + 3, 4) == 2); + always_check(Math::DivAndCeil(4 + 4, 4) == 2); + + always_check(Math::DivAndCeil(-4 - 0, 4) == -1); + always_check(Math::DivAndCeil(-4 - 1, 4) == -1); + always_check(Math::DivAndCeil(-4 - 3, 4) == -1); + always_check(Math::DivAndCeil(-4 - 4, 4) == -2); + + always_check(Math::DivAndFloor(4 + 0, 4) == 1); + always_check(Math::DivAndFloor(4 + 1, 4) == 1); + always_check(Math::DivAndFloor(4 + 3, 4) == 1); + always_check(Math::DivAndFloor(4 + 4, 4) == 2); + + always_check(Math::DivAndFloor(-4 - 0, 4) == -1); + always_check(Math::DivAndFloor(-4 - 1, 4) == -2); + always_check(Math::DivAndFloor(-4 - 3, 4) == -2); + always_check(Math::DivAndFloor(-4 - 4, 4) == -2); + + always_check(Math::DivAndRound(4 + 0, 4) == 1); + always_check(Math::DivAndRound(4 + 1, 4) == 1); + always_check(Math::DivAndRound(4 + 3, 4) == 2); + always_check(Math::DivAndRound(4 + 4, 4) == 2); + + always_check(Math::DivAndRound(-4 - 0, 4) == -1); + always_check(Math::DivAndRound(-4 - 1, 4) == -1); + always_check(Math::DivAndRound(-4 - 3, 4) == -2); + always_check(Math::DivAndRound(-4 - 4, 4) == -2); + always_check(Math::IsNearlyEqual(4.0, 4.0)); always_check(Math::IsNearlyZero(0.0)); @@ -167,6 +299,311 @@ void TestMath() enum class ETest : uint16 { A = 65535 }; always_check(Math::NaNPayload(Math::NaN(ETest::A)) == ETest::A); + + always_check(Math::IsNearlyEqual(Math::FMod(5.0, 2.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::FMod(5.0, 2.5), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::FMod(5.0, 3.0), 2.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::FMod(-5.0, 2.0), -1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::FMod(-5.0, 2.5), -0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::FMod(-5.0, 3.0), -2.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Remainder(5.0, 2.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Remainder(5.0, 2.5), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Remainder(5.0, 3.0), -1.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Remainder(-5.0, 2.0), -1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Remainder(-5.0, 2.5), -0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Remainder(-5.0, 3.0), 1.0, 1e-8)); + + always_check(Math::RemQuo(5.0, 2.0).Quotient == 2); + always_check(Math::RemQuo(5.0, 2.5).Quotient == 2); + always_check(Math::RemQuo(5.0, 3.0).Quotient == 2); + + always_check(Math::IsNearlyEqual(Math::RemQuo(5.0, 2.0).Remainder, 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::RemQuo(5.0, 2.5).Remainder, 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::RemQuo(5.0, 3.0).Remainder, -1.0, 1e-8)); + + always_check(Math::RemQuo(-5.0, 2.0).Quotient == -2); + always_check(Math::RemQuo(-5.0, 2.5).Quotient == -2); + always_check(Math::RemQuo(-5.0, 3.0).Quotient == -2); + + always_check(Math::IsNearlyEqual(Math::RemQuo(-5.0, 2.0).Remainder, -1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::RemQuo(-5.0, 2.5).Remainder, -0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::RemQuo(-5.0, 3.0).Remainder, 1.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::ModF(123.456).IntegralPart, 123.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::ModF(123.456).FractionalPart, 0.456, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Exp(-1.5), 0.2231301601, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp(-1.0), 0.3678794412, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp( 0.0), 1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp( 1.0), 2.7182818284, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp( 1.5), 4.4816890703, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Exp2(-1.5), 0.3535533906, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp2(-1.0), 0.5000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp2( 0.0), 1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp2( 1.0), 2.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Exp2( 1.5), 2.8284271247, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::ExpMinus1(-1.5), -0.7768698398, 1e-8)); + always_check(Math::IsNearlyEqual(Math::ExpMinus1(-1.0), -0.6321205588, 1e-8)); + always_check(Math::IsNearlyEqual(Math::ExpMinus1( 0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::ExpMinus1( 1.0), 1.7182818284, 1e-8)); + always_check(Math::IsNearlyEqual(Math::ExpMinus1( 1.5), 3.4816890703, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Log(0.5), -0.6931471806, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log(1.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log(1.5), 0.4054651081, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log(2.0), 0.6931471806, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log(2.5), 0.9162907319, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Log2(0.5), -1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log2(1.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log2(1.5), 0.5849625007, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log2(2.0), 1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log2(2.5), 1.3219280949, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Log10(0.5), -0.3010299957, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log10(1.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log10(1.5), 0.1760912591, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log10(2.0), 0.3010299957, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log10(2.5), 0.3979400087, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Log1Plus(0.5), 0.4054651081, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log1Plus(1.0), 0.6931471806, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log1Plus(1.5), 0.9162907319, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log1Plus(2.0), 1.0986122887, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Log1Plus(2.5), 1.2527629685, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Square(0.0), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Square(1.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Square(2.0), 4.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Square(3.0), 9.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Cube(0.0), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cube(1.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cube(2.0), 8.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cube(3.0), 27.0, 1e-8)); + + always_check(Math::Pow(2, 0) == 1); + always_check(Math::Pow(2, 1) == 2); + always_check(Math::Pow(2, 2) == 4); + always_check(Math::Pow(2, 3) == 8); + + always_check(Math::IsNearlyEqual(Math::Pow(2.0, 0.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Pow(2.0, 1.0), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Pow(2.0, 2.0), 4.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Pow(2.0, 3.0), 8.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Sqrt(0), 0, 1)); + always_check(Math::IsNearlyEqual(Math::Sqrt(1), 1, 1)); + always_check(Math::IsNearlyEqual(Math::Sqrt(4), 2, 1)); + always_check(Math::IsNearlyEqual(Math::Sqrt(8), 2, 1)); + + always_check(Math::IsNearlyEqual(Math::Sqrt(0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sqrt(1.0), 1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sqrt(2.0), 1.4142135624, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sqrt(3.0), 1.7320508076, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Cbrt(0), 0, 1)); + always_check(Math::IsNearlyEqual(Math::Cbrt(1), 1, 1)); + always_check(Math::IsNearlyEqual(Math::Cbrt(4), 1, 1)); + always_check(Math::IsNearlyEqual(Math::Cbrt(8), 2, 1)); + + always_check(Math::IsNearlyEqual(Math::Cbrt(0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cbrt(1.0), 1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cbrt(2.0), 1.2599210499, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cbrt(3.0), 1.4422495703, 1e-8)); + + always_check(Math::Sum(1, 2, 3, 4, 5) == 15); + + always_check(Math::SquaredSum(1, 2, 3, 4, 5) == 55); + + always_check(Math::Avg(1, 2, 3, 4, 5) == 3); + + always_check(Math::IsNearlyEqual(Math::Hypot(1.0, 2.0, 3.0, 4.0, 5.0), 7.4161984871, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Sin(-9.0), -0.4121184852, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin(-6.0), 0.2794154982, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin(-2.0), -0.9092974268, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin(-1.0), -0.8414709848, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin( 0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin( 1.0), 0.8414709848, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin( 2.0), 0.9092974268, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin( 6.0), -0.2794154982, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Sin( 9.0), 0.4121184852, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Cos(-9.0), -0.9111302619, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos(-6.0), 0.9601702866, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos(-2.0), -0.4161468365, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos(-1.0), 0.5403023059, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos( 0.0), 1.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos( 1.0), 0.5403023059, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos( 2.0), -0.4161468365, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos( 6.0), 0.9601702866, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Cos( 9.0), -0.9111302619, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Tan(-9.0), 0.4523156594, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan(-6.0), 0.2910061914, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan(-2.0), 2.1850398633, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan(-1.0), -1.5574077247, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan( 0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan( 1.0), 1.5574077247, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan( 2.0), -2.1850398633, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan( 6.0), -0.2910061914, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Tan( 9.0), -0.4523156594, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Asin(-1.0), -1.5707963268, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Asin(-0.5), -0.5235987756, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Asin( 0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Asin( 0.5), 0.5235987756, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Asin( 1.0), 1.5707963268, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Acos(-1.0), 3.1415926536, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Acos(-0.5), 2.0943951024, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Acos( 0.0), 1.5707963268, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Acos( 0.5), 1.0471975512, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Acos( 1.0), 0.0000000000, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Atan(-1.0), -0.7853981634, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan(-0.5), -0.4636476090, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan( 0.0), 0.0000000000, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan( 0.5), 0.4636476090, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan( 1.0), 0.7853981634, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Atan2(-1.0, -1.0), -2.3561944902, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan2(-0.5, -1.0), -2.6779450446, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan2( 0.0, -1.0), 3.1415926536, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan2( 0.5, -1.0), 2.6779450446, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Atan2( 1.0, -1.0), 2.3561944902, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Sinh(-9.0), -4051.5419020, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh(-6.0), -201.71315737, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh(-2.0), -3.6268604078, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh(-1.0), -1.1752011936, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh( 0.0), 0.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh( 1.0), 1.1752011936, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh( 2.0), 3.6268604078, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh( 6.0), 201.71315737, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Sinh( 9.0), 4051.5419020, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::Cosh(-9.0), 4051.5420254, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh(-6.0), 201.71563612, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh(-2.0), 3.7621956911, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh(-1.0), 1.5430806348, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh( 0.0), 1.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh( 1.0), 1.5430806348, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh( 2.0), 3.7621956911, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh( 6.0), 201.71563612, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Cosh( 9.0), 4051.5420254, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::Tanh(-9.0), -1.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh(-6.0), -1.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh(-2.0), -0.9640275801, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh(-1.0), -0.7615941559, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh( 0.0), 0.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh( 1.0), 0.7615941559, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh( 2.0), 0.9640275801, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh( 6.0), 1.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Tanh( 9.0), 1.0000000000, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::Asinh(-9.0), -2.8934439858, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh(-6.0), -2.4917798526, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh(-2.0), -1.4436354752, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh(-1.0), -0.8813735870, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh( 0.0), 0.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh( 1.0), 0.8813735870, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh( 2.0), 1.4436354752, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh( 6.0), 2.4917798526, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Asinh( 9.0), 2.8934439858, 1e-4)); + + always_check(Math::IsNaN( Math::Acosh(-9.0) )); + always_check(Math::IsNaN( Math::Acosh(-6.0) )); + always_check(Math::IsNaN( Math::Acosh(-2.0) )); + always_check(Math::IsNaN( Math::Acosh(-1.0) )); + always_check(Math::IsNaN( Math::Acosh( 0.0) )); + always_check(Math::IsNearlyEqual(Math::Acosh( 1.0), 0.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Acosh( 2.0), 1.3169578969, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Acosh( 6.0), 2.4778887302, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Acosh( 9.0), 2.8872709503, 1e-4)); + + always_check(Math::IsInfinity( Math::Atanh(-1.0) )); + always_check(Math::IsNearlyEqual(Math::Atanh(-0.5), -0.5493061443, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Atanh( 0.0), 0.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Atanh( 0.5), 0.5493061443, 1e-4)); + always_check(Math::IsInfinity( Math::Atanh( 1.0) )); + + always_check(Math::IsNearlyEqual(Math::Erf(-6.0), -1.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erf(-2.0), -0.9953222650, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erf(-1.0), -0.8427007929, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erf( 0.0), 0.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erf( 1.0), 0.8427007929, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erf( 2.0), 0.9953222650, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erf( 6.0), 1.0000000000, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::Erfc(-6.0), 2.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erfc(-2.0), 1.9953222650, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erfc(-1.0), 1.8427007929, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erfc( 0.0), 1.0000000000, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erfc( 1.0), 0.1572992070, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erfc( 2.0), 0.0046777349, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Erfc( 6.0), 0.0000000000, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::Gamma(-0.75), -4.8341465442, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Gamma(-0.50), -3.5449077018, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Gamma(-0.25), -4.9016668098, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Gamma( 0.25), 3.6256099082, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Gamma( 0.50), 1.7724538509, 1e-4)); + always_check(Math::IsNearlyEqual(Math::Gamma( 0.75), 1.2254167025, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::LogGamma(-0.75), 1.5757045971, 1e-4)); + always_check(Math::IsNearlyEqual(Math::LogGamma(-0.50), 1.2655121235, 1e-4)); + always_check(Math::IsNearlyEqual(Math::LogGamma(-0.25), 1.5895753125, 1e-4)); + always_check(Math::IsNearlyEqual(Math::LogGamma( 0.25), 1.2880225246, 1e-4)); + always_check(Math::IsNearlyEqual(Math::LogGamma( 0.50), 0.5723649429, 1e-4)); + always_check(Math::IsNearlyEqual(Math::LogGamma( 0.75), 0.2032809514, 1e-4)); + + always_check(Math::IsNearlyEqual(Math::LdExp(1.0, 0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::LdExp(1.0, 1), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::LdExp(1.0, 2), 4.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::LdExp(1.0, 3), 8.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::RadiansToDegrees(0.0), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::RadiansToDegrees(Math::TNumbers::Pi), 180.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::RadiansToDegrees(Math::TNumbers::TwoPi), 360.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::DegreesToRadians( 0.0), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::DegreesToRadians(180.0), Math::TNumbers::Pi, 1e-8)); + always_check(Math::IsNearlyEqual(Math::DegreesToRadians(360.0), Math::TNumbers::TwoPi, 1e-8)); + + always_check(Math::GCD(0, 0) == 0); + always_check(Math::GCD(0, 1) == 1); + always_check(Math::GCD(9, 6) == 3); + + always_check(Math::LCM(0, 0) == 0); + always_check(Math::LCM(0, 1) == 0); + always_check(Math::LCM(9, 6) == 18); + + always_check(Math::IsNearlyEqual(Math::Clamp(0.0, 1.0, 2.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Clamp(1.0, 1.0, 2.0), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Clamp(2.0, 1.0, 2.0), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Clamp(3.0, 1.0, 2.0), 2.0, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::WrappingClamp(0.5, 0.0, 2.0), 0.5, 1e-8)); + always_check(Math::IsNearlyEqual(Math::WrappingClamp(1.5, 0.0, 2.0), 1.5, 1e-8)); + always_check(Math::IsNearlyEqual(Math::WrappingClamp(2.5, 0.0, 2.0), 0.5, 1e-8)); + always_check(Math::IsNearlyEqual(Math::WrappingClamp(3.5, 0.0, 2.0), 1.5, 1e-8)); + + always_check(Math::IsNearlyEqual(Math::Lerp(0.0, 2.0, 0.0), 0.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Lerp(0.0, 2.0, 0.5), 1.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Lerp(0.0, 2.0, 1.0), 2.0, 1e-8)); + always_check(Math::IsNearlyEqual(Math::Lerp(0.0, 2.0, 1.5), 3.0, 1e-8)); + + always_check(static_cast(Math::LerpStable(0, 255, 0.0)) == 0); + always_check(static_cast(Math::LerpStable(0, 255, 0.5)) == 127); + always_check(static_cast(Math::LerpStable(0, 255, 1.0)) == 255); } NAMESPACE_END(Testing) diff --git a/Redcraft.Utility/Source/Public/Numeric/Math.h b/Redcraft.Utility/Source/Public/Numeric/Math.h index e4b2bad..73803c8 100644 --- a/Redcraft.Utility/Source/Public/Numeric/Math.h +++ b/Redcraft.Utility/Source/Public/Numeric/Math.h @@ -3,7 +3,9 @@ #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" @@ -75,64 +77,98 @@ struct TFloatingTypeTraits 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) \ - FORCEINLINE constexpr auto Func(T A, U B) \ + NODISCARD FORCEINLINE constexpr auto Func(T A, U B) \ { \ - return Math::Func>(A, B); \ + using FCommonT = TCommonType; \ + \ + return Math::Func( \ + static_cast(A), \ + static_cast(B) \ + ); \ } #define RESOLVE_ARITHMETIC_AMBIGUITY_3_ARGS(Concept, Func) \ template requires (CCommonType) \ - FORCEINLINE constexpr auto Func(T A, U B, V C) \ + NODISCARD FORCEINLINE constexpr auto Func(T A, U B, V C) \ { \ - return Math::Func>(A, B, 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 -FORCEINLINE constexpr T IsWithin(T A, T MinValue, T MaxValue) +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 -FORCEINLINE constexpr T IsWithinInclusive(T A, T MinValue, T MaxValue) +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 -FORCEINLINE constexpr T Trunc(T A) +NODISCARD 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(); + FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(trunc) } +/* @return The nearest integer not greater in magnitude than the given value. */ template -FORCEINLINE constexpr T TruncTo(U A) +NODISCARD FORCEINLINE constexpr T TruncTo(U A) { - if constexpr (CIntegral && CIntegral) return A; + if constexpr (CIntegral) + { + if constexpr (!CIntegral) + { + return static_cast(A); + } + else return A; + } else if constexpr (CSameAs || CSameAs) { - return NAMESPACE_STD::trunc(static_cast(A)); - } - - else if constexpr (CIntegral) - { - return static_cast(A); + return Math::Trunc(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); @@ -140,38 +176,35 @@ FORCEINLINE constexpr T TruncTo(U A) return TNumericLimits::QuietNaN(); } +/* @return The nearest integer not less than the given value. */ template -FORCEINLINE constexpr T Ceil(T A) +NODISCARD 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(); + FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(ceil) } +/* @return The nearest integer not less than the given value. */ template -FORCEINLINE constexpr T CeilTo(U A) +NODISCARD FORCEINLINE constexpr T CeilTo(U A) { - if constexpr (CIntegral && CIntegral) return 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 NAMESPACE_STD::ceil(static_cast(A)); - } - - else if constexpr (CIntegral) - { - T I = Math::TruncTo(A); - - I += static_cast(I) < A; - - return I; + return Math::Ceil(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); @@ -179,38 +212,35 @@ FORCEINLINE constexpr T CeilTo(U A) return TNumericLimits::QuietNaN(); } +/* @return The nearest integer not greater than the given value. */ template -FORCEINLINE constexpr T Floor(T A) +NODISCARD 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(); + FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(floor) } +/* @return The nearest integer not greater than the given value. */ template -FORCEINLINE constexpr T FloorTo(U A) +NODISCARD FORCEINLINE constexpr T FloorTo(U A) { - if constexpr (CIntegral && CIntegral) return 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 NAMESPACE_STD::floor(static_cast(A)); - } - - else if constexpr (CIntegral) - { - T I = Math::TruncTo(A); - - I -= static_cast(I) > A; - - return I; + return Math::Floor(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); @@ -218,34 +248,31 @@ FORCEINLINE constexpr T FloorTo(U A) return TNumericLimits::QuietNaN(); } +/* @return The nearest integer to the given value, rounding away from zero in halfway cases. */ template -FORCEINLINE constexpr T Round(T A) +NODISCARD 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(); + FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(round) } +/* @return The nearest integer to the given value, rounding away from zero in halfway cases. */ template -FORCEINLINE constexpr T RoundTo(U A) +NODISCARD FORCEINLINE constexpr T RoundTo(U A) { - if constexpr (CIntegral && CIntegral) return A; + if constexpr (CIntegral) + { + if constexpr (!CIntegral) + { + return Math::FloorTo(A + static_cast(0.5)); + } + else 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)); + return Math::Round(static_cast(A)); } else static_assert(sizeof(T) == -1, "Unsupported floating point type."); @@ -253,20 +280,23 @@ FORCEINLINE constexpr T RoundTo(U A) return TNumericLimits::QuietNaN(); } +/* @return The absolute value of the given value. */ template -FORCEINLINE constexpr T Abs(T A) +NODISCARD FORCEINLINE constexpr T Abs(T A) { return A < 0 ? -A : A; } +/* @return The absolute value of the given value. */ template -FORCEINLINE constexpr T Abs(T A) +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 -FORCEINLINE constexpr T Sign(T A) +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); @@ -274,8 +304,9 @@ FORCEINLINE constexpr T Sign(T A) return static_cast(1); } +/* @return The minimum value of the given values. */ template requires (CCommonType) -FORCEINLINE constexpr auto Min(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr auto Min(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; @@ -289,8 +320,9 @@ FORCEINLINE constexpr auto Min(T A, Ts... InOther) } } +/* @return The maximum value of the given values. */ template requires (CCommonType) -FORCEINLINE constexpr auto Max(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr auto Max(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; @@ -305,8 +337,9 @@ FORCEINLINE constexpr auto Max(T A, Ts... InOther) } +/* @return The index of the minimum value of the given values. */ template requires (CCommonType) -FORCEINLINE constexpr size_t MinIndex(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr size_t MinIndex(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return 0; @@ -322,8 +355,9 @@ FORCEINLINE constexpr size_t MinIndex(T A, Ts... InOther) } } +/* @return The index of the maximum value of the given values. */ template requires (CCommonType) -FORCEINLINE constexpr size_t MaxIndex(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr size_t MaxIndex(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return 0; @@ -340,11 +374,15 @@ FORCEINLINE constexpr size_t MaxIndex(T A, Ts... InOther) } template -FORCEINLINE constexpr auto Div(T A, T B) +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.")); - struct { T Quotient; T Remainder; } Result; + Math::TDiv Result; Result.Quotient = A / B; Result.Remainder = A % B; @@ -352,30 +390,34 @@ FORCEINLINE constexpr auto Div(T A, T B) return Result; } +RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CIntegral, Div) + +/* @return The quotient of the division of the given values and rounds up. */ template -FORCEINLINE constexpr T DivAndCeil(T A, T B) +NODISCARD FORCEINLINE constexpr T DivAndCeil(T A, T B) { - return (A + B - 1) / B; + return A >= 0 ? (A + B - 1) / B : A / B; } +/* @return The quotient of the division of the given values and rounds down. */ template -FORCEINLINE constexpr T DivAndFloor(T A, T B) +NODISCARD FORCEINLINE constexpr T DivAndFloor(T A, T B) { - return A / B; + return A >= 0 ? A / B : (A - B + 1) / B; } +/* @return The quotient of the division of the given values and rounds to nearest. */ template -FORCEINLINE constexpr T DivAndRound(T A, T B) +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, Div) - +/* @return true if the given values are nearly equal, false otherwise. */ template -FORCEINLINE constexpr bool IsNearlyEqual(T A, T B, T Epsilon = TNumericLimits::Epsilon()) +NODISCARD FORCEINLINE constexpr bool IsNearlyEqual(T A, T B, T Epsilon = TNumericLimits::Epsilon()) { return Math::Abs(A - B) <= Epsilon; } @@ -383,16 +425,18 @@ FORCEINLINE constexpr bool IsNearlyEqual(T A, T B, T Epsilon = TNumericLimits 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 -FORCEINLINE constexpr bool IsNearlyZero(T A, T Epsilon = TNumericLimits::Epsilon()) +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 -FORCEINLINE constexpr T IsInfinity(T A) +NODISCARD FORCEINLINE constexpr T IsInfinity(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -401,8 +445,9 @@ FORCEINLINE constexpr T IsInfinity(T A) return (IntegralValue & Traits::ExponentMask) == Traits::ExponentMask && (IntegralValue & Traits::MantissaMask) == 0; } +/* @return true if the given value is NaN, false otherwise. */ template -FORCEINLINE constexpr T IsNaN(T A) +NODISCARD FORCEINLINE constexpr T IsNaN(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -411,8 +456,9 @@ FORCEINLINE constexpr T IsNaN(T A) return (IntegralValue & Traits::ExponentMask) == Traits::ExponentMask && (IntegralValue & Traits::MantissaMask) != 0; } +/* @return true if the given value is normal, false otherwise. */ template -FORCEINLINE constexpr T IsNormal(T A) +NODISCARD FORCEINLINE constexpr T IsNormal(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -421,8 +467,9 @@ FORCEINLINE constexpr T IsNormal(T A) return (IntegralValue & Traits::ExponentMask) != 0 && (IntegralValue & Traits::ExponentMask) != Traits::ExponentMask; } +/* @return true if the given value is subnormal, false otherwise. */ template -FORCEINLINE constexpr T IsDenorm(T A) +NODISCARD FORCEINLINE constexpr T IsDenorm(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -431,8 +478,9 @@ FORCEINLINE constexpr T IsDenorm(T A) return (IntegralValue & Traits::ExponentMask) == 0 && (IntegralValue & Traits::MantissaMask) != 0; } +/* @return true if the given value is negative, even -0.0, false otherwise. */ template -FORCEINLINE constexpr bool IsNegative(T A) +NODISCARD FORCEINLINE constexpr bool IsNegative(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -441,8 +489,9 @@ FORCEINLINE constexpr bool IsNegative(T A) return (IntegralValue & Traits::SignMask) >> Traits::SignShift; } +/* @return The exponent of the given value. */ template -FORCEINLINE constexpr uint Exponent(T A) +NODISCARD FORCEINLINE constexpr uint Exponent(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -451,8 +500,9 @@ FORCEINLINE constexpr uint Exponent(T A) return ((IntegralValue & Traits::ExponentMask) >> Traits::ExponentShift) - Traits::ExponentBias; } +/* @return The NaN value with the given payload. */ template -FORCEINLINE constexpr T NaN(U Payload) +NODISCARD FORCEINLINE constexpr T NaN(U Payload) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -467,16 +517,18 @@ FORCEINLINE constexpr T NaN(U Payload) return Math::BitCast(ValidPayload | Traits::ExponentMask); } +/* @return The NaN value with the given payload. */ template -FORCEINLINE constexpr T NaN(U Payload) +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 -FORCEINLINE constexpr auto NaNPayload(T A) +NODISCARD FORCEINLINE constexpr auto NaNPayload(T A) { using Traits = NAMESPACE_PRIVATE::TFloatingTypeTraits; @@ -485,46 +537,33 @@ FORCEINLINE constexpr auto NaNPayload(T A) return IntegralValue & Traits::MantissaMask; } +/* @return The NaN payload of the given value. */ template -FORCEINLINE constexpr auto NaNPayload(U A) +NODISCARD FORCEINLINE constexpr auto NaNPayload(U A) { return static_cast(Math::NaNPayload(A)); } +/* @return The remainder of the floating point division operation. */ 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(); -} +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 -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(); -} +NODISCARD FORCEINLINE constexpr T Remainder(T A, T B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(remainder) RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, Remainder) template -FORCEINLINE constexpr auto RemQuo(T A, T B) +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) { - struct { int Quotient; T Remainder; } Result; + Math::TRemQuo Result; if constexpr (CSameAs || CSameAs) { @@ -541,9 +580,13 @@ FORCEINLINE constexpr auto RemQuo(T A, T B) RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, RemQuo) template -FORCEINLINE constexpr auto ModF(T A) +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) { - struct { T IntegralPart; T FractionalPart; } Result; + Math::TModF Result; if constexpr (CSameAs || CSameAs) { @@ -557,111 +600,51 @@ FORCEINLINE constexpr auto ModF(T A) return Result; } +/* @return The e raised to the given power. */ 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(); -} +NODISCARD FORCEINLINE constexpr T Exp(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(exp) +/* @return The 2 raised to the given power. */ 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(); -} +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 -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(); -} +NODISCARD FORCEINLINE constexpr T ExpMinus1(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(expm1) +/* @return The natural logarithm of the given value. */ 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(); -} +NODISCARD FORCEINLINE constexpr T Log(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log) +/* @return The base-2 logarithm of the given value. */ 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(); -} +NODISCARD FORCEINLINE constexpr T Log2(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log2) +/* @return The base-10 logarithm of the given value. */ 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(); -} +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 -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(); -} +NODISCARD FORCEINLINE constexpr T Log1Plus(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(log1p) +/* @return The square of the given values. */ template -FORCEINLINE constexpr T Square(T A) +NODISCARD FORCEINLINE constexpr T Square(T A) { return A * A; } +/* @return The cube of the given values. */ template -FORCEINLINE constexpr T Cube(T A) +NODISCARD FORCEINLINE constexpr T Cube(T A) { return A * A * A; } +/* @return The 'A' raised to the power of 'B'. */ template -FORCEINLINE constexpr T Pow(T A, T B) +NODISCARD FORCEINLINE constexpr T Pow(T A, T B) { if (B < 0) { @@ -682,23 +665,15 @@ FORCEINLINE constexpr T Pow(T A, T B) return Result; } +/* @return The 'A' raised to the power of 'B'. */ template -FORCEINLINE constexpr T Pow(T A, T B) -{ - if constexpr (CSameAs || CSameAs) - { - return NAMESPACE_STD::pow(A, B); - } +NODISCARD FORCEINLINE constexpr T Pow(T A, T B) FORWARD_FLOATING_POINT_IMPLEMENT_2_ARGS(pow) - else static_assert(sizeof(T) == -1, "Unsupported floating point type."); - - return TNumericLimits::QuietNaN(); -} - -RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CFloatingPoint, Pow) +RESOLVE_ARITHMETIC_AMBIGUITY_2_ARGS(CArithmetic, Pow) +/* @return The square root of the given value. */ template -FORCEINLINE constexpr T Sqrt(T A) +NODISCARD FORCEINLINE constexpr T Sqrt(T A) { if (A < 0) { @@ -707,6 +682,8 @@ FORCEINLINE constexpr T Sqrt(T A) return TNumericLimits::QuietNaN(); } + if (A == 0) return 0; + T X = A; while (true) @@ -719,24 +696,18 @@ FORCEINLINE constexpr T Sqrt(T A) } } +/* @return The square root of the given value. */ 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(); -} +NODISCARD FORCEINLINE constexpr T Sqrt(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(sqrt) +/* @return The cube root of the given value. */ template -FORCEINLINE constexpr T Cbrt(T A) +NODISCARD FORCEINLINE constexpr T Cbrt(T A) { if (A < 0) return -Math::Cbrt(-A); + if (A == 0) return 0; + T X = A; while (true) @@ -749,21 +720,13 @@ FORCEINLINE constexpr T Cbrt(T A) } } +/* @return The cube root of the given value. */ 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(); -} +NODISCARD FORCEINLINE constexpr T Cbrt(T A) FORWARD_FLOATING_POINT_IMPLEMENT_1_ARGS(cbrt) +/* @return The sum of the given value. */ template requires (CCommonType) -FORCEINLINE constexpr auto Sum(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr auto Sum(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return A; @@ -777,8 +740,9 @@ FORCEINLINE constexpr auto Sum(T A, Ts... InOther) } } +/* @return The sum of the squared values. */ template requires (CCommonType) -FORCEINLINE constexpr auto SquaredSum(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr auto SquaredSum(T A, Ts... InOther) { if constexpr (sizeof...(Ts) == 0) return Math::Square(A); @@ -786,83 +750,248 @@ FORCEINLINE constexpr auto SquaredSum(T A, Ts... InOther) { using FCommonT = TCommonType; - FCommonT Sum = A + Math::SquaredSum(InOther...); + FCommonT Sum = Math::Square(A) + Math::SquaredSum(InOther...); return Sum; } } +/* @return The average of the given values. */ template requires (CCommonType) -FORCEINLINE constexpr auto Avg(T A, Ts... InOther) +NODISCARD FORCEINLINE constexpr auto Avg(T A, Ts... InOther) { - if constexpr (sizeof...(Ts) == 0) return A; + 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 { - using FCommonT = TCommonType; - FCommonT Sum = A + Math::Sum(InOther...); - return Sum / (sizeof...(Ts) + 1); + return static_cast(Sum / Count); } } -template -FORCEINLINE constexpr T Hypot(T A) +/* @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) { - return Math::Abs(A); -} + using FCommonT = TCommonType; -template -FORCEINLINE constexpr auto Hypot(T A, U B) -{ - using FCommonT = TCommonType; + constexpr size_t Count = sizeof...(Ts) + 1; - if constexpr (CIntegral) return static_cast(Math::Sqrt(Math::Square(A) + Math::Square(B))); + if constexpr (Count == 1) return Math::Abs(A); - else if constexpr (CSameAs || CSameAs) + else if constexpr (Count == 2) { - return NAMESPACE_STD::hypot(static_cast(A), static_cast(B)); + 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 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) + else if constexpr (Count == 3) { - return NAMESPACE_STD::hypot(static_cast(A), static_cast(B), static_cast(C)); + 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 static_assert(sizeof(FCommonT) == -1, "Unsupported floating point type."); - - return TNumericLimits::QuietNaN(); + else return Math::Sqrt(Math::SquaredSum(A, InOther...)); } -template requires (CCommonType) -FORCEINLINE constexpr auto Hypot(Ts... 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 Math::Sqrt(Math::SquaredSum(InOther...)); + 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 -FORCEINLINE constexpr T Clamp(T A, T MinValue, T MaxValue) +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 -FORCEINLINE constexpr T WrappingClamp(T A, T MinValue, T MaxValue) +NODISCARD FORCEINLINE constexpr T WrappingClamp(T A, T MinValue, T MaxValue) { if (MinValue > MaxValue) { @@ -915,6 +1044,27 @@ FORCEINLINE constexpr T WrappingClamp(T A, T MinValue, T MaxValue) 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