From 2395df3359843c409042098e69664d1b22c823a4 Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Fri, 18 Sep 2026 20:22:09 +0800 Subject: [PATCH] Give an overflow the largest finite value in the modes which ask for it - 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 type gave an infinity in every mode. - A new helper reads the mode, and each constructor packs the largest finite value in place of the infinity when the helper says so. - The float constructors of the three IEEE types had their own overflow check, which lost the sign. They now take the same path as the fast types. Fixes #1459 --- include/boost/decimal/decimal128_t.hpp | 17 ++-- include/boost/decimal/decimal32_t.hpp | 19 ++-- include/boost/decimal/decimal64_t.hpp | 17 ++-- include/boost/decimal/decimal_fast128_t.hpp | 7 +- include/boost/decimal/decimal_fast32_t.hpp | 7 +- include/boost/decimal/decimal_fast64_t.hpp | 7 +- .../boost/decimal/detail/fenv_rounding.hpp | 15 +++ test/Jamfile | 2 + test/github_issue_1459.cpp | 98 +++++++++++++++++++ test/github_issue_1459_toward_zero.cpp | 11 +++ 10 files changed, 171 insertions(+), 29 deletions(-) create mode 100644 test/github_issue_1459.cpp create mode 100644 test/github_issue_1459_toward_zero.cpp 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"