Skip to content

Commit e3df03a

Browse files
authored
Merge pull request #378 from cppalliance/377
2 parents 775ec68 + 3ffebe0 commit e3df03a

File tree

4 files changed

+171
-15
lines changed

4 files changed

+171
-15
lines changed

include/boost/int128/detail/common_div.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr void half_word_div(
5656
template <typename T>
5757
BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr void half_word_div(const T& lhs, const std::uint32_t rhs, T& quotient) noexcept
5858
{
59+
using high_word_type = decltype(T{}.high);
60+
5961
BOOST_INT128_ASSUME(rhs != 0); // LCOV_EXCL_LINE
6062

61-
quotient.high = lhs.high / rhs;
63+
quotient.high = static_cast<high_word_type>(static_cast<std::uint64_t>(lhs.high) / rhs);
6264
auto remainder {((static_cast<std::uint64_t>(lhs.high) % rhs) << 32) | (lhs.low >> 32)};
6365
quotient.low = (remainder / rhs) << 32;
6466
remainder = ((remainder % rhs) << 32) | (lhs.low & UINT32_MAX);

include/boost/int128/detail/int128_imp.hpp

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator+(const
317317

318318
BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator-(const int128_t value) noexcept
319319
{
320-
return (value.low == 0) ? int128_t{-value.high, 0} :
320+
return (value.low == 0) ? int128_t{static_cast<std::int64_t>(0ULL - static_cast<std::uint64_t>(value.high)), 0} :
321321
int128_t{~value.high, ~value.low + 1};
322322
}
323323

@@ -2058,7 +2058,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const
20582058
return 0;
20592059
}
20602060

2061-
return lhs << rhs.low;
2061+
return lhs >> rhs.low;
20622062
}
20632063

20642064
#ifdef BOOST_INT128_HAS_INT128
@@ -2072,7 +2072,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr detail::builtin_u128 oper
20722072
return 0;
20732073
}
20742074

2075-
return lhs << rhs.low;
2075+
return lhs >> rhs.low;
20762076
}
20772077

20782078
BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr detail::builtin_i128 operator>>(const detail::builtin_i128 lhs, const int128_t rhs) noexcept
@@ -2084,7 +2084,7 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr detail::builtin_i128 oper
20842084
return 0;
20852085
}
20862086

2087-
return lhs << rhs.low;
2087+
return lhs >> rhs.low;
20882088
}
20892089

20902090
#endif
@@ -2881,18 +2881,18 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const
28812881
return {0, 0};
28822882
}
28832883

2884+
constexpr int128_t min_val {INT64_MIN, 0};
28842885
const auto abs_lhs {abs(lhs)};
28852886
const auto abs_rhs {abs(rhs)};
28862887

2887-
if (abs_lhs < abs_rhs)
2888+
if (lhs != min_val && abs_lhs < abs_rhs)
28882889
{
28892890
return {0,0};
28902891
}
28912892
#if defined(BOOST_INT128_HAS_INT128)
2892-
else
2893-
{
2894-
return static_cast<int128_t>(static_cast<detail::builtin_i128>(lhs) / static_cast<detail::builtin_i128>(rhs));
2895-
}
2893+
2894+
return static_cast<int128_t>(static_cast<detail::builtin_i128>(lhs) / static_cast<detail::builtin_i128>(rhs));
2895+
28962896
#else
28972897

28982898
int128_t quotient {};
@@ -2964,7 +2964,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const UnsignedInteger lhs,
29642964
{
29652965
auto abs_rhs {abs(rhs)};
29662966
const auto res {static_cast<std::uint64_t>(lhs) / abs_rhs.low};
2967-
return int128_t{rhs.high, res};
2967+
const int128_t result {0, res};
2968+
return rhs < 0 ? -result : result;
29682969
}
29692970

