diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 2313c6dcb..e781ecd19 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -926,12 +926,20 @@ BOOST_DECIMAL_CUDA_CONSTEXPR decimal128_t::decimal128_t(T1 coeff, T2 exp, const exp -= available_space; *this = detail::pack_in_range(reduced_coeff, exp, is_negative); } + else if (detail::overflow_is_finite(is_negative)) + { + *this = detail::pack_in_range(detail::d128_max_significand_value, detail::max_biased_exp_v - detail::bias_v, is_negative); + } else { bits_ = detail::d128_inf_mask; bits_.high |= is_negative ? detail::d128_sign_mask : UINT64_C(0); } } + else if (exp >= 0 && detail::overflow_is_finite(is_negative)) + { + *this = detail::pack_in_range(detail::d128_max_significand_value, detail::max_biased_exp_v - detail::bias_v, is_negative); + } else { bits_ = exp < 0 ? zero : detail::d128_inf_mask; @@ -1103,14 +1111,7 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal128_t::decimal128_t(const Float val) noexce << "\nSign: " << components.sign << std::endl; #endif - if (components.exponent > detail::emax_v) - { - *this = from_bits(detail::d128_inf_mask); - } - else - { - *this = decimal128_t {components.mantissa, components.exponent, components.sign}; - } + *this = decimal128_t {components.mantissa, components.exponent, components.sign}; } } diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index 24188c86f..af97316a8 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -820,12 +820,20 @@ BOOST_DECIMAL_CUDA_CONSTEXPR decimal32_t::decimal32_t(T1 coeff, T2 exp, const de exp -= available_space; *this = detail::pack_in_range(reduced_coeff, exp, is_negative); } + else if (detail::overflow_is_finite(is_negative)) + { + *this = detail::pack_in_range(detail::d32_max_significand_value, detail::max_biased_exp_v - detail::bias, is_negative); + } else { bits_ = detail::d32_inf_mask; bits_ |= is_negative ? detail::d32_sign_mask : UINT32_C(0); } } + else if (exp >= 0 && detail::overflow_is_finite(is_negative)) + { + *this = detail::pack_in_range(detail::d32_max_significand_value, detail::max_biased_exp_v - detail::bias, is_negative); + } else { // Reset the value and make sure to preserve the sign of 0/inf @@ -1850,16 +1858,7 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal32_t::decimal32_t(const Float val) noexcept << "\nSign: " << components.sign << std::endl; #endif - #ifndef BOOST_DECIMAL_FAST_MATH - if (components.exponent > detail::emax) - { - *this = boost::decimal::from_bits(boost::decimal::detail::d32_inf_mask); - } - else - #endif - { - *this = decimal32_t {components.mantissa, components.exponent, components.sign}; - } + *this = decimal32_t {components.mantissa, components.exponent, components.sign}; } } diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index a5bd6f9a1..2bbbf5c6a 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -857,12 +857,20 @@ BOOST_DECIMAL_CUDA_CONSTEXPR decimal64_t::decimal64_t(T1 coeff, T2 exp, const de exp -= available_space; *this = detail::pack_in_range(reduced_coeff, exp, is_negative); } + else if (detail::overflow_is_finite(is_negative)) + { + *this = detail::pack_in_range(detail::d64_max_significand_value, detail::max_biased_exp_v - detail::bias_v, is_negative); + } else { bits_ = exp < 0 ? UINT64_C(0) : detail::d64_inf_mask; bits_ |= is_negative ? detail::d64_sign_mask : UINT64_C(0); } } + else if (exp >= 0 && detail::overflow_is_finite(is_negative)) + { + *this = detail::pack_in_range(detail::d64_max_significand_value, detail::max_biased_exp_v - detail::bias_v, is_negative); + } else { // Reset the value and make sure to preserve the sign of 0/inf @@ -1031,14 +1039,7 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal64_t::decimal64_t(const Float val) noexcept << "\nSign: " << components.sign << std::endl; #endif - if (components.exponent > detail::emax_v) - { - *this = from_bits(detail::d64_inf_mask); - } - else - { - *this = decimal64_t {components.mantissa, components.exponent, components.sign}; - } + *this = decimal64_t {components.mantissa, components.exponent, components.sign}; } } diff --git a/include/boost/decimal/decimal_fast128_t.hpp b/include/boost/decimal/decimal_fast128_t.hpp index fd9213033..f96c421f3 100644 --- a/include/boost/decimal/decimal_fast128_t.hpp +++ b/include/boost/decimal/decimal_fast128_t.hpp @@ -579,7 +579,12 @@ constexpr decimal_fast128_t::decimal_fast128_t(T1 coeff, T2 exp, const detail::c const auto biased_exp {static_cast(exp) + detail::bias_v}; - if (biased_exp > detail::max_biased_exp_v) + if (biased_exp > detail::max_biased_exp_v && detail::overflow_is_finite(is_negative)) + { + significand_ = static_cast(max_normal_significand); + exponent_ = static_cast(detail::max_biased_exp_v); + } + else if (biased_exp > detail::max_biased_exp_v) { significand_ = detail::d128_fast_inf; } diff --git a/include/boost/decimal/decimal_fast32_t.hpp b/include/boost/decimal/decimal_fast32_t.hpp index f1bb9b559..e79d51723 100644 --- a/include/boost/decimal/decimal_fast32_t.hpp +++ b/include/boost/decimal/decimal_fast32_t.hpp @@ -562,7 +562,12 @@ constexpr decimal_fast32_t::decimal_fast32_t(T1 coeff, T2 exp, const detail::con const auto biased_exp {static_cast(exp) + detail::bias}; // decimal32_t exponent holds 8 bits - if (biased_exp > detail::max_biased_exp_v) + if (biased_exp > detail::max_biased_exp_v && detail::overflow_is_finite(is_negative)) + { + significand_ = static_cast(max_normal_significand); + exponent_ = static_cast(detail::max_biased_exp_v); + } + else if (biased_exp > detail::max_biased_exp_v) { significand_ = detail::d32_fast_inf; } diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index a304264af..3d4464eba 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -575,7 +575,12 @@ constexpr decimal_fast64_t::decimal_fast64_t(T1 coeff, T2 exp, const detail::con const auto biased_exp {static_cast(exp) + detail::bias_v}; - if (biased_exp > detail::max_biased_exp_v) + if (biased_exp > detail::max_biased_exp_v && detail::overflow_is_finite(is_negative)) + { + significand_ = static_cast(max_normal_significand); + exponent_ = static_cast(detail::max_biased_exp_v); + } + else if (biased_exp > detail::max_biased_exp_v) { significand_ = detail::d64_fast_inf; } diff --git a/include/boost/decimal/detail/fenv_rounding.hpp b/include/boost/decimal/detail/fenv_rounding.hpp index 3a684478c..8f36166f2 100644 --- a/include/boost/decimal/detail/fenv_rounding.hpp +++ b/include/boost/decimal/detail/fenv_rounding.hpp @@ -645,6 +645,21 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto fenv_round(T& val, bool is_neg = false, bool s #endif +// IEEE 754-2019 7.4: an overflow is the largest finite value in the toward zero mode, and +// in the directed mode which points at zero for the sign. Else it is an infinity. +BOOST_DECIMAL_CUDA_CONSTEXPR auto overflow_is_finite(const bool is_negative) noexcept -> bool +{ + auto round {_boost_decimal_global_rounding_mode}; + #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + if (!BOOST_DECIMAL_IS_CONSTANT_EVALUATED(is_negative)) + { + round = _boost_decimal_global_runtime_rounding_mode; + } + #endif + return round == rounding_mode::fe_dec_toward_zero || + round == (is_negative ? rounding_mode::fe_dec_upward : rounding_mode::fe_dec_downward); +} + #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wsign-conversion" diff --git a/test/Jamfile b/test/Jamfile index 54b970b42..589b2c10e 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -106,6 +106,8 @@ run github_issue_1451.cpp ; run github_issue_1453.cpp ; run github_issue_1455.cpp ; run github_issue_1455_downward.cpp ; +run github_issue_1459.cpp ; +run github_issue_1459_toward_zero.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; diff --git a/test/github_issue_1459.cpp b/test/github_issue_1459.cpp new file mode 100644 index 000000000..9a638167b --- /dev/null +++ b/test/github_issue_1459.cpp @@ -0,0 +1,98 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// https://github.com/boostorg/decimal/issues/1459 +// +// IEEE 754-2019 7.4: an overflow is the largest finite value in the toward zero mode, and +// in the directed mode which points at zero for the sign. Every overflow gave an infinity. + +#include +#include +#include + +using namespace boost::decimal; + +// BOOST_DECIMAL_IF_CONSTEXPR is a plain if before C++17, so the constant condition warns +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) +#endif + +// The largest finite value or an infinity, with the sign of the result +template +void check_overflow(const T value, const bool neg, const bool finite) +{ + BOOST_TEST_EQ(signbit(value), neg); + BOOST_TEST_EQ(isinf(value), !finite); + BOOST_TEST_EQ(abs(value) == (std::numeric_limits::max)(), finite); +} + +template +void check(const rounding_mode mode) +{ + fesetround(mode); + const T m {(std::numeric_limits::max)()}; + const T zero {0, 0}; + const T two {2, 0}; + const bool pos {mode == rounding_mode::fe_dec_toward_zero || mode == rounding_mode::fe_dec_downward}; + const bool neg {mode == rounding_mode::fe_dec_toward_zero || mode == rounding_mode::fe_dec_upward}; + + check_overflow(m * m, false, pos); + check_overflow(-m * m, true, neg); + check_overflow(m + m, false, pos); + check_overflow(-m - m, true, neg); + check_overflow(m * two, false, pos); + check_overflow(-m * two, true, neg); + check_overflow(fma(m, m, zero), false, pos); + check_overflow(fma(-m, m, zero), true, neg); + check_overflow(T {1, 99999}, false, pos); + check_overflow(T {-1, 99999}, true, neg); + check_overflow(T {"1e99999"}, false, pos); + check_overflow(T {"-1e99999"}, true, neg); + + // The largest long double overflows the 32 and 64 bit types, but not the 128 bit types + BOOST_DECIMAL_IF_CONSTEXPR (std::numeric_limits::max_exponent10 > std::numeric_limits::max_exponent10) + { + check_overflow(T {std::numeric_limits::max()}, false, pos); + check_overflow(T {-std::numeric_limits::max()}, true, neg); + } +} + +template +void test() +{ + #ifdef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + // fesetround has no effect here, thus only the compile-time mode is tested + check(_boost_decimal_global_rounding_mode); + #else + check(rounding_mode::fe_dec_to_nearest); + check(rounding_mode::fe_dec_downward); + check(rounding_mode::fe_dec_upward); + check(rounding_mode::fe_dec_toward_zero); + check(rounding_mode::fe_dec_to_nearest_from_zero); + #endif +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +int main() +{ + constexpr bool finite_positive {_boost_decimal_global_rounding_mode == rounding_mode::fe_dec_toward_zero || + _boost_decimal_global_rounding_mode == rounding_mode::fe_dec_downward}; + static_assert(isinf(decimal32_t {1, 99999}) != finite_positive, ""); + static_assert(isinf(decimal128_t {1, 99999}) != finite_positive, ""); + + test(); + test(); + test(); + test(); + test(); + test(); + + fesetround(rounding_mode::fe_dec_to_nearest); + + return boost::report_errors(); +} diff --git a/test/github_issue_1459_toward_zero.cpp b/test/github_issue_1459_toward_zero.cpp new file mode 100644 index 000000000..1354e4973 --- /dev/null +++ b/test/github_issue_1459_toward_zero.cpp @@ -0,0 +1,11 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// The same checks under the compile-time toward zero mode, where both signs are finite, +// and without constant evaluation detection, where only that mode exists + +#define BOOST_DECIMAL_FE_DEC_TOWARD_ZERO +#define BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + +#include "github_issue_1459.cpp"