From 16b57d0bb0bb8474adc95fab131537856e667604 Mon Sep 17 00:00:00 2001 From: 0x74h51n Date: Mon, 24 Aug 2026 15:40:35 +0300 Subject: [PATCH] Fix fmod post-condition check for negative remainders The check compared signed values instead of magnitudes, so it fired on every negative remainder, git_issue_604.cpp now covers non-zero remainders too --- .../multiprecision/detail/default_ops.hpp | 30 +++++------ test/git_issue_604.cpp | 50 +++++++++++++++++++ 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/include/boost/multiprecision/detail/default_ops.hpp b/include/boost/multiprecision/detail/default_ops.hpp index 87ada6e4d..2695c5a27 100644 --- a/include/boost/multiprecision/detail/default_ops.hpp +++ b/include/boost/multiprecision/detail/default_ops.hpp @@ -1299,28 +1299,28 @@ inline BOOST_MP_CXX14_CONSTEXPR void eval_fmod(T& result, const T& a, const T& b eval_floor(n, result); eval_multiply(n, b); eval_subtract(result, a, n); - if (eval_get_sign(result) != 0) + + int c = eval_get_sign(result); + if (c != 0) { // // Sanity check, that due to rounding errors in division, // we haven't accidently calculated the wrong value: // See https://github.com/boostorg/multiprecision/issues/604 for an example. // - if (eval_get_sign(result) == eval_get_sign(b)) - { - if (result.compare(b) >= 0) - { - eval_subtract(result, b); - } - } - else - { - n = b; + // The check is by magnitude, so the comparison reverses when the remainder + // is negative: see https://github.com/boostorg/multiprecision/issues/764 + // + + n = b; + if (c != eval_get_sign(b)) n.negate(); - if (result.compare(n) >= 0) - { - eval_subtract(result, n); - } + int cmp = result.compare(n); + if (c < 0) + cmp = -cmp; + if (cmp >= 0) + { + eval_subtract(result, n); } } } diff --git a/test/git_issue_604.cpp b/test/git_issue_604.cpp index 8d289336d..8bdf10809 100644 --- a/test/git_issue_604.cpp +++ b/test/git_issue_604.cpp @@ -65,6 +65,56 @@ void test() T r = boost::multiprecision::fmod(val1, val2); BOOST_CHECK_EQUAL(r, T(0)); } + // Non-zero remainders with a negative dividend, see issue 764: both conventions + // agree at zero, so the cases above cannot tell them apart. + { + const T val1{-8.5}; + const T val2{2.5}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(-1)); + } + { + const T val1{-8.5}; + const T val2{-2.5}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(-1)); + } + { + const T val1{8.5}; + const T val2{2.5}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(1)); + } + { + const T val1{8.5}; + const T val2{-2.5}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(1)); + } + { + const T val1{0.5}; + const T val2{-1}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(0.5)); + } + { + const T val1{-0.5}; + const T val2{1}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(-0.5)); + } + { + const T val1{-0.5}; + const T val2{-1}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(-0.5)); + } + { + const T val1{0.5}; + const T val2{1}; + T r = boost::multiprecision::fmod(val1, val2); + BOOST_CHECK_EQUAL(r, T(0.5)); + } }