29702971
#else
@@ -2989,11 +2990,12 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator/(const int128_t lhs, const
29892990

29902991
int128_t quotient {};
29912992

2993+
constexpr int128_t min_val {INT64_MIN, 0};
29922994
const auto negative_res {static_cast<bool>((lhs.high < 0) ^ (rhs < 0))};
29932995
const auto abs_rhs {rhs < 0 ? -rhs : rhs};
29942996
const auto abs_lhs {abs(lhs)};
29952997

2996-
if (abs_lhs < abs_rhs)
2998+
if (lhs != min_val && abs_lhs < abs_rhs)
29972999
{
29983000
return {0, 0};
29993001
}
@@ -3214,9 +3216,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator%(const UnsignedInteger lhs,
32143216
return lhs;
32153217
}
32163218

3217-
const int128_t remainder {0, static_cast<eval_type>(lhs) % rhs.low};
3219+
const int128_t remainder {0, static_cast<eval_type>(lhs) % abs_rhs.low};
32183220

3219-
return rhs < 0 ? -remainder : remainder;
3221+
return remainder;
32203222

32213223
#else
32223224

@@ -3247,10 +3249,11 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator%(const int128_t lhs, const
32473249
return {0, 0};
32483250
}
32493251

3252+
constexpr int128_t min_val {INT64_MIN, 0};
32503253
const auto abs_lhs {abs(lhs)};
32513254
const auto abs_rhs {abs(rhs)};
32523255

3253-
if (abs_rhs > abs_lhs)
3256+
if (lhs != min_val && rhs != min_val && abs_rhs > abs_lhs)
32543257
{
32553258
return lhs;
32563259
}

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ run github_issue_207.cpp ;
112112
run github_issue_210.cpp ;
113113
run github_issue_221.cpp ;
114114
run github_issue_272.cpp ;
115+
run github_issue_377.cpp ;
115116

116117
# Compilation of individual headers
117118
compile compile_tests/int128_master_header_compile.cpp ;

test/github_issue_377.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright 2026 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
//
5+
// See: https://github.com/cppalliance/int128/issues/377
6+
7+
#define BOOST_INT128_ALLOW_SIGN_CONVERSION
8+
#include <boost/int128.hpp>
9+
#include <boost/core/lightweight_test.hpp>
10+
#include <limits>
11+
#include <cstdint>
12+
13+
using namespace boost::int128;
14+
15+
template <typename T>
16+
void test_div_by_one()
17+
{
18+
constexpr auto min_val {std::numeric_limits<int128_t>::min()};
19+
BOOST_TEST_EQ(min_val, min_val / T{1});
20+
}
21+
22+
template <typename T>
23+
void test_other_vals()
24+
{
25+
constexpr auto min_val {std::numeric_limits<int128_t>::min()};
26+
const auto min_div_2 {BOOST_INT128_INT128_C(-85070591730234615865843651857942052864)};
27+
const auto min_div_4 {BOOST_INT128_INT128_C(-42535295865117307932921825928971026432)};
28+
const auto min_div_16 {BOOST_INT128_INT128_C(-10633823966279326983230456482242756608)};
29+
30+
BOOST_TEST_EQ(min_div_2, min_val / T{2});
31+
BOOST_TEST_EQ(min_div_4, min_val / T{4});
32+
BOOST_TEST_EQ(min_div_16, min_val / T{16});
33+
}
34+
35+
// Bug 1: operator>>(int128_t, int128_t) was calling << instead of >>
36+
void test_right_shift_int128_amount()
37+
{
38+
const auto val {int128_t(0, 0xFF00)};
39+
const auto shift_4 {int128_t(0, 4)};
40+
41+
// Right-shift with int128_t shift amount must match integer shift
42+
BOOST_TEST_EQ(val >> shift_4, val >> 4);
43+
44+
const auto expected_ff0 {int128_t(0, 0xFF0)};
45+
BOOST_TEST_EQ(val >> shift_4, expected_ff0);
46+
47+
// Test >>= with int128_t rhs
48+
auto val2 {val};
49+
val2 >>= shift_4;
50+
BOOST_TEST_EQ(val2, expected_ff0);
51+
52+
// Cross-word shift
53+
const auto big_val {int128_t(0x1234, 0)};
54+
const auto shift_64 {int128_t(0, 64)};
55+
const auto expected_1234 {int128_t(0, 0x1234)};
56+
BOOST_TEST_EQ(big_val >> shift_64, expected_1234);
57+
58+
// Arithmetic right shift preserves sign for negative values
59+
constexpr auto min_val {std::numeric_limits<int128_t>::min()};
60+
const auto shift_1 {int128_t(0, 1)};
61+
BOOST_TEST_EQ(min_val >> shift_1, min_val >> 1);
62+
BOOST_TEST((min_val >> shift_1) < 0);
63+
}
64+
65+
// Bug 2: UnsignedInteger / int128_t returned {rhs.high, res} instead of proper sign handling
66+
void test_unsigned_div_negative_int128()
67+
{
68+
const std::uint64_t lhs {10};
69+
const auto neg3 {-int128_t(0, 3)};
70+
const auto pos3 {int128_t(0, 3)};
71+
const auto expected_neg3 {-int128_t(0, 3)};
72+
const auto expected_pos3 {int128_t(0, 3)};
73+
74+
// 10 / -3 = -3
75+
BOOST_TEST_EQ(lhs / neg3, expected_neg3);
76+
77+
// 10 / 3 = 3
78+
BOOST_TEST_EQ(lhs / pos3, expected_pos3);
79+
80+
// 7 / -1 = -7
81+
const std::uint64_t seven {7};
82+
const auto neg1 {-int128_t(0, 1)};
83+
const auto expected_neg7 {-int128_t(0, 7)};
84+
BOOST_TEST_EQ(seven / neg1, expected_neg7);
85+
}
86+
87+
// Bug 3: UnsignedInteger % int128_t used rhs.low instead of abs_rhs.low
88+
// and applied wrong sign to remainder
89+
void test_unsigned_mod_negative_int128()
90+
{
91+
const std::uint64_t lhs {10};
92+
const auto neg3 {-int128_t(0, 3)};
93+
const auto pos3 {int128_t(0, 3)};
94+
const auto expected_1 {int128_t(0, 1)};
95+
96+
// 10 % -3 = 1 (remainder has sign of dividend, which is unsigned/positive)
97+
BOOST_TEST_EQ(lhs % neg3, expected_1);
98+
99+
// 10 % 3 = 1
100+
BOOST_TEST_EQ(lhs % pos3, expected_1);
101+
102+
// 12 % -5 = 2
103+
const std::uint64_t twelve {12};
104+
const auto neg5 {-int128_t(0, 5)};
105+
const auto expected_2 {int128_t(0, 2)};
106+
BOOST_TEST_EQ(twelve % neg5, expected_2);
107+
}
108+
109+
// Bug 4: operator%(int128_t, int128_t) early return was wrong when lhs = INT128_MIN
110+
// because abs(INT128_MIN) overflows back to INT128_MIN
111+
void test_min_val_modulo()
112+
{
113+
constexpr auto min_val {std::numeric_limits<int128_t>::min()};
114+
const auto zero {int128_t(0, 0)};
115+
116+
// INT128_MIN % 1 = 0
117+
const auto one {int128_t(0, 1)};
118+
BOOST_TEST_EQ(min_val % one, zero);
119+
120+
// INT128_MIN % 2 = 0 (2^127 is even)
121+
const auto two {int128_t(0, 2)};
122+
BOOST_TEST_EQ(min_val % two, zero);
123+
124+
// INT128_MIN % 3 = -2
125+
// -170141183460469231731687303715884105728 = -56713727820156410577229101238628035242 * 3 + (-2)
126+
const auto three {int128_t(0, 3)};
127+
const auto expected_neg2 {BOOST_INT128_INT128_C(-2)};
128+
BOOST_TEST_EQ(min_val % three, expected_neg2);
129+
130+
// INT128_MIN % INT128_MIN = 0
131+
BOOST_TEST_EQ(min_val % min_val, zero);
132+
}
133+
134+
int main()
135+
{
136+
test_div_by_one<std::int32_t>();
137+
test_div_by_one<std::int64_t>();
138+
test_div_by_one<int128_t>();
139+
140+
test_other_vals<std::int32_t>();
141+
test_other_vals<std::int64_t>();
142+
test_other_vals<int128_t>();
143+
144+
test_right_shift_int128_amount();
145+
test_unsigned_div_negative_int128();
146+
test_unsigned_mod_negative_int128();
147+
test_min_val_modulo();
148+
149+
return boost::report_errors();
150+
}

0 commit comments

Comments
 (0)