Skip to content

Commit a93e2ae

Browse files
authored
Merge pull request #405 from cppalliance/397
Add ipow function
2 parents ae034fd + ad18eb3 commit a93e2ae

4 files changed

Lines changed: 287 additions & 1 deletion

File tree

include/boost/int128/detail/int128_imp.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2024,7 +2024,7 @@ BOOST_INT128_HOST_DEVICE BOOST_INT128_FORCE_INLINE constexpr int128_t library_mu
20242024
const auto c {rhs.low >> 32U};
20252025
const auto d {rhs.low & UINT32_MAX};
20262026

2027-
int128_t result { static_cast<std::int64_t>(static_cast<std::uint64_t>(lhs.high) * rhs.low + static_cast<std::uint64_t>(lhs.low) * rhs.high + a * c), b * d };
2027+
int128_t result { static_cast<std::int64_t>(static_cast<std::uint64_t>(lhs.high) * rhs.low + lhs.low * static_cast<std::uint64_t>(rhs.high) + a * c), b * d };
20282028
result += signed_shift_left_32(a * d) + signed_shift_left_32(b * c);
20292029

20302030
return result;

include/boost/int128/utilities.hpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,53 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t powm(const int12
171171
return static_cast<int128_t>(powm(ub, static_cast<uint128_t>(exp), um));
172172
}
173173

174+
// Computes base^exp using exponentiation by squaring. The result is reduced
175+
// modulo 2^128, mirroring the wrap-around behavior of operator*.
176+
BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t ipow(uint128_t base, std::uint64_t exp) noexcept
177+
{
178+
uint128_t result {1};
179+
180+
while (exp != 0U)
181+
{
182+
if (static_cast<bool>(exp & 1U))
183+
{
184+
result *= base;
185+
}
186+
187+
exp >>= 1;
188+
189+
if (exp != 0U)
190+
{
191+
base *= base;
192+
}
193+
}
194+
195+
return result;
196+
}
197+
198+
// Signed overload. Wraps modulo 2^128 on overflow, matching operator*.
199+
BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t ipow(int128_t base, std::uint64_t exp) noexcept
200+
{
201+
int128_t result {1};
202+
203+
while (exp != 0U)
204+
{
205+
if (static_cast<bool>(exp & 1U))
206+
{
207+
result *= base;
208+
}
209+
210+
exp >>= 1;
211+
212+
if (exp != 0U)
213+
{
214+
base *= base;
215+
}
216+
}
217+
218+
return result;
219+
}
220+
174221
} // namespace int128
175222
} // namespace boost
176223

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ run test_x64_msvc_div.cpp ;
8080
run test_gcd_lcm.cpp ;
8181
run test_midpoint.cpp ;
8282
run test_powm.cpp ;
83+
run test_ipow.cpp ;
8384

8485
run test_format.cpp ;
8586
run test_fmt_format.cpp ;

