Skip to content

Commit 51113cc

Browse files
authored
Merge pull request #1258 from cppalliance/upward
Fix addition and subtraction edges with upward rounding mode
2 parents 204d0eb + 556518d commit 51113cc

File tree

4 files changed

+269
-14
lines changed

4 files changed

+269
-14
lines changed

include/boost/decimal/detail/add_impl.hpp

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <boost/decimal/detail/components.hpp>
1212
#include <boost/decimal/detail/power_tables.hpp>
1313
#include <boost/decimal/detail/promotion.hpp>
14+
#include <boost/decimal/detail/is_power_of_10.hpp>
1415
#include "int128.hpp"
1516

1617
#ifndef BOOST_DECIMAL_BUILD_MODULE
@@ -76,8 +77,7 @@ constexpr auto add_impl(const T& lhs, const T& rhs) noexcept -> ReturnType
7677
{
7778
if (big_rhs != 0U && (lhs.isneg() != rhs.isneg()))
7879
{
79-
const auto removed_zeros {detail::remove_trailing_zeros(big_lhs)};
80-
if (removed_zeros.trimmed_number == 1U)
80+
if (is_power_of_10(big_lhs))
8181
{
8282
--big_lhs;
8383
big_lhs *= 10U;
@@ -96,8 +96,7 @@ constexpr auto add_impl(const T& lhs, const T& rhs) noexcept -> ReturnType
9696
{
9797
if (big_lhs != 0U && (lhs.isneg() != rhs.isneg()))
9898
{
99-
const auto removed_zeros {detail::remove_trailing_zeros(big_rhs)};
100-
if (removed_zeros.trimmed_number == 1U)
99+
if (is_power_of_10(big_rhs))
101100
{
102101
--big_rhs;
103102
big_rhs *= 10U;
@@ -117,9 +116,53 @@ constexpr auto add_impl(const T& lhs, const T& rhs) noexcept -> ReturnType
117116
{
118117
// rounding mode == fe_dec_upward
119118
// Unconditionally round up. Could be 5e+95 + 4e-100 -> 5.000001e+95
120-
return big_lhs != 0U && (lhs_exp > rhs_exp) ?
121-
ReturnType{lhs.full_significand() + 1U, lhs.biased_exponent(), lhs.isneg()} :
122-
ReturnType{rhs.full_significand() + 1U, rhs.biased_exponent(), rhs.isneg()};
119+
const bool use_lhs {big_lhs != 0U && (lhs_exp > rhs_exp)};
120+
121+
if (use_lhs)
122+
{
123+
if (big_rhs != 0U)
124+
{
125+
if (lhs.isneg() != rhs.isneg())
126+
{
127+
if (is_power_of_10(big_lhs))
128+
{
129+
--big_lhs;
130+
big_lhs *= 10U;
131+
big_lhs += 9U;
132+
--lhs_exp;
133+
}
134+
else
135+
{
136+
--big_lhs;
137+
}
138+
}
139+
else
140+
{
141+
++big_lhs;
142+
}
143+
}
144+
145+
return ReturnType{big_lhs, lhs_exp, lhs.isneg()} ;
146+
}
147+
else
148+
{
149+
if (big_lhs != 0U)
150+
{
151+
if (rhs.isneg() != lhs.isneg())
152+
{
153+
--big_rhs;
154+
big_rhs *= 10U;
155+
big_rhs += 9U;
156+
--rhs_exp;
157+
}
158+
else
159+
{
160+
++big_rhs;
161+
}
162+
}
163+
164+
return ReturnType{big_rhs, rhs_exp, rhs.isneg()};
165+
}
123166
}
124167
}
125168

@@ -194,8 +237,7 @@ constexpr auto d128_add_impl(T lhs_sig, U lhs_exp, bool lhs_sign,
194237
{
195238
if (rhs_sig != 0U && (lhs_sign != rhs_sign))
196239
{
197-
const auto removed_zeros {detail::remove_trailing_zeros(lhs_sig)};
198-
if (removed_zeros.trimmed_number == 1U)
240+
if (is_power_of_10(lhs_sig))
199241
{
200242
--lhs_sig;
201243
lhs_sig *= 10U;
@@ -214,8 +256,7 @@ constexpr auto d128_add_impl(T lhs_sig, U lhs_exp, bool lhs_sign,
214256
{
215257
if (lhs_sig != 0U && (lhs_sign != rhs_sign))
216258
{
217-
const auto removed_zeros {detail::remove_trailing_zeros(rhs_sig)};
218-
if (removed_zeros.trimmed_number == 1U)
259+
if (is_power_of_10(rhs_sig))
219260
{
220261
--rhs_sig;
221262
rhs_sig *= 10U;
@@ -235,9 +276,53 @@ constexpr auto d128_add_impl(T lhs_sig, U lhs_exp, bool lhs_sign,
235276
{
236277
// rounding mode == fe_dec_upward
237278
// Unconditionally round up. Could be 5e+95 + 4e-100 -> 5.000001e+95
238-
return lhs_sig != 0U && (lhs_exp > rhs_exp) ?
239-
ReturnType{lhs_sig + 1U, lhs_exp, lhs_sign} :
240-
ReturnType{rhs_sig + 1U, rhs_exp, rhs_sign};
279+
const bool use_lhs {lhs_sig != 0U && (lhs_exp > rhs_exp)};
280+
281+
if (use_lhs)
282+
{
283+
if (rhs_sig != 0U)
284+
{
285+
if (lhs_sign != rhs_sign)
286+
{
287+
if (is_power_of_10(lhs_sig))
288+
{
289+
--lhs_sig;
290+
lhs_sig *= 10U;
291+
lhs_sig += 9U;
292+
--lhs_exp;
293+
}
294+
else
295+
{
296+
--lhs_sig;
297+
}
298+
}
299+
else
300+
{
301+
++lhs_sig;
302+
}
303+
}
304+
305+
return ReturnType{lhs_sig, lhs_exp, lhs_sign} ;
306+
}
307+
else
308+
{
309+
if (lhs_sig != 0U)
310+
{
311+
if (rhs_sign != lhs_sign)
312+
{
313+
--rhs_sig;
314+
rhs_sig *= 10U;
315+
rhs_sig += 9U;
316+
--rhs_exp;
317+
}
318+
else
319+
{
320+
++rhs_sig;
321+
}
322+
}
323+
324+
return ReturnType{rhs_sig, rhs_exp, rhs_sign};
325+
}
241326
}
242327
}
243328

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
#ifndef BOOST_DECIMAL_DETAIL_IS_POWER_OF_10_HPP
6+
#define BOOST_DECIMAL_DETAIL_IS_POWER_OF_10_HPP
7+
8+
#include <boost/decimal/detail/config.hpp>
9+
#include <boost/decimal/detail/remove_trailing_zeros.hpp>
10+
#include <boost/decimal/detail/concepts.hpp>
11+
12+
namespace boost {
13+
namespace decimal {
14+
namespace detail {
15+
16+
template <typename T>
17+
constexpr auto is_power_of_10(const T x) noexcept
18+
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_unsigned_v, T, bool)
19+
{
20+
const auto removed_zeros {detail::remove_trailing_zeros(x)};
21+
return removed_zeros.trimmed_number == 1U;
22+
}
23+
24+
} // namespace detail
25+
} // namespace decimal
26+
} // namespace boost
27+
28+
#endif // BOOST_DECIMAL_DETAIL_IS_POWER_OF_10_HPP

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ run test_tgamma.cpp ;
188188
run test_to_chars.cpp ;
189189
run test_to_string.cpp ;
190190
run test_total_ordering.cpp ;
191+
run test_upward_rounding.cpp ;
191192
run test_zeta.cpp ;
192193

193194
run limits_link_1.cpp limits_link_2.cpp limits_link_3.cpp ;

test/test_upward_rounding.cpp

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
// Use compile-time rounding mode change so the test run on all platforms
6+
7+
#define BOOST_DECIMAL_FE_DEC_UPWARD
8+
9+
#include <boost/decimal.hpp>
10+
#include <boost/core/lightweight_test.hpp>
11+
#include <functional>
12+
#include <iostream>
13+
#include <iomanip>
14+
15+
template <typename T, typename Func>
16+
void test(const char* lhs_str, const char* rhs_str, const char* result_str, Func f)
17+
{
18+
const T lhs {lhs_str};
19+
const T rhs {rhs_str};
20+
const T result {result_str};
21+
22+
const T func_result {f(lhs, rhs)};
23+
24+
BOOST_TEST_EQ(func_result, result);
25+
}
26+
27+
template <typename T>
28+
void test_add(const char* lhs, const char* rhs, const char* result)
29+
{
30+
std::cerr << std::setprecision(std::numeric_limits<T>::max_digits10);
31+
test<T>(lhs, rhs, result, std::plus<>());
32+
test<T>(rhs, lhs, result, std::plus<>());
33+
}
34+
35+
template <typename T>
36+
void test_sub(const char* lhs, const char* rhs, const char* result)
37+
{
38+
std::cerr << std::setprecision(std::numeric_limits<T>::max_digits10);
39+
test<T>(lhs, rhs, result, std::minus<>());
40+
}
41+
42+
int main()
43+
{
44+
using namespace boost::decimal;
45+
46+
test_add<decimal64_t>("-1e+2", "+1e-383", "-99.99999999999999");
47+
test_add<decimal64_t>("-1e+1", "+1e-383", "-9.999999999999999");
48+
test_add<decimal64_t>("-1e+0", "+1e-383", "-0.9999999999999999");
49+
50+
test_add<decimal64_t>("+1e+2", "+1e-383", "100.0000000000001");
51+
test_add<decimal64_t>("+1e+1", "+1e-383", "10.00000000000001");
52+
test_add<decimal64_t>("+1e+0", "+1e-383", "1.000000000000001");
53+
test_add<decimal64_t>("+1e-1", "+1e-383", "0.1000000000000001");
54+
55+
test_add<decimal64_t>("+1e+2", "0", "100.0000000000000");
56+
test_add<decimal64_t>("+1e+1", "0", "10.00000000000000");
57+
test_add<decimal64_t>("+1e+0", "0", "1.000000000000000");
58+
test_add<decimal64_t>("+1e-1", "0", "0.1000000000000000");
59+
60+
test_add<decimal64_t>("-1e+2", "0", "-100.0000000000000");
61+
test_add<decimal64_t>("-1e+1", "0", "-10.00000000000000");
62+
test_add<decimal64_t>("-1e+0", "0", "-1.000000000000000");
63+
test_add<decimal64_t>("-1e-1", "0", "-0.1000000000000000");
64+
65+
test_sub<decimal64_t>("+1e+2", "0", "100.0000000000000");
66+
test_sub<decimal64_t>("+1e+1", "0", "10.00000000000000");
67+
test_sub<decimal64_t>("+1e+0", "0", "1.000000000000000");
68+
test_sub<decimal64_t>("+1e-1", "0", "0.1000000000000000");
69+
70+
test_sub<decimal64_t>("-1e+2", "0", "-100.0000000000000");
71+
test_sub<decimal64_t>("-1e+1", "0", "-10.00000000000000");
72+
test_sub<decimal64_t>("-1e+0", "0", "-1.000000000000000");
73+
test_sub<decimal64_t>("-1e-1", "0", "-0.1000000000000000");
74+
75+
test_add<decimal_fast64_t>("-1e+2", "+1e-383", "-99.99999999999999");
76+
test_add<decimal_fast64_t>("-1e+1", "+1e-383", "-9.999999999999999");
77+
test_add<decimal_fast64_t>("-1e+0", "+1e-383", "-0.9999999999999999");
78+
79+
test_add<decimal_fast64_t>("+1e+2", "+1e-383", "100.0000000000001");
80+
test_add<decimal_fast64_t>("+1e+1", "+1e-383", "10.00000000000001");
81+
test_add<decimal_fast64_t>("+1e+0", "+1e-383", "1.000000000000001");
82+
test_add<decimal_fast64_t>("+1e-1", "+1e-383", "0.1000000000000001");
83+
84+
test_add<decimal_fast64_t>("+1e+2", "0", "100.0000000000000");
85+
test_add<decimal_fast64_t>("+1e+1", "0", "10.00000000000000");
86+
test_add<decimal_fast64_t>("+1e+0", "0", "1.000000000000000");
87+
test_add<decimal_fast64_t>("+1e-1", "0", "0.1000000000000000");
88+
89+
test_add<decimal_fast64_t>("-1e+2", "0", "-100.0000000000000");
90+
test_add<decimal_fast64_t>("-1e+1", "0", "-10.00000000000000");
91+
test_add<decimal_fast64_t>("-1e+0", "0", "-1.000000000000000");
92+
test_add<decimal_fast64_t>("-1e-1", "0", "-0.1000000000000000");
93+
94+
test_sub<decimal_fast64_t>("+1e+2", "0", "100.0000000000000");
95+
test_sub<decimal_fast64_t>("+1e+1", "0", "10.00000000000000");
96+
test_sub<decimal_fast64_t>("+1e+0", "0", "1.000000000000000");
97+
test_sub<decimal_fast64_t>("+1e-1", "0", "0.1000000000000000");
98+
99+
test_sub<decimal_fast64_t>("-1e+2", "0", "-100.0000000000000");
100+
test_sub<decimal_fast64_t>("-1e+1", "0", "-10.00000000000000");
101+
test_sub<decimal_fast64_t>("-1e+0", "0", "-1.000000000000000");
102+
test_sub<decimal_fast64_t>("-1e-1", "0", "-0.1000000000000000");
103+
104+
test_add<decimal32_t>("-1e+2", "+1e-20", "-99.99999");
105+
test_add<decimal32_t>("-1e+1", "+1e-20", "-9.999999");
106+
test_add<decimal32_t>("-1e+0", "+1e-20", "-0.9999999");
107+
108+
test_add<decimal32_t>("+1e+2", "+1e-20", "100.0001");
109+
test_add<decimal32_t>("+1e+1", "+1e-20", "10.00001");
110+
test_add<decimal32_t>("+1e+0", "+1e-20", "1.000001");
111+
test_add<decimal32_t>("+1e-1", "+1e-20", "0.10000001");
112+
113+
test_add<decimal_fast32_t>("-1e+2", "+1e-20", "-99.99999");
114+
test_add<decimal_fast32_t>("-1e+1", "+1e-20", "-9.999999");
115+
test_add<decimal_fast32_t>("-1e+0", "+1e-20", "-0.9999999");
116+
117+
test_add<decimal_fast32_t>("+1e+2", "+1e-20", "100.0001");
118+
test_add<decimal_fast32_t>("+1e+1", "+1e-20", "10.00001");
119+
test_add<decimal_fast32_t>("+1e+0", "+1e-20", "1.000001");
120+
test_add<decimal_fast32_t>("+1e-1", "+1e-20", "0.10000001");
121+
122+
test_add<decimal128_t>("-1e+2", "+1e-383", "-99.99999999999999999999999999999999");
123+
test_add<decimal128_t>("-1e+1", "+1e-383", "-9.999999999999999999999999999999999");
124+
test_add<decimal128_t>("-1e+0", "+1e-383", "-0.9999999999999999999999999999999999");
125+
126+
test_add<decimal128_t>("+1e+2", "+1e-383", "100.00000000000000000000000000000001");
127+
test_add<decimal128_t>("+1e+1", "+1e-383", "10.000000000000000000000000000000001");
128+
test_add<decimal128_t>("+1e+0", "+1e-383", "1.0000000000000000000000000000000001");
129+
test_add<decimal128_t>("+1e-1", "+1e-383", "0.10000000000000000000000000000000001");
130+
131+
test_add<decimal_fast128_t>("-1e+2", "+1e-383", "-99.99999999999999999999999999999999");
132+
test_add<decimal_fast128_t>("-1e+1", "+1e-383", "-9.999999999999999999999999999999999");
133+
test_add<decimal_fast128_t>("-1e+0", "+1e-383", "-0.9999999999999999999999999999999999");
134+
135+
test_add<decimal_fast128_t>("+1e+2", "+1e-383", "100.00000000000000000000000000000001");
136+
test_add<decimal_fast128_t>("+1e+1", "+1e-383", "10.000000000000000000000000000000001");
137+
test_add<decimal_fast128_t>("+1e+0", "+1e-383", "1.0000000000000000000000000000000001");
138+
test_add<decimal_fast128_t>("+1e-1", "+1e-383", "0.10000000000000000000000000000000001");
139+
140+
return boost::report_errors();
141+
}

0 commit comments

Comments
 (0)