diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 6e9693511..4daf10730 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -815,6 +815,23 @@ constexpr decimal128_t::decimal128_t(T1 coeff, T2 exp, const detail::constructio { reduced_coeff *= detail::pow10(static_cast(biased_exp)); } + else if (biased_exp < 0) + { + const auto pos_biased_exp {-biased_exp}; + bool sticky {false}; + if (pos_biased_exp > 1) + { + // Need to ensure that we are following the current global rounding mode when packing subnormals + const auto shift_pow_10 {detail::pow10(static_cast(pos_biased_exp - 1))}; + const auto div_res {detail::impl::divmod(reduced_coeff, shift_pow_10)}; + reduced_coeff = div_res.quotient; + sticky = div_res.remainder != 0U; + } + // We may have to round the value so that it fits correctly + // e.g. 13e-399 -> 1e-398 + detail::fenv_round(reduced_coeff, is_negative, sticky); + } + bits_ = reduced_coeff; bits_.high |= is_negative ? detail::d128_sign_mask : UINT64_C(0); // Reset the sign bit } diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index 0018665b6..735ecc0b7 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -749,6 +749,23 @@ constexpr decimal32_t::decimal32_t(T1 coeff, T2 exp, const detail::construction_ { reduced_coeff *= detail::pow10(static_cast(biased_exp)); } + else if (biased_exp < 0) + { + const auto pos_biased_exp {-biased_exp}; + bool sticky {false}; + if (pos_biased_exp > 1) + { + // Need to ensure that we are following the current global rounding mode when packing subnormals + const auto shift_pow_10 {detail::pow10(static_cast(pos_biased_exp - 1))}; + const auto div_res {detail::impl::divmod(reduced_coeff, shift_pow_10)}; + reduced_coeff = div_res.quotient; + sticky = div_res.remainder != 0U; + } + // We may have to round the value so that it fits correctly + // e.g. 13e-399 -> 1e-398 + detail::fenv_round(reduced_coeff, is_negative, sticky); + } + bits_ |= reduced_coeff; } else if (digit_delta < 0 && coeff_digits - digit_delta <= detail::precision) diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index a510705a2..8c7fb6e99 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -745,6 +745,23 @@ constexpr decimal64_t::decimal64_t(T1 coeff, T2 exp, const detail::construction_ { reduced_coeff *= detail::pow10(static_cast(biased_exp)); } + else if (biased_exp < 0) + { + const auto pos_biased_exp {-biased_exp}; + bool sticky {false}; + if (pos_biased_exp > 1) + { + // Need to ensure that we are following the current global rounding mode when packing subnormals + const auto shift_pow_10 {detail::pow10(static_cast(pos_biased_exp - 1))}; + const auto div_res {detail::impl::divmod(reduced_coeff, shift_pow_10)}; + reduced_coeff = div_res.quotient; + sticky = div_res.remainder != 0U; + } + // We may have to round the value so that it fits correctly + // e.g. 13e-399 -> 1e-398 + detail::fenv_round(reduced_coeff, is_negative, sticky); + } + bits_ |= reduced_coeff; } else if (digit_delta < 0 && coeff_digits - digit_delta <= detail::precision_v) diff --git a/include/boost/decimal/detail/cmath/next.hpp b/include/boost/decimal/detail/cmath/next.hpp index fbd15c8bd..7ebcbc930 100644 --- a/include/boost/decimal/detail/cmath/next.hpp +++ b/include/boost/decimal/detail/cmath/next.hpp @@ -57,7 +57,13 @@ constexpr auto nextafter_impl(const DecimalType val, const bool direction) noexc sig = removed_zeros.trimmed_number; exp += static_cast(removed_zeros.number_of_removed_zeros); - if (removed_zeros.number_of_removed_zeros > 0) + if (exp == detail::etiny_v) + { + // If we are at the absolute minimum just add to the sig + // e.g. 2e-101 < 1.1e-100 + ++sig; + } + else if (removed_zeros.number_of_removed_zeros > 0) { // We need to shift an add // 1 -> 11 instead of 2 since 11e-101 < 2e-100 starting at 1e-100 diff --git a/test/Jamfile b/test/Jamfile index 2696639a1..e439f7ebf 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -86,6 +86,7 @@ run github_issue_1294.cpp ; run github_issue_1299.cpp ; run github_issue_1302.cpp ; run github_issue_1304.cpp ; +run github_issue_1306.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; diff --git a/test/github_issue_1105.cpp b/test/github_issue_1105.cpp index 2baf85f64..bcd501cfe 100644 --- a/test/github_issue_1105.cpp +++ b/test/github_issue_1105.cpp @@ -61,7 +61,7 @@ void test_non_preserving() BOOST_TEST_LE(two_val, next); const auto nines_value = decimal32_t{"99e-101"}; - const auto next_nines_res = decimal32_t{"991e-102"}; + const auto next_nines_res = decimal32_t{"100e-101"}; const auto res = nextafter(nines_value, one); BOOST_TEST_EQ(res, next_nines_res); diff --git a/test/github_issue_1306.cpp b/test/github_issue_1306.cpp new file mode 100644 index 000000000..18d005341 --- /dev/null +++ b/test/github_issue_1306.cpp @@ -0,0 +1,36 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// See: https://github.com/cppalliance/decimal/issues/1306 + +#include +#include +#include + +using namespace boost::decimal; + +template +void test() +{ + const T downward {13, detail::etiny_v + 3}; + BOOST_TEST_EQ(downward * T{"1e-4"}, std::numeric_limits::denorm_min()); + + const T upward {15, detail::etiny_v + 3}; + BOOST_TEST_EQ(upward * T{"1e-4"}, T(2, detail::etiny_v)); + + const T non_rounded {1234, detail::etiny_v - 3}; + BOOST_TEST_EQ(non_rounded, std::numeric_limits::denorm_min()); + + const T rounded {1999, detail::etiny_v - 3}; + BOOST_TEST_EQ(rounded, T(2, detail::etiny_v)); +} + +int main() +{ + test(); + test(); + test(); + + return boost::report_errors(); +}