test/test_ipow.cpp

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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+
#if defined(__GNUC__) && __GNUC__ == 7 && defined(__i386__)
6+
7+
// 32-bit GCC-7 fails with: "error: constexpr loop iteration count exceeds limit of 262144"
8+
9+
int main() { return 0; }
10+
11+
#else
12+
13+
#ifndef BOOST_INT128_BUILD_MODULE
14+
15+
#include <boost/int128.hpp>
16+
17+
#else
18+
19+
import boost.int128;
20+
21+
#endif
22+
23+
#include <boost/core/lightweight_test.hpp>
24+
#include <cstdint>
25+
#include <limits>
26+
27+
using namespace boost::int128;
28+
29+
namespace {
30+
31+
// Naive reference implementation used to cross-check the squaring loop.
32+
template <typename T>
33+
constexpr T ipow_ref(T base, std::uint64_t exp) noexcept
34+
{
35+
T result {1};
36+
37+
for (std::uint64_t i {0}; i < exp; ++i)
38+
{
39+
result *= base;
40+
}
41+
42+
return result;
43+
}
44+
45+
} // namespace
46+
47+
void test_uint128_ipow_basic()
48+
{
49+
BOOST_TEST_EQ(ipow(uint128_t{0}, 0U), uint128_t{1});
50+
BOOST_TEST_EQ(ipow(uint128_t{1}, 0U), uint128_t{1});
51+
BOOST_TEST_EQ(ipow(uint128_t{42}, 0U), uint128_t{1});
52+
BOOST_TEST_EQ(ipow(uint128_t{0}, 1U), uint128_t{0});
53+
BOOST_TEST_EQ(ipow(uint128_t{0}, 5U), uint128_t{0});
54+
BOOST_TEST_EQ(ipow(uint128_t{1}, 1000U), uint128_t{1});
55+
BOOST_TEST_EQ(ipow(uint128_t{42}, 1U), uint128_t{42});
56+
57+
BOOST_TEST_EQ(ipow(uint128_t{2}, 0U), uint128_t{1});
58+
BOOST_TEST_EQ(ipow(uint128_t{2}, 1U), uint128_t{2});
59+
BOOST_TEST_EQ(ipow(uint128_t{2}, 2U), uint128_t{4});
60+
BOOST_TEST_EQ(ipow(uint128_t{2}, 10U), uint128_t{1024});
61+
BOOST_TEST_EQ(ipow(uint128_t{3}, 5U), uint128_t{243});
62+
BOOST_TEST_EQ(ipow(uint128_t{10}, 9U), uint128_t{UINT64_C(1000000000)});
63+
BOOST_TEST_EQ(ipow(uint128_t{10}, 18U), uint128_t{UINT64_C(1000000000000000000)});
64+
}
65+
66+
void test_uint128_ipow_power_of_two()
67+
{
68+
// 2^k fills bit k, so we can hit every bit position up to 127.
69+
for (std::uint64_t k {0}; k < 64; ++k)
70+
{
71+
const uint128_t expected {static_cast<std::uint64_t>(1) << k};
72+
BOOST_TEST_EQ(ipow(uint128_t{2}, k), expected);
73+
}
74+
75+
for (std::uint64_t k {64}; k < 128; ++k)
76+
{
77+
const uint128_t expected {static_cast<std::uint64_t>(1) << (k - 64), 0U};
78+
BOOST_TEST_EQ(ipow(uint128_t{2}, k), expected);
79+
}
80+
81+
// 2^128 wraps to 0 in uint128 arithmetic.
82+
BOOST_TEST_EQ(ipow(uint128_t{2}, 128U), uint128_t{0});
83+
BOOST_TEST_EQ(ipow(uint128_t{2}, 200U), uint128_t{0});
84+
}
85+
86+
void test_uint128_ipow_large()
87+
{
88+
// 10^38 is the largest power of 10 that fits in 128 bits.
89+
// 10^38 = 100000000000000000000000000000000000000.
90+
const uint128_t ten_pow_38 {UINT64_C(0x4B3B4CA85A86C47A), UINT64_C(0x098A224000000000)};
91+
BOOST_TEST_EQ(ipow(uint128_t{10}, 38U), ten_pow_38);
92+
93+
// Cross-check a range of bases against the naive reference for small
94+
// exponents where the result is hand-verifiable through repeated mul.
95+
for (std::uint64_t base {2}; base < 8; ++base)
96+
{
97+
for (std::uint64_t exp {0}; exp < 12; ++exp)
98+
{
99+
BOOST_TEST_EQ(ipow(uint128_t{base}, exp), ipow_ref(uint128_t{base}, exp));
100+
}
101+
}
102+
}
103+
104+
void test_uint128_ipow_wrap()
105+
{
106+
// Squaring 2^64 yields 2^128 which wraps to 0.
107+
const uint128_t two_pow_64 {1U, 0U};
108+
BOOST_TEST_EQ(ipow(two_pow_64, 2U), uint128_t{0});
109+
110+
// (2^64 - 1)^2 mod 2^128 = 2^128 - 2^65 + 1, which has a known bit pattern.
111+
const uint128_t u64_max {(std::numeric_limits<std::uint64_t>::max)()};
112+
const uint128_t expected {UINT64_C(0xFFFFFFFFFFFFFFFE), 1U};
113+
BOOST_TEST_EQ(ipow(u64_max, 2U), expected);
114+
115+
// Anything to a sufficiently large power eventually wraps to 0 if the base
116+
// shares a factor of 2 with 2^128.
117+
BOOST_TEST_EQ(ipow(uint128_t{4}, 64U), uint128_t{0});
118+
BOOST_TEST_EQ(ipow(uint128_t{6}, 200U), uint128_t{0});
119+
}
120+
121+
void test_uint128_ipow_identities()
122+
{
123+
// a^(b+c) == a^b * a^c (under wrap modulo 2^128).
124+
const uint128_t a {UINT64_C(0xDEADBEEF)};
125+
BOOST_TEST_EQ(ipow(a, 7U), ipow(a, 3U) * ipow(a, 4U));
126+
BOOST_TEST_EQ(ipow(a, 20U), ipow(a, 13U) * ipow(a, 7U));
127+
128+
// (a*b)^e == a^e * b^e.
129+
const uint128_t aa {7};
130+
const uint128_t bb {11};
131+
BOOST_TEST_EQ(ipow(aa * bb, 6U), ipow(aa, 6U) * ipow(bb, 6U));
132+
133+
// (a^b)^c == a^(b*c).
134+
BOOST_TEST_EQ(ipow(ipow(uint128_t{3}, 4U), 5U), ipow(uint128_t{3}, 4U * 5U));
135+
}
136+
137+
void test_int128_ipow_basic()
138+
{
139+
BOOST_TEST_EQ(ipow(int128_t{0}, 0U), int128_t{1});
140+
BOOST_TEST_EQ(ipow(int128_t{1}, 0U), int128_t{1});
141+
BOOST_TEST_EQ(ipow(int128_t{-1}, 0U), int128_t{1});
142+
BOOST_TEST_EQ(ipow(int128_t{0}, 5U), int128_t{0});
143+
BOOST_TEST_EQ(ipow(int128_t{42}, 1U), int128_t{42});
144+
145+
BOOST_TEST_EQ(ipow(int128_t{2}, 10U), int128_t{1024});
146+
BOOST_TEST_EQ(ipow(int128_t{3}, 5U), int128_t{243});
147+
BOOST_TEST_EQ(ipow(int128_t{10}, 18U), int128_t{INT64_C(1000000000000000000)});
148+
}
149+
150+
void test_int128_ipow_negative_base()
151+
{
152+
// Even exponents are non-negative, odd exponents preserve the sign.
153+
BOOST_TEST_EQ(ipow(int128_t{-2}, 0U), int128_t{1});
154+
BOOST_TEST_EQ(ipow(int128_t{-2}, 1U), int128_t{-2});
155+
BOOST_TEST_EQ(ipow(int128_t{-2}, 2U), int128_t{4});
156+
BOOST_TEST_EQ(ipow(int128_t{-2}, 3U), int128_t{-8});
157+
BOOST_TEST_EQ(ipow(int128_t{-2}, 10U), int128_t{1024});
158+
BOOST_TEST_EQ(ipow(int128_t{-3}, 5U), int128_t{-243});
159+
160+
BOOST_TEST_EQ(ipow(int128_t{-1}, 100U), int128_t{1});
161+
BOOST_TEST_EQ(ipow(int128_t{-1}, 101U), int128_t{-1});
162+
163+
BOOST_TEST_EQ(ipow(int128_t{-10}, 18U), int128_t{INT64_C(1000000000000000000)});
164+
BOOST_TEST_EQ(ipow(int128_t{-10}, 17U), int128_t{INT64_C(-100000000000000000)});
165+
}
166+
167+
void test_int128_ipow_large()
168+
{
169+
// 10^38 still fits in int128_t (signed max is roughly 1.7e38).
170+
const int128_t ten_pow_38 {static_cast<int128_t>(uint128_t{UINT64_C(0x4B3B4CA85A86C47A), UINT64_C(0x098A224000000000)})};
171+
BOOST_TEST_EQ(ipow(int128_t{10}, 38U), ten_pow_38);
172+
BOOST_TEST_EQ(ipow(int128_t{-10}, 38U), ten_pow_38);
173+
174+
// Cross-check small bases against the naive reference.
175+
for (std::int64_t base {-7}; base < 8; ++base)
176+
{
177+
for (std::uint64_t exp {0}; exp < 12; ++exp)
178+
{
179+
BOOST_TEST_EQ(ipow(int128_t{base}, exp), ipow_ref(int128_t{base}, exp));
180+
}
181+
}
182+
}
183+
184+
void test_int128_ipow_identities()
185+
{
186+
const int128_t a {12345};
187+
BOOST_TEST_EQ(ipow(a, 7U), ipow(a, 3U) * ipow(a, 4U));
188+
BOOST_TEST_EQ(ipow(ipow(int128_t{3}, 4U), 5U), ipow(int128_t{3}, 4U * 5U));
189+
190+
// Sign behaves multiplicatively.
191+
BOOST_TEST_EQ(ipow(int128_t{-7}, 3U) * ipow(int128_t{-7}, 4U), ipow(int128_t{-7}, 7U));
192+
}
193+
194+
#ifdef _MSC_VER
195+
# pragma warning(push)
196+
# pragma warning(disable : 4307) // integral constant overflow
197+
# pragma warning(disable : 4308) // negative integral constant converted to unsigned type
198+
#endif
199+
200+
void test_constexpr_ipow()
201+
{
202+
constexpr uint128_t r1 {ipow(uint128_t{2}, 10U)};
203+
static_assert(r1 == uint128_t{1024}, "ipow constexpr uint128 small case");
204+
205+
constexpr uint128_t r2 {ipow(uint128_t{10}, 18U)};
206+
static_assert(r2 == uint128_t{UINT64_C(1000000000000000000)}, "ipow constexpr uint128 18 digits");
207+
208+
constexpr int128_t r3 {ipow(int128_t{-3}, 5U)};
209+
static_assert(r3 == int128_t{-243}, "ipow constexpr int128 negative base odd exp");
210+
211+
constexpr int128_t r4 {ipow(int128_t{-3}, 4U)};
212+
static_assert(r4 == int128_t{81}, "ipow constexpr int128 negative base even exp");
213+
214+
constexpr uint128_t r5 {ipow(uint128_t{2}, 128U)};
215+
static_assert(r5 == uint128_t{0}, "ipow constexpr uint128 wrap to zero");
216+
}
217+
218+
#ifdef _MSC_VER
219+
# pragma warning(pop)
220+
#endif
221+
222+
int main()
223+
{
224+
test_uint128_ipow_basic();
225+
test_uint128_ipow_power_of_two();
226+
test_uint128_ipow_large();
227+
test_uint128_ipow_wrap();
228+
test_uint128_ipow_identities();
229+
test_int128_ipow_basic();
230+
test_int128_ipow_negative_base();
231+
test_int128_ipow_large();
232+
test_int128_ipow_identities();
233+
test_constexpr_ipow();
234+
235+
return boost::report_errors();
236+
}
237+
238+
#endif

0 commit comments

Comments
 (0)