Skip to content

Commit 037cdbf

Browse files
authored
Merge pull request #850 from cppalliance/nextafter
Fix `nextafter` and `nexttowards`
2 parents cd1e650 + f8a3b3f commit 037cdbf

File tree

4 files changed

+151
-14
lines changed

4 files changed

+151
-14
lines changed

include/boost/decimal/detail/cmath/next.hpp

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,66 @@
1212
#include <boost/decimal/detail/type_traits.hpp>
1313
#include <boost/decimal/detail/concepts.hpp>
1414
#include <boost/decimal/detail/config.hpp>
15+
#include <boost/decimal/detail/attributes.hpp>
1516
#include <boost/decimal/detail/cmath/modf.hpp>
1617
#include <boost/decimal/detail/cmath/abs.hpp>
1718
#include <boost/decimal/detail/cmath/round.hpp>
1819
#include <boost/decimal/detail/cmath/ilogb.hpp>
1920
#include <boost/decimal/detail/cmath/fpclassify.hpp>
21+
#include <boost/decimal/detail/cmath/frexp10.hpp>
2022

2123
#ifndef BOOST_DECIMAL_BUILD_MODULE
22-
#include <type_traits>
2324
#include <limits>
24-
#include <cstdint>
2525
#endif
2626

2727
namespace boost {
2828
namespace decimal {
2929

30+
namespace detail {
31+
32+
template <BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T1>
33+
constexpr auto nextafter_impl(T1 val, bool direction) noexcept -> T1
34+
{
35+
constexpr T1 zero {0};
36+
37+
// Val < direction = +
38+
// Val > direction = -
39+
const auto abs_val {abs(val)};
40+
41+
if (val == zero)
42+
{
43+
const auto min_val {direction ? std::numeric_limits<T1>::denorm_min() :
44+
-std::numeric_limits<T1>::denorm_min()};
45+
return min_val;
46+
}
47+
if (abs_val > zero && abs_val < std::numeric_limits<T1>::epsilon())
48+
{
49+
const auto min_val {direction ? val + std::numeric_limits<T1>::min() :
50+
val - std::numeric_limits<T1>::min()};
51+
return min_val;
52+
}
53+
54+
const auto val_eps {direction ? val + std::numeric_limits<T1>::epsilon() :
55+
val - std::numeric_limits<T1>::epsilon()};
56+
57+
// If adding epsilon does nothing then we need to manipulate the representation
58+
if (val == val_eps)
59+
{
60+
int exp {} ;
61+
auto significand {frexp10(val, &exp)};
62+
63+
direction ? significand++ : significand--;
64+
65+
return T1{significand, exp};
66+
}
67+
else
68+
{
69+
return val_eps;
70+
}
71+
}
72+
73+
} // namespace detail
74+
3075
BOOST_DECIMAL_EXPORT
3176
template <BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T1,
3277
BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T2>
@@ -48,12 +93,10 @@ constexpr auto nextafter(T1 val, T2 direction) noexcept
4893
return direction;
4994
}
5095
#endif
51-
else if (val < direction)
96+
else
5297
{
53-
return val + std::numeric_limits<T1>::epsilon();
98+
return detail::nextafter_impl(val, val < direction);
5499
}
55-
56-
return val - std::numeric_limits<T1>::epsilon();
57100
}
58101

59102
BOOST_DECIMAL_EXPORT template <typename T>

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ run crash_report_1.cpp ;
4949
run github_issue_426.cpp ;
5050
run github_issue_448.cpp ;
5151
run-fail github_issue_519.cpp ;
52+
run github_issue_798.cpp ;
5253
run github_issue_799.cpp ;
5354
run github_issue_802.cpp ;
5455
run github_issue_805.cpp ;

test/github_issue_798.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// (C) Copyright Matt Borland 2025.
2+
// Use, modification and distribution are subject to the
3+
// Boost Software License, Version 1.0. (See accompanying file
4+
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5+
6+
#include <boost/decimal.hpp>
7+
#include <boost/core/lightweight_test.hpp>
8+
#include <boost/decimal/detail/cmath/next.hpp>
9+
#include <iomanip>
10+
#include <limits>
11+
12+
template <typename T>
13+
void test_zero()
14+
{
15+
constexpr T zero {0};
16+
constexpr T one {1};
17+
18+
const auto next_after_zero {boost::decimal::nextafter(zero, one)};
19+
20+
BOOST_TEST_GT(next_after_zero, zero);
21+
BOOST_TEST_LT(next_after_zero, zero + 2*std::numeric_limits<T>::min());
22+
}
23+
24+
template <typename T>
25+
void test_eps()
26+
{
27+
constexpr T eps {std::numeric_limits<T>::epsilon()};
28+
constexpr T one {1};
29+
30+
const auto next_after_eps {boost::decimal::nextafter(eps, one)};
31+
32+
BOOST_TEST_GT(next_after_eps, eps);
33+
BOOST_TEST_LT(next_after_eps, eps + 2*std::numeric_limits<T>::epsilon());
34+
}
35+
36+
template <typename T>
37+
void test_one()
38+
{
39+
constexpr T one {1};
40+
constexpr T two {2};
41+
42+
const auto next_after_one {boost::decimal::nextafter(one, two)};
43+
44+
BOOST_TEST_GT(next_after_one, one);
45+
BOOST_TEST_LT(next_after_one, one + 2*std::numeric_limits<T>::epsilon());
46+
}
47+
48+
template <typename T>
49+
void test_onek()
50+
{
51+
constexpr T onek {1024};
52+
constexpr T twok {2048};
53+
54+
const auto next_after_onek {boost::decimal::nextafter(onek, twok)};
55+
56+
BOOST_TEST_GT(next_after_onek, onek);
57+
BOOST_TEST_LT(next_after_onek, twok);
58+
}
59+
60+
int main()
61+
{
62+
using namespace boost::decimal;
63+
64+
test_zero<decimal32>();
65+
test_zero<decimal32_fast>();
66+
test_zero<decimal64>();
67+
test_zero<decimal64_fast>();
68+
test_zero<decimal128>();
69+
test_zero<decimal128_fast>();
70+
71+
test_eps<decimal32>();
72+
test_eps<decimal32_fast>();
73+
test_eps<decimal64>();
74+
test_eps<decimal64_fast>();
75+
test_eps<decimal128>();
76+
test_eps<decimal128_fast>();
77+
78+
test_one<decimal32>();
79+
test_one<decimal32_fast>();
80+
test_one<decimal64>();
81+
test_one<decimal64_fast>();
82+
test_one<decimal128>();
83+
test_one<decimal128_fast>();
84+
85+
test_onek<decimal32>();
86+
test_onek<decimal32_fast>();
87+
test_onek<decimal64>();
88+
test_onek<decimal64_fast>();
89+
test_onek<decimal128>();
90+
test_onek<decimal128_fast>();
91+
92+
return boost::report_errors();
93+
}

