Skip to content

Commit 35fb20b

Browse files
authored
Merge pull request #1133 from cppalliance/1105
Directly manipulate value to calculate next
2 parents afd1d64 + e1975ab commit 35fb20b

File tree

3 files changed

+93
-20
lines changed

3 files changed

+93
-20
lines changed

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

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,54 @@ constexpr auto nextafter_impl(const DecimalType val, const bool direction) noexc
3434
{
3535
constexpr DecimalType zero {0};
3636

37-
// Val < direction = +
38-
// Val > direction = -
39-
const auto abs_val {abs(val)};
37+
const bool is_neg {val < 0};
4038

4139
if (val == zero)
4240
{
4341
const auto min_val {direction ? std::numeric_limits<DecimalType>::denorm_min() :
4442
-std::numeric_limits<DecimalType>::denorm_min()};
4543
return min_val;
4644
}
47-
else if (abs_val > zero && abs_val < std::numeric_limits<DecimalType>::epsilon())
48-
{
49-
auto exp {val.biased_exponent()};
50-
auto significand {val.full_significand()};
51-
direction ? ++significand : --significand;
5245

53-
return {significand, exp, val.isneg()};
54-
}
46+
int exp {};
47+
auto sig {frexp10(val, &exp)};
48+
const auto removed_zeros(remove_trailing_zeros(sig));
5549

56-
const auto val_eps {direction ? val + std::numeric_limits<DecimalType>::epsilon() :
57-
val - std::numeric_limits<DecimalType>::epsilon()};
50+
// Our two boundaries
51+
const bool is_pow_10 {removed_zeros.trimmed_number == 1U};
52+
const bool is_max_sig {sig == detail::max_significand_v<DecimalType>};
5853

59-
// If adding epsilon does nothing, then we need to manipulate the representation
60-
if (val == val_eps)
54+
if (!isnormal(val))
6155
{
62-
int exp {} ;
63-
auto significand {frexp10(val, &exp)};
64-
65-
direction ? ++significand : --significand;
56+
// Not to make sure that denorms aren't normalized
57+
sig = removed_zeros.trimmed_number;
58+
exp += static_cast<int>(removed_zeros.number_of_removed_zeros);
59+
}
6660

67-
return DecimalType{significand, exp};
61+
if (direction)
62+
{
63+
// Val < direction = +
64+
++sig;
65+
if (is_max_sig)
66+
{
67+
sig /= 10u;
68+
++exp;
69+
}
6870
}
6971
else
7072
{
71-
return val_eps;
73+
// Val > direction = -
74+
--sig;
75+
if (is_pow_10)
76+
{
77+
// 1000 becomes 999 but needs to be 9999
78+
sig *= 10u;
79+
sig += 9u;
80+
--exp;
81+
}
7282
}
83+
84+
return DecimalType{sig, exp, is_neg};
7385
}
7486

7587
} // namespace detail

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ run github_issue_1057.cpp ;
7070
compile-fail github_issue_1087.cpp ;
7171
run github_issue_1091.cpp ;
7272
run github_issue_1094.cpp ;
73+
run github_issue_1105.cpp ;
7374
run github_issue_1106.cpp ;
7475
run github_issue_1110.cpp ;
7576
run github_issue_1112.cpp ;

test/github_issue_1105.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2025 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
//
5+
// See: https://github.com/cppalliance/decimal/issues/1105
6+
7+
#include <boost/decimal.hpp>
8+
#include <boost/core/lightweight_test.hpp>
9+
#include <random>
10+
11+
static std::mt19937_64 rng(42);
12+
13+
using namespace boost::decimal;
14+
15+
template <typename T>
16+
void test()
17+
{
18+
std::uniform_int_distribution<int> dist(1, 1);
19+
20+
const T one {dist(rng), 0};
21+
const T zero {0, 0};
22+
23+
const T val {dist(rng), -5};
24+
int val_exp {};
25+
const auto val_sig {frexp10(val, &val_exp)};
26+
27+
const auto next {nextafter(val, one)};
28+
int next_exp {};
29+
const auto next_sig {frexp10(next, &next_exp)};
30+
BOOST_TEST_EQ(next_exp, val_exp);
31+
BOOST_TEST_EQ(next_sig, val_sig + 1U);
32+
33+
const auto prev {nextafter(val, zero)};
34+
int prev_exp {};
35+
const auto prev_sig {frexp10(prev, &prev_exp)};
36+
BOOST_TEST_EQ(prev_exp, val_exp - 1);
37+
BOOST_TEST_EQ(prev_sig, detail::max_significand_v<T>);
38+
39+
// Max significand + 1 should be reduced
40+
const auto original_val {nextafter(prev, one)};
41+
int original_exp {};
42+
const auto original_sig {frexp10(original_val, &original_exp)};
43+
BOOST_TEST_EQ(original_exp, val_exp);
44+
BOOST_TEST_EQ(original_sig, val_sig);
45+
46+
const auto zero_next {nextafter(zero, one)};
47+
BOOST_TEST_EQ(zero_next, std::numeric_limits<T>::denorm_min());
48+
49+
const auto two_next_zero {nextafter(zero_next, one)};
50+
BOOST_TEST_EQ(two_next_zero, T(2, detail::etiny_v<T>));
51+
}
52+
53+
int main()
54+
{
55+
test<decimal32_t>();
56+
test<decimal64_t>();
57+
test<decimal128_t>();
58+
59+
return boost::report_errors();
60+
}

0 commit comments

Comments
 (0)