Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions include/boost/decimal/detail/cmath/next.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,66 @@
#include <boost/decimal/detail/type_traits.hpp>
#include <boost/decimal/detail/concepts.hpp>
#include <boost/decimal/detail/config.hpp>
#include <boost/decimal/detail/attributes.hpp>
#include <boost/decimal/detail/cmath/modf.hpp>
#include <boost/decimal/detail/cmath/abs.hpp>
#include <boost/decimal/detail/cmath/round.hpp>
#include <boost/decimal/detail/cmath/ilogb.hpp>
#include <boost/decimal/detail/cmath/fpclassify.hpp>
#include <boost/decimal/detail/cmath/frexp10.hpp>

#ifndef BOOST_DECIMAL_BUILD_MODULE
#include <type_traits>
#include <limits>
#include <cstdint>
#endif

namespace boost {
namespace decimal {

namespace detail {

template <BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T1>
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<T1>::denorm_min() :
-std::numeric_limits<T1>::denorm_min()};
return min_val;
}
if (abs_val > zero && abs_val < std::numeric_limits<T1>::epsilon())
{
const auto min_val {direction ? val + std::numeric_limits<T1>::min() :
val - std::numeric_limits<T1>::min()};
return min_val;
}

const auto val_eps {direction ? val + std::numeric_limits<T1>::epsilon() :
val - std::numeric_limits<T1>::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 <BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T1,
BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T2>
Expand All @@ -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<T1>::epsilon();
return detail::nextafter_impl(val, val < direction);
}

return val - std::numeric_limits<T1>::epsilon();
}

BOOST_DECIMAL_EXPORT template <typename T>
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down
93 changes: 93 additions & 0 deletions test/github_issue_798.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/decimal.hpp>
#include <boost/core/lightweight_test.hpp>
#include <boost/decimal/detail/cmath/next.hpp>
#include <iomanip>
#include <limits>

template <typename T>
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<T>::min());
}

template <typename T>
void test_eps()
{
constexpr T eps {std::numeric_limits<T>::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<T>::epsilon());
}

template <typename T>
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<T>::epsilon());
}

template <typename T>
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<decimal32>();
test_zero<decimal32_fast>();
test_zero<decimal64>();
test_zero<decimal64_fast>();
test_zero<decimal128>();
test_zero<decimal128_fast>();

test_eps<decimal32>();
test_eps<decimal32_fast>();
test_eps<decimal64>();
test_eps<decimal64_fast>();
test_eps<decimal128>();
test_eps<decimal128_fast>();

test_one<decimal32>();
test_one<decimal32_fast>();
test_one<decimal64>();
test_one<decimal64_fast>();
test_one<decimal128>();
test_one<decimal128_fast>();

test_onek<decimal32>();
test_onek<decimal32_fast>();
test_onek<decimal64>();
test_onek<decimal64_fast>();
test_onek<decimal128>();
test_onek<decimal128_fast>();

return boost::report_errors();
}
16 changes: 8 additions & 8 deletions test/test_cmath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
Expand All @@ -1147,8 +1147,8 @@ void test_nextafter()
BOOST_TEST(isnan(nextafter(Dec(1), std::numeric_limits<Dec>::quiet_NaN() * Dec(dist(rng)))));
BOOST_TEST(!isinf(nextafter(Dec(1), std::numeric_limits<Dec>::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<Dec>::epsilon());
BOOST_TEST_EQ(nextafter(Dec(0), Dec(-1)), -std::numeric_limits<Dec>::epsilon());
BOOST_TEST_EQ(nextafter(Dec(0), Dec(1)), std::numeric_limits<Dec>::denorm_min());
BOOST_TEST_EQ(nextafter(Dec(0), Dec(-1)), -std::numeric_limits<Dec>::denorm_min());
}

template <typename Dec>
Expand All @@ -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
Expand All @@ -1180,16 +1180,16 @@ 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
}
}

BOOST_TEST(isinf(nexttoward(std::numeric_limits<Dec>::infinity() * Dec(dist(rng)), 1)));
BOOST_TEST(isnan(nexttoward(std::numeric_limits<Dec>::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<Dec>::epsilon());
BOOST_TEST_EQ(nexttoward(Dec(0), -1), -std::numeric_limits<Dec>::epsilon());
BOOST_TEST_EQ(nexttoward(Dec(0), 1), std::numeric_limits<Dec>::denorm_min());
BOOST_TEST_EQ(nexttoward(Dec(0), -1), -std::numeric_limits<Dec>::denorm_min());
}

template <typename T>
Expand Down