From 4f41cb024e1cab827184e7c214e1d71c867f0a74 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 16 Jan 2026 15:35:04 -0500 Subject: [PATCH 1/4] Make reproducer test set --- test/Jamfile | 1 + test/github_issue_1302.cpp | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 test/github_issue_1302.cpp diff --git a/test/Jamfile b/test/Jamfile index b51a99beb..1bc5282c5 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -84,6 +84,7 @@ run github_issue_1174.cpp ; run github_issue_1260.cpp ; run github_issue_1294.cpp ; run github_issue_1299.cpp ; +run github_issue_1302.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; diff --git a/test/github_issue_1302.cpp b/test/github_issue_1302.cpp new file mode 100644 index 000000000..a131bf05d --- /dev/null +++ b/test/github_issue_1302.cpp @@ -0,0 +1,65 @@ +// 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/1294 + +#include +#include +#include + +using namespace boost::decimal; + +template +void test() +{ + // Positive LHS, Positive RHS + BOOST_TEST_EQ(std::numeric_limits::infinity() * std::numeric_limits::infinity(), std::numeric_limits::infinity()); + BOOST_TEST_EQ(std::numeric_limits::infinity() * T{1000}, std::numeric_limits::infinity()); + BOOST_TEST_EQ(std::numeric_limits::infinity() * 1000, std::numeric_limits::infinity()); + BOOST_TEST_EQ(T{1000} * std::numeric_limits::infinity(), std::numeric_limits::infinity()); + BOOST_TEST_EQ(1000 * std::numeric_limits::infinity(), std::numeric_limits::infinity()); + BOOST_TEST(isnan(std::numeric_limits::infinity() * std::numeric_limits::quiet_NaN())); + BOOST_TEST(isnan(std::numeric_limits::quiet_NaN() * std::numeric_limits::infinity())); + + // Positive LHS, Negative RHS + BOOST_TEST_EQ(std::numeric_limits::infinity() * -std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(std::numeric_limits::infinity() * -T{1000}, -std::numeric_limits::infinity()); + BOOST_TEST_EQ(std::numeric_limits::infinity() * -1000, -std::numeric_limits::infinity()); + BOOST_TEST_EQ(T{1000} * -std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(1000 * -std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + BOOST_TEST(isnan(std::numeric_limits::infinity() * -std::numeric_limits::quiet_NaN())); + BOOST_TEST(isnan(std::numeric_limits::quiet_NaN() * -std::numeric_limits::infinity())); + + // Negative RHS, positive LHS + BOOST_TEST_EQ(-std::numeric_limits::infinity() * std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(-std::numeric_limits::infinity() * T{1000}, -std::numeric_limits::infinity()); + BOOST_TEST_EQ(-std::numeric_limits::infinity() * 1000, -std::numeric_limits::infinity()); + BOOST_TEST_EQ(-T{1000} * std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + BOOST_TEST_EQ(-1000 * std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + BOOST_TEST(isnan(-std::numeric_limits::infinity() * std::numeric_limits::quiet_NaN())); + BOOST_TEST(isnan(-std::numeric_limits::quiet_NaN() * std::numeric_limits::infinity())); + + // Negative RHS, Negative RHS + BOOST_TEST_EQ(-std::numeric_limits::infinity() * -std::numeric_limits::infinity(), std::numeric_limits::infinity()); + BOOST_TEST_EQ(-std::numeric_limits::infinity() * -T{1000}, std::numeric_limits::infinity()); + BOOST_TEST_EQ(-std::numeric_limits::infinity() * -1000, std::numeric_limits::infinity()); + BOOST_TEST_EQ(-T{1000} * -std::numeric_limits::infinity(), std::numeric_limits::infinity()); + BOOST_TEST_EQ(-1000 * -std::numeric_limits::infinity(), std::numeric_limits::infinity()); + BOOST_TEST(isnan(-std::numeric_limits::infinity() * -std::numeric_limits::quiet_NaN())); + BOOST_TEST(isnan(-std::numeric_limits::quiet_NaN() * -std::numeric_limits::infinity())); + +} + +int main() +{ + test(); + test(); + test(); + + test(); + test(); + test(); + + return boost::report_errors(); +} From b0a255da9383235d713afca695b7aff39bc80185 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 16 Jan 2026 15:58:51 -0500 Subject: [PATCH 2/4] Fix decimal32 logic --- include/boost/decimal/decimal32_t.hpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index 8eea6240c..c3e285db9 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -1772,6 +1772,22 @@ constexpr auto operator*(const decimal32_t lhs, const decimal32_t rhs) noexcept { return from_bits(detail::d32_nan_mask); } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) != signbit(rhs))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) == signbit(rhs))) + { + return signbit(lhs) ? -lhs : lhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) != signbit(lhs))) + { + return signbit(rhs) ? rhs : -rhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) == signbit(lhs))) + { + return signbit(rhs) ? -rhs : rhs; + } return detail::check_non_finite(lhs, rhs); } @@ -1793,6 +1809,15 @@ constexpr auto operator*(const decimal32_t lhs, const Integer rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (!isfinite(lhs)) { + if (isinf(lhs) && (signbit(lhs) != (rhs < 0))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && (signbit(lhs) == (rhs < 0))) + { + return signbit(lhs) ? -lhs : lhs; + } + return detail::check_non_finite(lhs); } #endif From ae7575531b78cdb36bd4cf236d4965fc30006f9f Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 16 Jan 2026 16:17:22 -0500 Subject: [PATCH 3/4] Port logic to other types --- include/boost/decimal/decimal128_t.hpp | 25 +++++++++++++++++++++ include/boost/decimal/decimal64_t.hpp | 25 +++++++++++++++++++++ include/boost/decimal/decimal_fast128_t.hpp | 25 +++++++++++++++++++++ include/boost/decimal/decimal_fast32_t.hpp | 25 +++++++++++++++++++++ include/boost/decimal/decimal_fast64_t.hpp | 25 +++++++++++++++++++++ 5 files changed, 125 insertions(+) diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index 5048f455a..dab83bd8d 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -1771,6 +1771,22 @@ constexpr auto operator*(const decimal128_t& lhs, const decimal128_t& rhs) noexc { return from_bits(detail::d128_nan_mask); } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) != signbit(rhs))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) == signbit(rhs))) + { + return signbit(lhs) ? -lhs : lhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) != signbit(lhs))) + { + return signbit(rhs) ? rhs : -rhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) == signbit(lhs))) + { + return signbit(rhs) ? -rhs : rhs; + } return detail::check_non_finite(lhs, rhs); } @@ -1793,6 +1809,15 @@ constexpr auto operator*(const decimal128_t lhs, const Integer rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (not_finite(lhs)) { + if (isinf(lhs) && (signbit(lhs) != (rhs < 0))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && (signbit(lhs) == (rhs < 0))) + { + return signbit(lhs) ? -lhs : lhs; + } + return detail::check_non_finite(lhs); } #endif diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index b963aff10..e789b8bad 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -1774,6 +1774,22 @@ constexpr auto operator*(const decimal64_t lhs, const decimal64_t rhs) noexcept { return from_bits(detail::d64_nan_mask); } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) != signbit(rhs))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) == signbit(rhs))) + { + return signbit(lhs) ? -lhs : lhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) != signbit(lhs))) + { + return signbit(rhs) ? rhs : -rhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) == signbit(lhs))) + { + return signbit(rhs) ? -rhs : rhs; + } return detail::check_non_finite(lhs, rhs); } @@ -1795,6 +1811,15 @@ constexpr auto operator*(const decimal64_t lhs, const Integer rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (not_finite(lhs)) { + if (isinf(lhs) && (signbit(lhs) != (rhs < 0))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && (signbit(lhs) == (rhs < 0))) + { + return signbit(lhs) ? -lhs : lhs; + } + return detail::check_non_finite(lhs); } #endif diff --git a/include/boost/decimal/decimal_fast128_t.hpp b/include/boost/decimal/decimal_fast128_t.hpp index b6dd09a09..e61f9b285 100644 --- a/include/boost/decimal/decimal_fast128_t.hpp +++ b/include/boost/decimal/decimal_fast128_t.hpp @@ -1129,6 +1129,22 @@ constexpr auto operator*(const decimal_fast128_t& lhs, const decimal_fast128_t& { return direct_init_d128(detail::d128_fast_qnan, 0, false); } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) != signbit(rhs))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) == signbit(rhs))) + { + return signbit(lhs) ? -lhs : lhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) != signbit(lhs))) + { + return signbit(rhs) ? rhs : -rhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) == signbit(lhs))) + { + return signbit(rhs) ? -rhs : rhs; + } return detail::check_non_finite(lhs, rhs); } @@ -1147,6 +1163,15 @@ constexpr auto operator*(const decimal_fast128_t& lhs, const Integer rhs) noexce #ifndef BOOST_DECIMAL_FAST_MATH if (not_finite(lhs)) { + if (isinf(lhs) && (signbit(lhs) != (rhs < 0))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && (signbit(lhs) == (rhs < 0))) + { + return signbit(lhs) ? -lhs : lhs; + } + return detail::check_non_finite(lhs); } #endif diff --git a/include/boost/decimal/decimal_fast32_t.hpp b/include/boost/decimal/decimal_fast32_t.hpp index bd159dbe5..4380ba94f 100644 --- a/include/boost/decimal/decimal_fast32_t.hpp +++ b/include/boost/decimal/decimal_fast32_t.hpp @@ -1123,6 +1123,22 @@ constexpr auto operator*(const decimal_fast32_t lhs, const decimal_fast32_t rhs) { return direct_init(detail::d32_fast_qnan, UINT8_C(0)); } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) != signbit(rhs))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) == signbit(rhs))) + { + return signbit(lhs) ? -lhs : lhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) != signbit(lhs))) + { + return signbit(rhs) ? rhs : -rhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) == signbit(lhs))) + { + return signbit(rhs) ? -rhs : rhs; + } return detail::check_non_finite(lhs, rhs); } @@ -1141,6 +1157,15 @@ constexpr auto operator*(const decimal_fast32_t lhs, const Integer rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (!isfinite(lhs)) { + if (isinf(lhs) && (signbit(lhs) != (rhs < 0))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && (signbit(lhs) == (rhs < 0))) + { + return signbit(lhs) ? -lhs : lhs; + } + return detail::check_non_finite(lhs); } #endif diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index 23aa5efa6..43d1ac240 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -1248,6 +1248,22 @@ constexpr auto operator*(const decimal_fast64_t lhs, const decimal_fast64_t rhs) { return direct_init_d64(detail::d64_fast_qnan, 0, false); } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) != signbit(rhs))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && !isnan(rhs) && (signbit(lhs) == signbit(rhs))) + { + return signbit(lhs) ? -lhs : lhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) != signbit(lhs))) + { + return signbit(rhs) ? rhs : -rhs; + } + else if (isinf(rhs) && !isnan(lhs) && (signbit(rhs) == signbit(lhs))) + { + return signbit(rhs) ? -rhs : rhs; + } return detail::check_non_finite(lhs, rhs); } @@ -1266,6 +1282,15 @@ constexpr auto operator*(const decimal_fast64_t lhs, const Integer rhs) noexcept #ifndef BOOST_DECIMAL_FAST_MATH if (not_finite(lhs)) { + if (isinf(lhs) && (signbit(lhs) != (rhs < 0))) + { + return signbit(lhs) ? lhs : -lhs; + } + else if (isinf(lhs) && (signbit(lhs) == (rhs < 0))) + { + return signbit(lhs) ? -lhs : lhs; + } + return detail::check_non_finite(lhs); } #endif From 958ff9a993989f87b3cc177c305d2e8d569ae5f6 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Fri, 16 Jan 2026 16:17:31 -0500 Subject: [PATCH 4/4] Fix test logic --- test/test_to_chars.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_to_chars.cpp b/test/test_to_chars.cpp index 52d1c2ca5..85175b744 100644 --- a/test/test_to_chars.cpp +++ b/test/test_to_chars.cpp @@ -80,7 +80,7 @@ void test_error_value(const char* input, chars_format format, int precision = -1 template void test_non_finite_values() { - std::uniform_real_distribution dist(-1.0, 1.0); + std::uniform_real_distribution dist(0.0f, 1.0f); const auto formats = {chars_format::fixed, chars_format::scientific, chars_format::general, chars_format::hex};