diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 6483c1a2e..50ec691ab 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -779,6 +779,7 @@ constexpr decimal128_t::decimal128_t(T1 coeff, T2 exp, bool sign) noexcept else { bits_ = exp < 0 ? zero : detail::d128_inf_mask; + bits_.high |= sign ? detail::d128_sign_mask : UINT64_C(0); } } } diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index 9aea4680c..334e5e41b 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -700,7 +700,9 @@ constexpr decimal32_t::decimal32_t(T1 coeff, T2 exp, bool sign) noexcept // NOLI } else { + // Reset the value and make sure to preserve the sign of 0/inf bits_ = exp < 0 ? UINT32_C(0) : detail::d32_inf_mask; + bits_ |= sign ? detail::d32_sign_mask : UINT32_C(0); } } } diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index 9e26c57d6..afb7f2172 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -693,7 +693,9 @@ constexpr decimal64_t::decimal64_t(T1 coeff, T2 exp, bool sign) noexcept } else { + // Reset the value and make sure to preserve the sign of 0/inf bits_ = exp < 0 ? UINT64_C(0) : detail::d64_inf_mask; + bits_ |= sign ? detail::d64_sign_mask : UINT64_C(0); } } } diff --git a/include/boost/decimal/dpd_conversion.hpp b/include/boost/decimal/dpd_conversion.hpp index c77f9f215..96253a876 100644 --- a/include/boost/decimal/dpd_conversion.hpp +++ b/include/boost/decimal/dpd_conversion.hpp @@ -435,21 +435,26 @@ constexpr auto from_dpd_d32(const std::uint32_t dpd) noexcept static_assert(std::is_same::value || std::is_same::value, "Target decimal type must be 32-bits"); + const auto sign {(dpd & detail::d32_sign_mask) != 0}; + // First we check for non-finite values // Since they are in the same initial format as BID it's easy to check with our existing masks if ((dpd & detail::d32_inf_mask) == detail::d32_inf_mask) { if ((dpd & detail::d32_snan_mask) == detail::d32_snan_mask) { - return std::numeric_limits::signaling_NaN(); + return sign ? -std::numeric_limits::signaling_NaN() : + std::numeric_limits::signaling_NaN(); } else if ((dpd & detail::d32_nan_mask) == detail::d32_nan_mask) { - return std::numeric_limits::quiet_NaN(); + return sign ? -std::numeric_limits::quiet_NaN() : + std::numeric_limits::quiet_NaN(); } else { - return std::numeric_limits::infinity(); + return sign ? -std::numeric_limits::infinity() : + std::numeric_limits::infinity(); } } @@ -458,7 +463,6 @@ constexpr auto from_dpd_d32(const std::uint32_t dpd) noexcept constexpr std::uint32_t dpd_d32_combination_mask {UINT32_C(0b0'11111'000000'0000000000'0000000000)}; // The bit lengths are the same as used in the standard bid format - const auto sign {(dpd & detail::d32_sign_mask) != 0}; const auto combination_field_bits {(dpd & dpd_d32_combination_mask) >> 26U}; const auto exponent_field_bits {(dpd & dpd_d32_exponent_mask) >> 20U}; const auto significand_bits {(dpd & dpd_d32_significand_mask)}; @@ -635,21 +639,26 @@ constexpr auto from_dpd_d64(const std::uint64_t dpd) noexcept static_assert(std::is_same::value || std::is_same::value, "Target decimal type must be 64-bits"); + const auto sign {(dpd & detail::d64_sign_mask) != 0}; + // First we check for non-finite values // Since they are in the same initial format as BID it's easy to check with our existing masks if ((dpd & detail::d64_inf_mask) == detail::d64_inf_mask) { if ((dpd & detail::d64_snan_mask) == detail::d64_snan_mask) { - return std::numeric_limits::signaling_NaN(); + return sign ? -std::numeric_limits::signaling_NaN() : + std::numeric_limits::signaling_NaN(); } else if ((dpd & detail::d64_nan_mask) == detail::d64_nan_mask) { - return std::numeric_limits::quiet_NaN(); + return sign ? -std::numeric_limits::quiet_NaN() : + std::numeric_limits::quiet_NaN(); } else { - return std::numeric_limits::infinity(); + return sign ? -std::numeric_limits::infinity() : + std::numeric_limits::infinity(); } } @@ -659,7 +668,6 @@ constexpr auto from_dpd_d64(const std::uint64_t dpd) noexcept constexpr std::uint64_t dpd_d64_exponent_field_mask {UINT64_C(0b0'00000'11111111'0000000000'0000000000'0000000000'0000000000'0000000000)}; constexpr std::uint64_t dpd_d64_significand_field_mask {UINT64_C(0b0'00000'00000000'1111111111'1111111111'1111111111'1111111111'1111111111)}; - const auto sign {(dpd & detail::d64_sign_mask) != 0}; const auto combination_field_bits {(dpd & dpd_d64_combination_field_mask) >> 58U}; const auto exponent_field_bits {(dpd & dpd_d64_exponent_field_mask) >> 50U}; auto significand_bits {(dpd & dpd_d64_significand_field_mask)}; @@ -841,19 +849,24 @@ constexpr auto from_dpd_d128(const int128::uint128_t dpd) noexcept static_assert(std::is_same::value || std::is_same::value, "Target decimal type must be 128-bits"); + const auto sign {(dpd.high & detail::d128_sign_mask) != 0}; + if ((dpd & detail::d128_inf_mask) == detail::d128_inf_mask) { if ((dpd & detail::d128_snan_mask) == detail::d128_snan_mask) { - return std::numeric_limits::signaling_NaN(); + return sign ? -std::numeric_limits::signaling_NaN() : + std::numeric_limits::signaling_NaN(); } else if ((dpd & detail::d128_nan_mask) == detail::d128_nan_mask) { - return std::numeric_limits::quiet_NaN(); + return sign ? -std::numeric_limits::quiet_NaN() : + std::numeric_limits::quiet_NaN(); } else { - return std::numeric_limits::infinity(); + return sign ? -std::numeric_limits::infinity() : + std::numeric_limits::infinity(); } } @@ -862,7 +875,6 @@ constexpr auto from_dpd_d128(const int128::uint128_t dpd) noexcept constexpr int128::uint128_t d128_dpd_significand_mask {UINT64_C(0b1111111111'1111111111'1111111111'1111111111'111111), UINT64_MAX}; // The bit lengths are the same as used in the standard bid format - const auto sign {(dpd.high & detail::d128_sign_mask) != 0}; const auto combination_field_bits {(dpd.high & d128_dpd_combination_field_mask_high_bits) >> 58U}; const auto exponent_field_bits {(dpd.high & d128_dpd_exponent_mask_high_bits) >> 46U}; auto significand_bits {(dpd & d128_dpd_significand_mask)}; diff --git a/test/Jamfile b/test/Jamfile index b1817f974..e7a56586c 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -67,6 +67,7 @@ run github_issue_1054.cpp ; run github_issue_1055.cpp ; run github_issue_1057.cpp ; compile-fail github_issue_1087.cpp ; +run github_issue_1091.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; run random_decimal32_comp.cpp ; diff --git a/test/github_issue_1091.cpp b/test/github_issue_1091.cpp new file mode 100644 index 000000000..0c5b80162 --- /dev/null +++ b/test/github_issue_1091.cpp @@ -0,0 +1,51 @@ +// Copyright 2025 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/1091 + +#include +#include + +using namespace boost::decimal; + +template +void test() +{ + constexpr T one {1u, 0}; + T low {std::numeric_limits::lowest()}; + BOOST_TEST(!isinf(low)); + + low *= 2; + + BOOST_TEST(low < one); + BOOST_TEST(one > low); + BOOST_TEST(isinf(low)); + + T high {std::numeric_limits::max()}; + BOOST_TEST(!isinf(high)); + + high *= 2; + + BOOST_TEST(high > one); + BOOST_TEST(one < high); + BOOST_TEST(isinf(high)); + + T min {-std::numeric_limits::denorm_min()}; + + min /= 2; + + BOOST_TEST(min < one); + BOOST_TEST(min == (one - one)); + BOOST_TEST(signbit(min)); +} + +int main() +{ + test(); + test(); + test(); + + return boost::report_errors(); +} +