test/test_cmath.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ void test_nextafter()
11281128
{
11291129
BOOST_TEST_EQ(ret_val, ret_dec); // LCOV_EXCL_LINE
11301130
}
1131-
else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 10))
1131+
else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 25))
11321132
{
11331133
// LCOV_EXCL_START
11341134
std::cerr << "Val 1: " << val1
@@ -1137,7 +1137,7 @@ void test_nextafter()
11371137
<< "\nDec 2: " << d2
11381138
<< "\nRet val: " << ret_val
11391139
<< "\nRet dec: " << ret_dec
1140-
<< "\nDist: " << boost::math::float_distance(ret_val, ret_dec);
1140+
<< "\nDist: " << boost::math::float_distance(ret_val, ret_dec) << std::endl;
11411141
// LCOV_EXCL_STOP
11421142
}
11431143
}
@@ -1147,8 +1147,8 @@ void test_nextafter()
11471147
BOOST_TEST(isnan(nextafter(Dec(1), std::numeric_limits<Dec>::quiet_NaN() * Dec(dist(rng)))));
11481148
BOOST_TEST(!isinf(nextafter(Dec(1), std::numeric_limits<Dec>::infinity() * Dec(dist(rng)))));
11491149
BOOST_TEST_EQ(nextafter(Dec(1), Dec(1)), Dec(1));
1150-
BOOST_TEST_EQ(nextafter(Dec(0), Dec(1)), std::numeric_limits<Dec>::epsilon());
1151-
BOOST_TEST_EQ(nextafter(Dec(0), Dec(-1)), -std::numeric_limits<Dec>::epsilon());
1150+
BOOST_TEST_EQ(nextafter(Dec(0), Dec(1)), std::numeric_limits<Dec>::denorm_min());
1151+
BOOST_TEST_EQ(nextafter(Dec(0), Dec(-1)), -std::numeric_limits<Dec>::denorm_min());
11521152
}
11531153

11541154
template <typename Dec>
@@ -1171,7 +1171,7 @@ void test_nexttoward()
11711171
{
11721172
BOOST_TEST_EQ(ret_val, ret_dec); // LCOV_EXCL_LINE
11731173
}
1174-
else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 10))
1174+
else if (!BOOST_TEST(boost::math::float_distance(ret_val, ret_dec) < 25))
11751175
{
11761176
// LCOV_EXCL_START
11771177
std::cerr << "Val 1: " << val1
@@ -1180,16 +1180,16 @@ void test_nexttoward()
11801180
<< "\nDec 2: " << d2
11811181
<< "\nRet val: " << ret_val
11821182
<< "\nRet dec: " << ret_dec
1183-
<< "\nDist: " << boost::math::float_distance(ret_val, ret_dec);
1183+
<< "\nDist: " << boost::math::float_distance(ret_val, ret_dec) << std::endl;
11841184
// LCOV_EXCL_STOP
11851185
}
11861186
}
11871187

11881188
BOOST_TEST(isinf(nexttoward(std::numeric_limits<Dec>::infinity() * Dec(dist(rng)), 1)));
11891189
BOOST_TEST(isnan(nexttoward(std::numeric_limits<Dec>::quiet_NaN() * Dec(dist(rng)), 1)));
11901190
BOOST_TEST_EQ(nexttoward(Dec(1), 1), Dec(1));
1191-
BOOST_TEST_EQ(nexttoward(Dec(0), 1), std::numeric_limits<Dec>::epsilon());
1192-
BOOST_TEST_EQ(nexttoward(Dec(0), -1), -std::numeric_limits<Dec>::epsilon());
1191+
BOOST_TEST_EQ(nexttoward(Dec(0), 1), std::numeric_limits<Dec>::denorm_min());
1192+
BOOST_TEST_EQ(nexttoward(Dec(0), -1), -std::numeric_limits<Dec>::denorm_min());
11931193
}
11941194

11951195
template <typename T>

0 commit comments

Comments
 (0)