Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions include/boost/decimal/detail/div_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,12 @@ BOOST_DECIMAL_FORCE_INLINE BOOST_DECIMAL_CUDA_CONSTEXPR auto generic_div_impl(co
constexpr auto wide_offset {std::numeric_limits<std::uint64_t>::digits10 - precision_v<DecimalType>};
constexpr auto wide_tens {pow10(static_cast<std::uint64_t>(wide_offset))};
const auto wide_sig {static_cast<std::uint64_t>(lhs_c.sig) * wide_tens};
const auto wide_q {wide_sig / static_cast<std::uint64_t>(rhs_c.sig)};
const auto wide_exp {(lhs_c.exp - static_cast<int>(wide_offset)) - rhs_c.exp};
return DecimalType{wide_q, wide_exp, sign};
const auto wide_div {static_cast<std::uint64_t>(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<unsigned>(wide_sig - wide_q * wide_div != 0U)};
const auto wide_exp {(lhs_c.exp - static_cast<int>(wide_offset) - 1) - rhs_c.exp};
return DecimalType{wide_q * 10U + sticky, wide_exp, sign};
}

constexpr auto ten_to_p {pow10(static_cast<std::uint64_t>(precision_v<DecimalType>))};
Expand Down Expand Up @@ -301,9 +304,12 @@ BOOST_DECIMAL_FORCE_INLINE BOOST_DECIMAL_CUDA_CONSTEXPR auto d64_generic_div_imp
constexpr auto wide_offset {std::numeric_limits<unsigned_int128_type>::digits10 - precision_v<DecimalType>};
const auto wide_tens {pow10(static_cast<unsigned_int128_type>(wide_offset))};
const auto wide_sig {static_cast<unsigned_int128_type>(lhs_c.sig) * wide_tens};
const auto wide_q {wide_sig / static_cast<unsigned_int128_type>(rhs_c.sig)};
const auto wide_exp {(lhs_c.exp - static_cast<int>(wide_offset)) - rhs_c.exp};
return DecimalType{wide_q, wide_exp, sign};
const auto wide_div {static_cast<unsigned_int128_type>(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<unsigned>(wide_sig - wide_q * wide_div != 0U)};
const auto wide_exp {(lhs_c.exp - static_cast<int>(wide_offset) - 1) - rhs_c.exp};
return DecimalType{wide_q * 10U + sticky, wide_exp, sign};
}

constexpr auto ten_to_p {static_cast<unsigned_int128_type>(pow10(static_cast<std::uint64_t>(precision_v<DecimalType>)))};
Expand Down Expand Up @@ -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<std::uint64_t>(precision_v<DecimalType>)})};
// 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<std::uint64_t>(precision_v<DecimalType> + 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<int>(precision_v<DecimalType>)};
const auto wide_dr {impl::div_mod(wide_sig, rhs_c.sig)};
const auto wide_exp {lhs_c.exp - rhs_c.exp - static_cast<int>(precision_v<DecimalType>) - 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<int128::uint128_t>::digits10};
wide_q /= pow10(int128::uint128_t{static_cast<std::uint64_t>(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<unsigned>(wide_dr.remainder != u256{})};
return DecimalType{wide_q * 10U + sticky, wide_exp, sign};
}

constexpr auto ten_to_p {pow10(int128::uint128_t{static_cast<std::uint64_t>(precision_v<DecimalType>)})};
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down
88 changes: 88 additions & 0 deletions test/github_issue_1453.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/decimal.hpp>
#include <boost/core/lightweight_test.hpp>

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 <typename T>
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 <typename T>
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();
}
Loading