diff --git a/include/boost/decimal/detail/div_impl.hpp b/include/boost/decimal/detail/div_impl.hpp index 046210c5b..0ef288c56 100644 --- a/include/boost/decimal/detail/div_impl.hpp +++ b/include/boost/decimal/detail/div_impl.hpp @@ -260,9 +260,12 @@ BOOST_DECIMAL_FORCE_INLINE BOOST_DECIMAL_CUDA_CONSTEXPR auto generic_div_impl(co constexpr auto wide_offset {std::numeric_limits::digits10 - precision_v}; constexpr auto wide_tens {pow10(static_cast(wide_offset))}; const auto wide_sig {static_cast(lhs_c.sig) * wide_tens}; - const auto wide_q {wide_sig / static_cast(rhs_c.sig)}; - const auto wide_exp {(lhs_c.exp - static_cast(wide_offset)) - rhs_c.exp}; - return DecimalType{wide_q, wide_exp, sign}; + const auto wide_div {static_cast(rhs_c.sig)}; + const auto wide_q {wide_sig / wide_div}; + // The constructor rounds by the digits it drops, thus the remainder goes in as one digit + const auto sticky {static_cast(wide_sig - wide_q * wide_div != 0U)}; + const auto wide_exp {(lhs_c.exp - static_cast(wide_offset) - 1) - rhs_c.exp}; + return DecimalType{wide_q * 10U + sticky, wide_exp, sign}; } constexpr auto ten_to_p {pow10(static_cast(precision_v))}; @@ -301,9 +304,12 @@ BOOST_DECIMAL_FORCE_INLINE BOOST_DECIMAL_CUDA_CONSTEXPR auto d64_generic_div_imp constexpr auto wide_offset {std::numeric_limits::digits10 - precision_v}; const auto wide_tens {pow10(static_cast(wide_offset))}; const auto wide_sig {static_cast(lhs_c.sig) * wide_tens}; - const auto wide_q {wide_sig / static_cast(rhs_c.sig)}; - const auto wide_exp {(lhs_c.exp - static_cast(wide_offset)) - rhs_c.exp}; - return DecimalType{wide_q, wide_exp, sign}; + const auto wide_div {static_cast(rhs_c.sig)}; + const auto wide_q {wide_sig / wide_div}; + // The constructor rounds by the digits it drops, thus the remainder goes in as one digit + const auto sticky {static_cast(wide_sig - wide_q * wide_div != 0U)}; + const auto wide_exp {(lhs_c.exp - static_cast(wide_offset) - 1) - rhs_c.exp}; + return DecimalType{wide_q * 10U + sticky, wide_exp, sign}; } constexpr auto ten_to_p {static_cast(pow10(static_cast(precision_v)))}; @@ -340,21 +346,17 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto d128_generic_div_impl(const T& lhs, const T& r if (BOOST_DECIMAL_UNLIKELY(!impl::div_default_rounding(lhs_c.sig))) { - const auto wide_tens {pow10(int128::uint128_t{static_cast(precision_v)})}; + // One digit past the precision for the constructor to round, then the remainder goes in + // as one more digit. Both operands have 34 digits, thus the quotient has at most 37. + const auto wide_tens {pow10(int128::uint128_t{static_cast(precision_v + 1)})}; const auto wide_sig {detail::umul256(lhs_c.sig, wide_tens)}; - auto wide_q {wide_sig / rhs_c.sig}; - auto wide_exp {lhs_c.exp - rhs_c.exp - static_cast(precision_v)}; + const auto wide_dr {impl::div_mod(wide_sig, rhs_c.sig)}; + const auto wide_exp {lhs_c.exp - rhs_c.exp - static_cast(precision_v) - 2}; - if (wide_q[3] != 0U || wide_q[2] != 0U) - { - const auto sig_dig {detail::num_digits(wide_q)}; - const auto digit_delta {sig_dig - std::numeric_limits::digits10}; - wide_q /= pow10(int128::uint128_t{static_cast(digit_delta)}); - wide_exp += digit_delta; - } - - BOOST_DECIMAL_ASSERT((wide_q[3] | wide_q[2]) == 0U); - return DecimalType{int128::uint128_t{wide_q[1], wide_q[0]}, wide_exp, sign}; + BOOST_DECIMAL_ASSERT((wide_dr.quotient[3] | wide_dr.quotient[2]) == 0U); + const int128::uint128_t wide_q {wide_dr.quotient[1], wide_dr.quotient[0]}; + const auto sticky {static_cast(wide_dr.remainder != u256{})}; + return DecimalType{wide_q * 10U + sticky, wide_exp, sign}; } constexpr auto ten_to_p {pow10(int128::uint128_t{static_cast(precision_v)})}; diff --git a/test/Jamfile b/test/Jamfile index 3aeab29fe..819ce7e6a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -103,6 +103,7 @@ run github_issue_1437.cpp ; run github_issue_1440.cpp ; run github_issue_1447.cpp ; run github_issue_1451.cpp ; +run github_issue_1453.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; diff --git a/test/github_issue_1453.cpp b/test/github_issue_1453.cpp new file mode 100644 index 000000000..13643b766 --- /dev/null +++ b/test/github_issue_1453.cpp @@ -0,0 +1,88 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// https://github.com/boostorg/decimal/issues/1453 +// +// The divide dropped the remainder of its wide quotient before the constructor rounded it, +// thus a directed mode could not move away from the truncated quotient. + +#include +#include + +using namespace boost::decimal; +using namespace boost::decimal::literals; + +// fesetround changes the mode only when the library can find a constant evaluation. +#ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + +template +void check(const rounding_mode mode, const T lhs, const T rhs, const T expected) +{ + fesetround(mode); + BOOST_TEST_EQ(lhs / rhs, expected); + BOOST_TEST_EQ(-lhs / -rhs, expected); +} + +// below and above are the neighbours of the exact quotient, and nearest is the one of the +// two which is nearer to it. An exact quotient is its own neighbour in every mode. +template +void test_quotients(const T lhs, const T rhs, const T below, const T above, const T nearest) +{ + check(rounding_mode::fe_dec_upward, lhs, rhs, above); + check(rounding_mode::fe_dec_upward, -lhs, rhs, -below); + + check(rounding_mode::fe_dec_downward, lhs, rhs, below); + check(rounding_mode::fe_dec_downward, -lhs, rhs, -above); + + check(rounding_mode::fe_dec_toward_zero, lhs, rhs, below); + check(rounding_mode::fe_dec_toward_zero, -lhs, rhs, -below); + + check(rounding_mode::fe_dec_to_nearest_from_zero, lhs, rhs, nearest); + check(rounding_mode::fe_dec_to_nearest_from_zero, -lhs, rhs, -nearest); +} + +#endif + +int main() +{ + #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + + // The last pair of each type has a quotient whose digits past the precision are zero for + // more digits than the wide quotient holds, thus only the remainder can select the direction. + test_quotients(1_DF, 4_DF, 2.5e-01_DF, 2.5e-01_DF, 2.5e-01_DF); + test_quotients(1_DF, 3_DF, 3.333333e-01_DF, 3.333334e-01_DF, 3.333333e-01_DF); + test_quotients(2_DF, 3_DF, 6.666666e-01_DF, 6.666667e-01_DF, 6.666667e-01_DF); + test_quotients(1_DF, 1.000001_DF, 9.99999e-01_DF, 9.999991e-01_DF, 9.99999e-01_DF); + + test_quotients(1_DFF, 4_DFF, 2.5e-01_DFF, 2.5e-01_DFF, 2.5e-01_DFF); + test_quotients(1_DFF, 3_DFF, 3.333333e-01_DFF, 3.333334e-01_DFF, 3.333333e-01_DFF); + test_quotients(2_DFF, 3_DFF, 6.666666e-01_DFF, 6.666667e-01_DFF, 6.666667e-01_DFF); + test_quotients(1_DFF, 1.000001_DFF, 9.99999e-01_DFF, 9.999991e-01_DFF, 9.99999e-01_DFF); + + test_quotients(1_DD, 4_DD, 2.5e-01_DD, 2.5e-01_DD, 2.5e-01_DD); + test_quotients(1_DD, 3_DD, 3.333333333333333e-01_DD, 3.333333333333334e-01_DD, 3.333333333333333e-01_DD); + test_quotients(2_DD, 3_DD, 6.666666666666666e-01_DD, 6.666666666666667e-01_DD, 6.666666666666667e-01_DD); + test_quotients(1_DD, 1.000000000000001_DD, 9.99999999999999e-01_DD, 9.999999999999991e-01_DD, 9.99999999999999e-01_DD); + + test_quotients(1_DDF, 4_DDF, 2.5e-01_DDF, 2.5e-01_DDF, 2.5e-01_DDF); + test_quotients(1_DDF, 3_DDF, 3.333333333333333e-01_DDF, 3.333333333333334e-01_DDF, 3.333333333333333e-01_DDF); + test_quotients(2_DDF, 3_DDF, 6.666666666666666e-01_DDF, 6.666666666666667e-01_DDF, 6.666666666666667e-01_DDF); + test_quotients(1_DDF, 1.000000000000001_DDF, 9.99999999999999e-01_DDF, 9.999999999999991e-01_DDF, 9.99999999999999e-01_DDF); + + test_quotients(1_DL, 4_DL, 2.5e-01_DL, 2.5e-01_DL, 2.5e-01_DL); + test_quotients(1_DL, 3_DL, 3.333333333333333333333333333333333e-01_DL, 3.333333333333333333333333333333334e-01_DL, 3.333333333333333333333333333333333e-01_DL); + test_quotients(2_DL, 3_DL, 6.666666666666666666666666666666666e-01_DL, 6.666666666666666666666666666666667e-01_DL, 6.666666666666666666666666666666667e-01_DL); + test_quotients(1_DL, 1.000000000000000000000000000000001_DL, 9.99999999999999999999999999999999e-01_DL, 9.999999999999999999999999999999991e-01_DL, 9.99999999999999999999999999999999e-01_DL); + + test_quotients(1_DLF, 4_DLF, 2.5e-01_DLF, 2.5e-01_DLF, 2.5e-01_DLF); + test_quotients(1_DLF, 3_DLF, 3.333333333333333333333333333333333e-01_DLF, 3.333333333333333333333333333333334e-01_DLF, 3.333333333333333333333333333333333e-01_DLF); + test_quotients(2_DLF, 3_DLF, 6.666666666666666666666666666666666e-01_DLF, 6.666666666666666666666666666666667e-01_DLF, 6.666666666666666666666666666666667e-01_DLF); + test_quotients(1_DLF, 1.000000000000000000000000000000001_DLF, 9.99999999999999999999999999999999e-01_DLF, 9.999999999999999999999999999999991e-01_DLF, 9.99999999999999999999999999999999e-01_DLF); + + fesetround(rounding_mode::fe_dec_to_nearest); + + #endif + + return boost::report_errors(); +}