Skip to content

Commit d1995d0

Browse files
authored
Merge pull request #1175 from cppalliance/clamp
2 parents 86e58cd + 0f93643 commit d1995d0

File tree

6 files changed

+240
-5
lines changed

6 files changed

+240
-5
lines changed

include/boost/decimal/decimal128_t.hpp

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,58 @@ constexpr decimal128_t::decimal128_t(T1 coeff, T2 exp, bool is_negative) noexcep
788788

789789
const auto exp_delta {biased_exp - static_cast<int>(detail::d128_max_biased_exponent)};
790790
const auto digit_delta {coeff_digits - exp_delta};
791-
if (digit_delta > 0 && coeff_digits + digit_delta <= detail::precision_v<decimal128_t>)
791+
if (biased_exp < 0 && coeff_digits == 1)
792+
{
793+
// This needs to be flushed to 0 or rounded to subnormal min
794+
rounding_mode current_round_mode {_boost_decimal_global_rounding_mode};
795+
796+
#ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION
797+
798+
if (!BOOST_DECIMAL_IS_CONSTANT_EVALUATED(coeff))
799+
{
800+
current_round_mode = _boost_decimal_global_runtime_rounding_mode;
801+
}
802+
803+
#endif
804+
805+
bool round {false};
806+
if (biased_exp == -1)
807+
{
808+
switch (current_round_mode)
809+
{
810+
case rounding_mode::fe_dec_to_nearest_from_zero:
811+
BOOST_DECIMAL_FALLTHROUGH
812+
case rounding_mode::fe_dec_to_nearest:
813+
if (reduced_coeff >= 5U)
814+
{
815+
round = true;
816+
}
817+
break;
818+
case rounding_mode::fe_dec_upward:
819+
if (!is_negative && reduced_coeff != 0U)
820+
{
821+
round = true;
822+
}
823+
break;
824+
default:
825+
round = false;
826+
break;
827+
}
828+
}
829+
830+
if (round)
831+
{
832+
// Subnormal min is just 1
833+
bits_ = UINT64_C(1);
834+
}
835+
else
836+
{
837+
bits_ = UINT64_C(0);
838+
}
839+
840+
bits_.high |= is_negative ? detail::d128_sign_mask : UINT64_C(0);
841+
}
842+
else if (digit_delta > 0 && coeff_digits + digit_delta <= detail::precision_v<decimal128_t>)
792843
{
793844
exp -= digit_delta;
794845
reduced_coeff *= detail::pow10(static_cast<significand_type>(digit_delta));
@@ -797,15 +848,16 @@ constexpr decimal128_t::decimal128_t(T1 coeff, T2 exp, bool is_negative) noexcep
797848
else if (coeff_digits + biased_exp <= detail::precision_v<decimal128_t>)
798849
{
799850
// Handle the case of sub-normals that don't need further rounding
800-
bits_.high = is_negative ? detail::d128_sign_mask : UINT64_C(0); // Reset the sign bit
851+
bits_ = {0u, 0u};
801852
const auto zeros {detail::remove_trailing_zeros(reduced_coeff)};
802853
biased_exp += static_cast<int>(zeros.number_of_removed_zeros);
803854
reduced_coeff = zeros.trimmed_number;
804855
if (biased_exp > 0)
805856
{
806857
reduced_coeff *= detail::pow10(static_cast<significand_type>(biased_exp));
807858
}
808-
bits_ |= reduced_coeff;
859+
bits_ = reduced_coeff;
860+
bits_.high |= is_negative ? detail::d128_sign_mask : UINT64_C(0); // Reset the sign bit
809861
}
810862
else if (digit_delta < 0 && coeff_digits - digit_delta <= detail::precision_v<decimal128_t>)
811863
{

include/boost/decimal/decimal32_t.hpp

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,58 @@ constexpr decimal32_t::decimal32_t(T1 coeff, T2 exp, bool is_negative) noexcept
710710

711711
const auto exp_delta {biased_exp - static_cast<int>(detail::d32_max_biased_exponent)};
712712
const auto digit_delta {coeff_digits - exp_delta};
713-
if (digit_delta > 0 && coeff_digits + digit_delta <= detail::precision)
713+
if (biased_exp < 0 && coeff_digits == 1)
714+
{
715+
// This needs to be flushed to 0 or rounded to subnormal min
716+
rounding_mode current_round_mode {_boost_decimal_global_rounding_mode};
717+
718+
#ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION
719+
720+
if (!BOOST_DECIMAL_IS_CONSTANT_EVALUATED(coeff))
721+
{
722+
current_round_mode = _boost_decimal_global_runtime_rounding_mode;
723+
}
724+
725+
#endif
726+
727+
bool round {false};
728+
if (biased_exp == -1)
729+
{
730+
switch (current_round_mode)
731+
{
732+
case rounding_mode::fe_dec_to_nearest_from_zero:
733+
BOOST_DECIMAL_FALLTHROUGH
734+
case rounding_mode::fe_dec_to_nearest:
735+
if (reduced_coeff >= 5U)
736+
{
737+
round = true;
738+
}
739+
break;
740+
case rounding_mode::fe_dec_upward:
741+
if (!is_negative && reduced_coeff != 0)
742+
{
743+
round = true;
744+
}
745+
break;
746+
default:
747+
round = false;
748+
break;
749+
}
750+
}
751+
752+
if (round)
753+
{
754+
// Subnormal min is just 1
755+
bits_ = UINT32_C(1);
756+
}
757+
else
758+
{
759+
bits_ = UINT32_C(0);
760+
}
761+
762+
bits_ |= is_negative ? detail::d32_sign_mask : UINT32_C(0);
763+
}
764+
else if (digit_delta > 0 && coeff_digits + digit_delta <= detail::precision)
714765
{
715766
exp -= digit_delta;
716767
reduced_coeff *= detail::pow10(static_cast<significand_type>(digit_delta));

include/boost/decimal/decimal64_t.hpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,59 @@ constexpr decimal64_t::decimal64_t(T1 coeff, T2 exp, bool is_negative) noexcept
704704

705705
const auto exp_delta {biased_exp - static_cast<int>(detail::d64_max_biased_exponent)};
706706
const auto digit_delta {coeff_digits - exp_delta};
707-
if (digit_delta > 0 && coeff_digits + digit_delta <= detail::precision_v<decimal64_t>)
707+
if (biased_exp < 0 && coeff_digits == 1)
708+
{
709+
// This needs to be flushed to 0 or rounded to subnormal min
710+
// e.g. 7e-399 should not become 70e-398 but 7e-400 should become 0
711+
rounding_mode current_round_mode {_boost_decimal_global_rounding_mode};
712+
713+
#ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION
714+
715+
if (!BOOST_DECIMAL_IS_CONSTANT_EVALUATED(coeff))
716+
{
717+
current_round_mode = _boost_decimal_global_runtime_rounding_mode;
718+
}
719+
720+
#endif
721+
722+
bool round {false};
723+
if (biased_exp == -1)
724+
{
725+
switch (current_round_mode)
726+
{
727+
case rounding_mode::fe_dec_to_nearest_from_zero:
728+
BOOST_DECIMAL_FALLTHROUGH
729+
case rounding_mode::fe_dec_to_nearest:
730+
if (reduced_coeff >= 5U)
731+
{
732+
round = true;
733+
}
734+
break;
735+
case rounding_mode::fe_dec_upward:
736+
if (!is_negative && reduced_coeff != 0)
737+
{
738+
round = true;
739+
}
740+
break;
741+
default:
742+
round = false;
743+
break;
744+
}
745+
}
746+
747+
if (round)
748+
{
749+
// Subnormal min is just 1
750+
bits_ = UINT64_C(1);
751+
}
752+
else
753+
{
754+
bits_ = UINT64_C(0);
755+
}
756+
757+
bits_ |= is_negative ? detail::d64_sign_mask : UINT64_C(0);
758+
}
759+
else if (digit_delta > 0 && coeff_digits + digit_delta <= detail::precision_v<decimal64_t>)
708760
{
709761
exp -= digit_delta;
710762
reduced_coeff *= detail::pow10(static_cast<significand_type>(digit_delta));
@@ -730,6 +782,22 @@ constexpr decimal64_t::decimal64_t(T1 coeff, T2 exp, bool is_negative) noexcept
730782
reduced_coeff *= detail::pow10(static_cast<significand_type>(offset));
731783
*this = decimal64_t(reduced_coeff, exp, is_negative);
732784
}
785+
else if (biased_exp > detail::max_biased_exp_v<decimal64_t>)
786+
{
787+
// Similar to subnormals, but for extremely large values
788+
const auto available_space {detail::precision_v<decimal64_t> - coeff_digits};
789+
if (available_space >= exp_delta)
790+
{
791+
reduced_coeff *= detail::pow10(static_cast<significand_type>(available_space));
792+
exp -= available_space;
793+
*this = decimal64_t(reduced_coeff, exp, is_negative);
794+
}
795+
else
796+
{
797+
bits_ = exp < 0 ? UINT64_C(0) : detail::d64_inf_mask;
798+
bits_ |= is_negative ? detail::d64_sign_mask : UINT64_C(0);
799+
}
800+
}
733801
else
734802
{
735803
// Reset the value and make sure to preserve the sign of 0/inf

include/boost/decimal/detail/config.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,4 +416,14 @@ static_assert(std::is_same<long double, __float128>::value, "__float128 should b
416416
# define BOOST_DECIMAL_HAS_CHAR8_T
417417
#endif
418418

419+
#if defined(__has_cpp_attribute) && __has_cpp_attribute(fallthrough) >= 201603L
420+
# define BOOST_DECIMAL_FALLTHROUGH [[fallthrough]];
421+
#elif defined(__clang__)
422+
# define BOOST_DECIMAL_FALLTHROUGH [[clang::fallthrough]];
423+
#elif defined(__GNUC__)
424+
# define BOOST_DECIMAL_FALLTHROUGH __attribute__ ((fallthrough));
425+
#else
426+
# define BOOST_DECIMAL_FALLTHROUGH
427+
#endif
428+
419429
#endif // BOOST_DECIMAL_DETAIL_CONFIG_HPP

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ run github_issue_1106.cpp ;
7575
run github_issue_1107.cpp ;
7676
run github_issue_1110.cpp ;
7777
run github_issue_1112.cpp ;
78+
run github_issue_1174.cpp ;
7879

7980
run link_1.cpp link_2.cpp link_3.cpp ;
8081
run quick.cpp ;

test/github_issue_1174.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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/1174
6+
//
7+
// The decTest tests only apply to decimal64_t so we need to cover the other two as well
8+
9+
#include <boost/decimal.hpp>
10+
#include <boost/core/lightweight_test.hpp>
11+
#include <random>
12+
13+
static std::mt19937_64 rng(42);
14+
static std::uniform_int_distribution<std::int32_t> dist(1, 1);
15+
16+
using namespace boost::decimal;
17+
18+
// These values are all from clamp.decTest
19+
// The tests for decimal32_t and decimal128_t are derived from the values used here
20+
template <typename T>
21+
void test()
22+
{
23+
constexpr T zero {0};
24+
constexpr auto sub_min {std::numeric_limits<T>::denorm_min()};
25+
const T a {dist(rng) * 7, detail::etiny_v<T>};
26+
27+
BOOST_TEST_GT(a, zero);
28+
29+
const T b {dist(rng) * 7, detail::etiny_v<T> - 1};
30+
31+
BOOST_TEST_EQ(b, sub_min);
32+
33+
const T c {dist(rng) * 7, detail::etiny_v<T> - 2};
34+
35+
BOOST_TEST_EQ(c, zero);
36+
37+
const T d {dist(rng) * 70, detail::etiny_v<T> - 1};
38+
39+
BOOST_TEST_EQ(a, d);
40+
41+
const T e {dist(rng) * 700, detail::etiny_v<T> - 2};
42+
43+
BOOST_TEST_EQ(a, e);
44+
}
45+
46+
int main()
47+
{
48+
test<decimal32_t>();
49+
test<decimal64_t>();
50+
test<decimal128_t>();
51+
52+
return boost::report_errors();
53+
}

0 commit comments

Comments
 (0)