diff --git a/ccmath_headers.cmake b/ccmath_headers.cmake index ce4afe5a..eca95b3b 100644 --- a/ccmath_headers.cmake +++ b/ccmath_headers.cmake @@ -1,6 +1,7 @@ set(ccmath_internal_helpers_headers ${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/endian.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/narrow_cast.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/ccmath/internal/helpers/promote.hpp ) set(ccmath_internal_typetraits_headers diff --git a/include/ccmath/detail/basic/fmod.hpp b/include/ccmath/detail/basic/fmod.hpp index daa6dd13..241c1448 100644 --- a/include/ccmath/detail/basic/fmod.hpp +++ b/include/ccmath/detail/basic/fmod.hpp @@ -11,83 +11,71 @@ //#include #include - - #include "ccmath/detail/compare/isnan.hpp" #include "ccmath/detail/compare/isfinite.hpp" #include "ccmath/detail/nearest/trunc.hpp" #include "ccmath/detail/compare/signbit.hpp" -#include - - namespace ccm { namespace { namespace impl { - template - inline constexpr T fmod_impl_internal(T x, T y) + template , int> = 0> + inline constexpr T fmod_impl_calculate(T x, T y) { // Calculate the remainder of the division of x by y. - return x - (ccm::trunc(x / y) * y); + return x - (ccm::trunc(x / y) * y); + } + + template , int> = 0> + inline constexpr T fmod_impl_calculate(T x, T y) + { + // Calculate the remainder of the division of x by y. + // static_cast is required to prevent the compiler from complaining about narrowing with integer types. + return static_cast(x - (ccm::trunc(x / y) * y)); } // Special case for floating point types - template , bool> = true> - inline constexpr Real fmod_impl_switch(Real x, Real y) noexcept + template + inline constexpr T fmod_impl_check(T x, T y) noexcept { // NOTE: We do not raise FE_INVALID even is situations where the standard demands it. - if constexpr (std::numeric_limits::is_iec559) + if constexpr (std::numeric_limits::is_iec559) { // If x is ±0 and y is not zero, ±0 is returned. - if ((x == static_cast(0.0) || static_cast(x) == static_cast(-0.0)) && (y != static_cast(0.0))) + if ((x == static_cast(0.0) || static_cast(x) == static_cast(-0.0)) && (y != static_cast(0.0))) { // The standard specifies that plus or minus 0 is returned depending on the sign of x. if (ccm::signbit(x)) { - return -Real{0.0}; + return -static_cast(0.0); } else { - return Real{0.0}; + return static_cast(0.0); } } // If x is ±∞ and y is not NaN, NaN is returned. - if ((x == +std::numeric_limits::infinity() || x == -std::numeric_limits::infinity()) && !ccm::isnan(y)) + if ((x == +std::numeric_limits::infinity() || x == -std::numeric_limits::infinity()) && !ccm::isnan(y)) { - // The standard specifies that we always return the same sign as x. - if (ccm::signbit(x)) - { - return -std::numeric_limits::quiet_NaN(); - } - else - { - return std::numeric_limits::quiet_NaN(); - } + // For some reason all the major compilers return a negative NaN even though I can't find anywhere + // in the standard that specifies this. I'm going to follow suit and return a negative NaN for now. + // Overall, this has little effect on checking for NaN. We only really care for conformance with the standard. + return -std::numeric_limits::quiet_NaN(); } // If y is ±0 and x is not NaN, NaN is returned. - if ((y == static_cast(0.0) || static_cast(y) == static_cast(-0.0)) && !ccm::isnan(x)) + if ((y == static_cast(0.0) || static_cast(y) == static_cast(-0.0)) && !ccm::isnan(x)) { - // The standard specifies that we always return the same sign as x. - if (ccm::signbit(x)) - { - return -std::numeric_limits::quiet_NaN(); - } - else - { - // NOTE: even though the standard specifies that we should return +NaN. On some compilers they return -NaN. - // We will return +NaN to be consistent with what is specified in the standard, but be - // aware that this edge case may be different from std::fmod. - return std::numeric_limits::quiet_NaN(); - } + // Same issue as before. All major compilers return a negative NaN. + return -std::numeric_limits::quiet_NaN(); } // If y is ±∞ and x is finite, x is returned. - if ((y == +std::numeric_limits::infinity() || y == -std::numeric_limits::infinity()) && ccm::isfinite(x)) + if ((y == +std::numeric_limits::infinity() || y == -std::numeric_limits::infinity()) && ccm::isfinite(x)) { return x; } @@ -95,25 +83,20 @@ namespace ccm // If either argument is NaN, NaN is returned. if (ccm::isnan(x) || ccm::isnan(y)) { - if (ccm::signbit(x)) // If x is negative then return negative NaN - { - return -std::numeric_limits::quiet_NaN(); - } - else - { - return std::numeric_limits::quiet_NaN(); - } + // Same problem as before but this time all major compilers return a positive NaN. + return std::numeric_limits::quiet_NaN(); } } - return fmod_impl_internal(x, y); + return fmod_impl_calculate(x, y); } - template , bool> = true> - inline constexpr double fmod_impl_switch(Integer x, Integer y) noexcept + template > + inline constexpr TC fmod_impl_type_check(T x, U y) noexcept { - return fmod_impl_switch(static_cast(x), static_cast(y)); + return fmod_impl_check(static_cast(x), static_cast(y)); } + } } @@ -125,12 +108,24 @@ namespace ccm * @param y A floating-point value. * @return The floating-point remainder of the division operation x/y. */ - template - inline constexpr T fmod(T x, T y) + template , int> = 0> + inline constexpr Real fmod(Real x, Real y) { - return impl::fmod_impl_switch(x, y); + return impl::fmod_impl_check(x, y); } + template , int> = 0> + inline constexpr double fmod(Integer x, Integer y) + { + return impl::fmod_impl_type_check(x, y); + } + + template + inline constexpr std::common_type_t fmod(T x, T y) + { + return impl::fmod_impl_type_check(x, y); + } + inline constexpr float fmodf(float x, float y) { return fmod(x, y); @@ -141,12 +136,4 @@ namespace ccm return fmod(x, y); } - - - - - - - - } // namespace ccm diff --git a/include/ccmath/detail/nearest/trunc.hpp b/include/ccmath/detail/nearest/trunc.hpp index 66cacce6..c9ca0cb6 100644 --- a/include/ccmath/detail/nearest/trunc.hpp +++ b/include/ccmath/detail/nearest/trunc.hpp @@ -10,7 +10,7 @@ #include "ccmath/detail/basic/abs.hpp" #include "ccmath/detail/compare/isnan.hpp" -#include "ccmath/internal/helpers/narrow_cast.hpp" +//#include "ccmath/internal/helpers/narrow_cast.hpp" #include #include @@ -45,7 +45,7 @@ namespace ccm } } - return internal::narrow_cast(static_cast(x)); + return static_cast(static_cast(x)); } } // namespace impl } // namespace @@ -57,8 +57,8 @@ namespace ccm * @param x The value to truncate. * @return Returns a truncated value. */ - template - inline constexpr T trunc(T x) noexcept + template , int> = 0> + inline constexpr Real trunc(Real x) noexcept { return impl::trunc_impl(x); } @@ -72,7 +72,7 @@ namespace ccm template ::value, int> = 0> inline constexpr double trunc(Integer x) noexcept { - return internal::narrow_cast(x); + return static_cast(x); } /** diff --git a/include/ccmath/internal/helpers/promote.hpp b/include/ccmath/internal/helpers/promote.hpp new file mode 100644 index 00000000..9a402e23 --- /dev/null +++ b/include/ccmath/internal/helpers/promote.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024-Present Ian Pike + * Copyright (c) 2024-Present ccmath contributors + * + * This library is provided under the MIT License. + * See LICENSE for more information. + */ + +#pragma once + +#include + +namespace ccm::helper +{ + template > + struct promote + { + using type = double; + }; + + template + struct promote + { }; + + template<> + struct promote + { + using type = long double; + }; + + template<> + struct promote + { + using type = double; + }; + + template<> + struct promote + { + using type = float; + }; + + template + using promote_t = decltype((typename promote::type(0) + ...)); // Assume we have fold expression + + // Deducing the promoted type is done by promoted_t, + // then promote is used to provide the nested type member. + template + using promote_2 = promote>; + + template + using promote_3 = promote>; + + template + using promote_4 = promote>; + + template + using promote_2_t = typename promote_2::type; + + template + using promote_3_t = typename promote_3::type; + + template + using promote_4_t = typename promote_4::type; + + + +} diff --git a/test/basic/fmod_test.cpp b/test/basic/fmod_test.cpp index 4271b755..7d3c0049 100644 --- a/test/basic/fmod_test.cpp +++ b/test/basic/fmod_test.cpp @@ -11,6 +11,12 @@ #include #include #include +#include + +namespace tes +{ + +} TEST(CcmathBasicTests, Fmod) @@ -18,12 +24,52 @@ TEST(CcmathBasicTests, Fmod) // Test fmod with floating point numbers - EXPECT_FLOAT_EQ(ccm::fmod(10.0f, 3.0f), std::fmod(10.0f, 3.0f)); // NOLINT + EXPECT_FLOAT_EQ(ccm::fmod(10.0f, 3.0f), std::fmod(10.0f, 3.0f)); EXPECT_FLOAT_EQ(ccm::fmod(10.0f, -3.0f), std::fmod(10.0f, -3.0f)); EXPECT_FLOAT_EQ(ccm::fmod(-10.0f, 3.0f), std::fmod(-10.0f, 3.0f)); EXPECT_FLOAT_EQ(ccm::fmod(-10.0f, -3.0f), std::fmod(-10.0f, -3.0f)); - //EXPECT_FLOAT_EQ(ccm::fmod(10.0f, 0.0f), std::fmod(10.0f, 0.0f)); // Not testable due to implementation defined behavior - EXPECT_FLOAT_EQ(ccm::fmod(0.0f, 3.0f), std::fmod(0.0f, 3.0f)); - EXPECT_FLOAT_EQ(ccm::fmod(0.0f, 0.0f), std::fmod(0.0f, 0.0f)); + EXPECT_FLOAT_EQ(ccm::fmod(0.0f, 3.0f), std::fmod(0.0f, 3.0f)); + + // Test fmod with integer numbers + EXPECT_FLOAT_EQ(ccm::fmod(10, 3), std::fmod(10, 3)); + + + /// Test Edge Cases + + // Test for edge case where if x is ±0 and y is not zero, ±0 is returned. + EXPECT_FLOAT_EQ(ccm::fmod(0.0f, 1.0f), std::fmod(0.0f, 1.0f)); + EXPECT_FLOAT_EQ(ccm::fmod(-0.0f, 1.0f), std::fmod(-0.0f, 1.0f)); + + // Test for edge case where if x is ±∞ and y is not NaN, NaN is returned. + auto testForNanCcmIfXIsInfAndYIsNotNan = std::isnan(ccm::fmod(10.0f, 0.0f)); + auto testForNanStdIfXIsInfAndYIsNotNan = std::isnan(std::fmod(10.0f, 0.0f)); + bool isCcmNanSameAsStdNanIfXIsInfAndYIsNotNan = testForNanCcmIfXIsInfAndYIsNotNan == testForNanStdIfXIsInfAndYIsNotNan; + EXPECT_TRUE(isCcmNanSameAsStdNanIfXIsInfAndYIsNotNan); + + // Test for edge case where if y is ±0 and x is not NaN, NaN is returned. + auto testForNanCcmIfYIsZeroAndXIsNotNan = std::isnan(ccm::fmod(10.0f, 0.0f)); + auto testForNanStdIfYIsZeroAndXIsNotNan = std::isnan(std::fmod(10.0f, 0.0f)); + bool isCcmNanSameAsStdNanIfYIsZeroAndXIsNotNan = testForNanCcmIfYIsZeroAndXIsNotNan == testForNanStdIfYIsZeroAndXIsNotNan; + EXPECT_TRUE(isCcmNanSameAsStdNanIfYIsZeroAndXIsNotNan); + + // Test for edge case where if y is ±∞ and x is finite, x is returned. + EXPECT_FLOAT_EQ(ccm::fmod(10.0f, std::numeric_limits::infinity()), std::fmod(10.0f, std::numeric_limits::infinity())); + EXPECT_FLOAT_EQ(ccm::fmod(10.0f, -std::numeric_limits::infinity()), std::fmod(10.0f, -std::numeric_limits::infinity())); + + // Test for edge case where if either argument is NaN, NaN is returned. + auto testForNanCcmIfEitherArgumentIsNan = std::isnan(ccm::fmod(std::numeric_limits::quiet_NaN(), 10.0f)); + auto testForNanStdIfEitherArgumentIsNan = std::isnan(std::fmod(std::numeric_limits::quiet_NaN(), 10.0f)); + bool isCcmNanSameAsStdNanIfEitherArgumentIsNan = testForNanCcmIfEitherArgumentIsNan == testForNanStdIfEitherArgumentIsNan; + EXPECT_TRUE(isCcmNanSameAsStdNanIfEitherArgumentIsNan); + + testForNanCcmIfEitherArgumentIsNan = std::isnan(ccm::fmod(10.0f, std::numeric_limits::quiet_NaN())); + testForNanStdIfEitherArgumentIsNan = std::isnan(std::fmod(10.0f, std::numeric_limits::quiet_NaN())); + isCcmNanSameAsStdNanIfEitherArgumentIsNan = testForNanCcmIfEitherArgumentIsNan == testForNanStdIfEitherArgumentIsNan; + EXPECT_TRUE(isCcmNanSameAsStdNanIfEitherArgumentIsNan); + + + + + }