diff --git a/include/boost/decimal/detail/cmath/next.hpp b/include/boost/decimal/detail/cmath/next.hpp index f0febcaf0..4b6247935 100644 --- a/include/boost/decimal/detail/cmath/next.hpp +++ b/include/boost/decimal/detail/cmath/next.hpp @@ -12,21 +12,66 @@ #include #include #include +#include #include #include #include #include #include +#include #ifndef BOOST_DECIMAL_BUILD_MODULE -#include #include -#include #endif namespace boost { namespace decimal { +namespace detail { + +template +constexpr auto nextafter_impl(T1 val, bool direction) noexcept -> T1 +{ + constexpr T1 zero {0}; + + // Val < direction = + + // Val > direction = - + const auto abs_val {abs(val)}; + + if (val == zero) + { + const auto min_val {direction ? std::numeric_limits::denorm_min() : + -std::numeric_limits::denorm_min()}; + return min_val; + } + if (abs_val > zero && abs_val < std::numeric_limits::epsilon()) + { + const auto min_val {direction ? val + std::numeric_limits::min() : + val - std::numeric_limits::min()}; + return min_val; + } + + const auto val_eps {direction ? val + std::numeric_limits::epsilon() : + val - std::numeric_limits::epsilon()}; + + // If adding epsilon does nothing then we need to manipulate the representation + if (val == val_eps) + { + int exp {} ; + auto significand {frexp10(val, &exp)}; + + direction ? significand++ : significand--; + + return T1{significand, exp}; + } + else + { + return val_eps; + } +} + +} // namespace detail + BOOST_DECIMAL_EXPORT template @@ -48,12 +93,10 @@ constexpr auto nextafter(T1 val, T2 direction) noexcept return direction; } #endif - else if (val < direction) + else { - return val + std::numeric_limits::epsilon(); + return detail::nextafter_impl(val, val < direction); } - - return val - std::numeric_limits::epsilon(); } BOOST_DECIMAL_EXPORT template diff --git a/test/Jamfile b/test/Jamfile index bca35baee..12d8d38d9 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -49,6 +49,7 @@ run crash_report_1.cpp ; run github_issue_426.cpp ; run github_issue_448.cpp ; run-fail github_issue_519.cpp ; +run github_issue_798.cpp ; run github_issue_799.cpp ; run github_issue_802.cpp ; run github_issue_805.cpp ; diff --git a/test/github_issue_798.cpp b/test/github_issue_798.cpp new file mode 100644 index 000000000..234265579 --- /dev/null +++ b/test/github_issue_798.cpp @@ -0,0 +1,93 @@ +// (C) Copyright Matt Borland 2025. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include + +template +void test_zero() +{ + constexpr T zero {0}; + constexpr T one {1}; + + const auto next_after_zero {boost::decimal::nextafter(zero, one)}; + + BOOST_TEST_GT(next_after_zero, zero); + BOOST_TEST_LT(next_after_zero, zero + 2*std::numeric_limits::min()); +} + +template +void test_eps() +{ + constexpr T eps {std::numeric_limits::epsilon()}; + constexpr T one {1}; + + const auto next_after_eps {boost::decimal::nextafter(eps, one)}; + + BOOST_TEST_GT(next_after_eps, eps); + BOOST_TEST_LT(next_after_eps, eps + 2*std::numeric_limits::epsilon()); +} + +template +void test_one() +{ + constexpr T one {1}; + constexpr T two {2}; + + const auto next_after_one {boost::decimal::nextafter(one, two)}; + + BOOST_TEST_GT(next_after_one, one); + BOOST_TEST_LT(next_after_one, one + 2*std::numeric_limits::epsilon()); +} + +template +void test_onek() +{ + constexpr T onek {1024}; + constexpr T twok {2048}; + + const auto next_after_onek {boost::decimal::nextafter(onek, twok)}; + + BOOST_TEST_GT(next_after_onek, onek); + BOOST_TEST_LT(next_after_onek, twok); +} + +int main() +{ + using namespace boost::decimal; + + test_zero(); + test_zero(); + test_zero(); + test_zero(); + test_zero(); + test_zero(); + + test_eps(); + test_eps(); + test_eps(); + test_eps(); + test_eps(); + test_eps(); + + test_one(); + test_one(); + test_one(); + test_one(); + test_one(); + test_one(); + + test_onek(); + test_onek(); + test_onek(); + test_onek(); + test_onek(); + test_onek(); + + return boost::report_errors(); +} diff --git a/test/test_cmath.cpp b/test/test_cmath.cpp index d969a1ac4..0b23c82bc 100644 --- a/test/test_cmath.cpp +++ b/test/test_cmath.cpp @@ -1128,7 +1128,7 @@ void test_nextafter() { BOOST_TEST_EQ(ret_val, ret_dec); // LCOV_EXCL_LINE } - else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 10)) + else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 25)) { // LCOV_EXCL_START std::cerr << "Val 1: " << val1 @@ -1137,7 +1137,7 @@ void test_nextafter() << "\nDec 2: " << d2 << "\nRet val: " << ret_val << "\nRet dec: " << ret_dec - << "\nDist: " << boost::math::float_distance(ret_val, ret_dec); + << "\nDist: " << boost::math::float_distance(ret_val, ret_dec) << std::endl; // LCOV_EXCL_STOP } } @@ -1147,8 +1147,8 @@ void test_nextafter() BOOST_TEST(isnan(nextafter(Dec(1), std::numeric_limits::quiet_NaN() * Dec(dist(rng))))); BOOST_TEST(!isinf(nextafter(Dec(1), std::numeric_limits::infinity() * Dec(dist(rng))))); BOOST_TEST_EQ(nextafter(Dec(1), Dec(1)), Dec(1)); - BOOST_TEST_EQ(nextafter(Dec(0), Dec(1)), std::numeric_limits::epsilon()); - BOOST_TEST_EQ(nextafter(Dec(0), Dec(-1)), -std::numeric_limits::epsilon()); + BOOST_TEST_EQ(nextafter(Dec(0), Dec(1)), std::numeric_limits::denorm_min()); + BOOST_TEST_EQ(nextafter(Dec(0), Dec(-1)), -std::numeric_limits::denorm_min()); } template @@ -1171,7 +1171,7 @@ void test_nexttoward() { BOOST_TEST_EQ(ret_val, ret_dec); // LCOV_EXCL_LINE } - else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 10)) + else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 25)) { // LCOV_EXCL_START std::cerr << "Val 1: " << val1 @@ -1180,7 +1180,7 @@ void test_nexttoward() << "\nDec 2: " << d2 << "\nRet val: " << ret_val << "\nRet dec: " << ret_dec - << "\nDist: " << boost::math::float_distance(ret_val, ret_dec); + << "\nDist: " << boost::math::float_distance(ret_val, ret_dec) << std::endl; // LCOV_EXCL_STOP } } @@ -1188,8 +1188,8 @@ void test_nexttoward() BOOST_TEST(isinf(nexttoward(std::numeric_limits::infinity() * Dec(dist(rng)), 1))); BOOST_TEST(isnan(nexttoward(std::numeric_limits::quiet_NaN() * Dec(dist(rng)), 1))); BOOST_TEST_EQ(nexttoward(Dec(1), 1), Dec(1)); - BOOST_TEST_EQ(nexttoward(Dec(0), 1), std::numeric_limits::epsilon()); - BOOST_TEST_EQ(nexttoward(Dec(0), -1), -std::numeric_limits::epsilon()); + BOOST_TEST_EQ(nexttoward(Dec(0), 1), std::numeric_limits::denorm_min()); + BOOST_TEST_EQ(nexttoward(Dec(0), -1), -std::numeric_limits::denorm_min()); } template