diff --git a/include/boost/decimal/detail/cmath/fmax.hpp b/include/boost/decimal/detail/cmath/fmax.hpp index 5f376efd6..c7c8ed800 100644 --- a/include/boost/decimal/detail/cmath/fmax.hpp +++ b/include/boost/decimal/detail/cmath/fmax.hpp @@ -28,12 +28,29 @@ constexpr auto fmax(const T1 lhs, const T2 rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (isnan(lhs) && !isnan(rhs)) { - return static_cast(rhs); + if (issignaling(lhs)) + { + return nan_conversion(lhs); + } + else + { + return static_cast(rhs); + } } - else if ((!isnan(lhs) && isnan(rhs)) || - (isnan(lhs) && isnan(rhs))) + else if ((!isnan(lhs) && isnan(rhs)) || (isnan(lhs) && isnan(rhs))) { - return static_cast(lhs); + if (issignaling(lhs)) + { + return nan_conversion(lhs); + } + else if (issignaling(rhs)) + { + return nan_conversion(rhs); + } + else + { + return static_cast(lhs); + } } #endif diff --git a/include/boost/decimal/detail/cmath/fmin.hpp b/include/boost/decimal/detail/cmath/fmin.hpp index 5d94fd87b..70baa715c 100644 --- a/include/boost/decimal/detail/cmath/fmin.hpp +++ b/include/boost/decimal/detail/cmath/fmin.hpp @@ -27,12 +27,29 @@ constexpr auto fmin(const T1 lhs, const T2 rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (isnan(lhs) && !isnan(rhs)) { - return static_cast(rhs); + if (issignaling(lhs)) + { + return nan_conversion(lhs); + } + else + { + return static_cast(rhs); + } } - else if ((!isnan(lhs) && isnan(rhs)) || - (isnan(lhs) && isnan(rhs))) + else if ((!isnan(lhs) && isnan(rhs)) || (isnan(lhs) && isnan(rhs))) { - return static_cast(lhs); + if (issignaling(lhs)) + { + return nan_conversion(lhs); + } + else if (issignaling(rhs)) + { + return nan_conversion(rhs); + } + else + { + return static_cast(lhs); + } } #endif diff --git a/include/boost/decimal/detail/cmath/nan.hpp b/include/boost/decimal/detail/cmath/nan.hpp index 94da957d8..953be4e2a 100644 --- a/include/boost/decimal/detail/cmath/nan.hpp +++ b/include/boost/decimal/detail/cmath/nan.hpp @@ -149,17 +149,19 @@ BOOST_DECIMAL_EXPORT template constexpr auto read_payload(const T value) noexcept BOOST_DECIMAL_REQUIRES_RETURN(detail::is_ieee_type_v, T, typename T::significand_type) { + const auto positive_value {signbit(value) ? -value : value}; + if (!isnan(value)) { return 0U; } else if (issignaling(value)) { - return value.bits_ ^ std::numeric_limits::signaling_NaN().bits_; + return positive_value.bits_ ^ std::numeric_limits::signaling_NaN().bits_; } else { - return value.bits_ ^ std::numeric_limits::quiet_NaN().bits_; + return positive_value.bits_ ^ std::numeric_limits::quiet_NaN().bits_; } } diff --git a/test/Jamfile b/test/Jamfile index e318db689..b51a99beb 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -83,6 +83,7 @@ run github_issue_1112.cpp ; run github_issue_1174.cpp ; run github_issue_1260.cpp ; run github_issue_1294.cpp ; +run github_issue_1299.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; diff --git a/test/github_issue_1299.cpp b/test/github_issue_1299.cpp new file mode 100644 index 000000000..bd29361ec --- /dev/null +++ b/test/github_issue_1299.cpp @@ -0,0 +1,145 @@ +// 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/1299 + +#include +#include +#include + +using namespace boost::decimal; + +enum class result_t +{ + lhs, + rhs, + qnan +}; + +enum class result_sign_t +{ + positive, + negative +}; + +template +void test_fmax_value(const T lhs, const T rhs, const result_t result, const result_sign_t sign = result_sign_t::positive, const int payload_value = 0) +{ + const auto res {fmax(lhs, rhs)}; + + switch (result) + { + case result_t::lhs: + BOOST_TEST_EQ(lhs, res); + break; + + case result_t::rhs: + BOOST_TEST_EQ(rhs, res); + break; + + case result_t::qnan: + BOOST_TEST(isnan(res)); + BOOST_TEST(!issignaling(res)); + + if (sign == result_sign_t::negative) + { + BOOST_TEST(signbit(res)); + } + + if (payload_value) + { + const auto payload {read_payload(res)}; + BOOST_TEST_EQ(static_cast(payload_value), payload); + } + + break; + } +} + +template +void test_fmin_value(const T lhs, const T rhs, const result_t result, const result_sign_t sign = result_sign_t::positive, const int payload_value = 0) +{ + const auto res {fmin(lhs, rhs)}; + + switch (result) + { + case result_t::lhs: + BOOST_TEST_EQ(lhs, res); + break; + + case result_t::rhs: + BOOST_TEST_EQ(rhs, res); + break; + + case result_t::qnan: + BOOST_TEST(isnan(res)); + BOOST_TEST(!issignaling(res)); + + if (sign == result_sign_t::negative) + { + BOOST_TEST(signbit(res)); + } + + if (payload_value) + { + const auto payload {read_payload(res)}; + BOOST_TEST_EQ(static_cast(payload_value), payload); + } + + break; + } +} + +template +void test_driver() +{ + test_fmax_value(T{5}, T{42}, result_t::rhs); + test_fmax_value(std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), result_t::lhs); + test_fmax_value(-std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), result_t::lhs); + test_fmax_value(T{5}, std::numeric_limits::quiet_NaN(), result_t::lhs); + + // Any operation on SNAN is invalid and returns QNAN with the same payload (as applicable) + test_fmax_value(std::numeric_limits::signaling_NaN(), T{5}, result_t::qnan); + test_fmax_value(std::numeric_limits::quiet_NaN(), std::numeric_limits::signaling_NaN(), result_t::qnan); + test_fmax_value(std::numeric_limits::signaling_NaN(), std::numeric_limits::quiet_NaN(), result_t::qnan); + + T snan_payload {"-sNaN97"}; + test_fmax_value(snan_payload, std::numeric_limits::quiet_NaN(), result_t::qnan, result_sign_t::negative, 97); + test_fmax_value(std::numeric_limits::quiet_NaN(), snan_payload, result_t::qnan, result_sign_t::negative, 97); + + snan_payload = -snan_payload; + T qnan_payload {"NaN100"}; + test_fmax_value(snan_payload, qnan_payload, result_t::qnan, result_sign_t::positive, 97); + + test_fmin_value(T{5}, T{42}, result_t::lhs); + test_fmin_value(std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), result_t::lhs); + test_fmin_value(-std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN(), result_t::lhs); + test_fmin_value(T{5}, std::numeric_limits::quiet_NaN(), result_t::lhs); + + // Any operation on SNAN is invalid and returns QNAN with the same payload (as applicable) + test_fmin_value(std::numeric_limits::signaling_NaN(), T{5}, result_t::qnan); + test_fmin_value(std::numeric_limits::quiet_NaN(), std::numeric_limits::signaling_NaN(), result_t::qnan); + test_fmin_value(std::numeric_limits::signaling_NaN(), std::numeric_limits::quiet_NaN(), result_t::qnan); + + snan_payload = T{"-sNaN97"}; + test_fmin_value(snan_payload, std::numeric_limits::quiet_NaN(), result_t::qnan, result_sign_t::negative, 97); + test_fmin_value(std::numeric_limits::quiet_NaN(), snan_payload, result_t::qnan, result_sign_t::negative, 97); + + snan_payload = -snan_payload; + qnan_payload = T{"NaN100"}; + test_fmin_value(snan_payload, qnan_payload, result_t::qnan, result_sign_t::positive, 97); +} + +int main() +{ + test_driver(); + test_driver(); + test_driver(); + + test_driver(); + test_driver(); + test_driver(); + + return boost::report_errors(); +}