Skip to content

Commit 9a8589b

Browse files
authored
Merge pull request #406 from cppalliance/sqrt
2 parents a93e2ae + c8a4cd6 commit 9a8589b

4 files changed

Lines changed: 316 additions & 1 deletion

File tree

.drone.jsonnet

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Copyright 2022, 2023 Peter Dimov
2+
# Copyright 2025 - 2026 Matt Borland
23
# Distributed under the Boost Software License, Version 1.0.
34
# https://www.boost.org/LICENSE_1_0.txt
45

5-
local library = "decimal";
6+
local library = "int128";
67

78
local triggers =
89
{

include/boost/int128/utilities.hpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,41 @@ BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t ipow(int128_t ba
218218
return result;
219219
}
220220

221+
// Integer square root: returns floor(sqrt(n)).
222+
BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr uint128_t isqrt(const uint128_t n) noexcept
223+
{
224+
if (n < 2U)
225+
{
226+
return n;
227+
}
228+
229+
// 2^ceil(bit_width(n)/2) is the smallest power of two whose square exceeds n.
230+
uint128_t x {uint128_t{1} << ((bit_width(n) + 1) / 2)};
231+
232+
while (true)
233+
{
234+
const uint128_t y {(x + n / x) >> 1};
235+
236+
if (y >= x)
237+
{
238+
return x;
239+
}
240+
241+
x = y;
242+
}
243+
}
244+
245+
// Signed overload. Negative inputs are documented to return 0.
246+
BOOST_INT128_EXPORT BOOST_INT128_HOST_DEVICE constexpr int128_t isqrt(const int128_t n) noexcept
247+
{
248+
if (BOOST_INT128_UNLIKELY(n < 0))
249+
{
250+
return int128_t{0};
251+
}
252+
253+
return static_cast<int128_t>(isqrt(static_cast<uint128_t>(n)));
254+
}
255+
221256
} // namespace int128
222257
} // namespace boost
223258

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ run test_gcd_lcm.cpp ;
8181
run test_midpoint.cpp ;
8282
run test_powm.cpp ;
8383
run test_ipow.cpp ;
84+
run test_isqrt.cpp ;
8485

8586
run test_format.cpp ;
8687
run test_fmt_format.cpp ;

test/test_isqrt.cpp

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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 bit-by-bit reference. Independent of the Newton implementation under
32+
// test, so it serves as a ground truth for cross-checking.
33+
constexpr uint128_t isqrt_ref(uint128_t n) noexcept
34+
{
35+
if (n < 2U)
36+
{
37+
return n;
38+
}
39+
40+
uint128_t res {0};
41+
uint128_t bit {uint128_t{1} << 126};
42+
43+
while (bit > n)
44+
{
45+
bit >>= 2;
46+
}
47+
48+
while (bit != 0U)
49+
{
50+
if (n >= res + bit)
51+
{
52+
n -= res + bit;
53+
res = (res >> 1) + bit;
54+
}
55+
else
56+
{
57+
res >>= 1;
58+
}
59+
60+
bit >>= 2;
61+
}
62+
63+
return res;
64+
}
65+
66+
// Verify the defining invariant: r == isqrt(n) iff r*r <= n < (r+1)^2.
67+
// Computed with overflow-safe comparisons so callers can pass values near the
68+
// uint128 upper bound.
69+
void check_invariant(const uint128_t n)
70+
{
71+
const uint128_t r {isqrt(n)};
72+
73+
BOOST_TEST(r * r <= n);
74+
75+
const uint128_t r_plus_1 {r + 1U};
76+
77+
// (r+1)^2 may overflow uint128 when r is close to 2^64; in that case the
78+
// invariant n < (r+1)^2 is trivially satisfied since n fits in 128 bits.
79+
if (r_plus_1 != 0U && r_plus_1 <= ((std::numeric_limits<uint128_t>::max)() / r_plus_1))
80+
{
81+
BOOST_TEST(n < r_plus_1 * r_plus_1);
82+
}
83+
}
84+
85+
} // namespace
86+
87+
void test_uint128_isqrt_small()
88+
{
89+
BOOST_TEST_EQ(isqrt(uint128_t{0}), uint128_t{0});
90+
BOOST_TEST_EQ(isqrt(uint128_t{1}), uint128_t{1});
91+
BOOST_TEST_EQ(isqrt(uint128_t{2}), uint128_t{1});
92+
BOOST_TEST_EQ(isqrt(uint128_t{3}), uint128_t{1});
93+
BOOST_TEST_EQ(isqrt(uint128_t{4}), uint128_t{2});
94+
BOOST_TEST_EQ(isqrt(uint128_t{5}), uint128_t{2});
95+
BOOST_TEST_EQ(isqrt(uint128_t{8}), uint128_t{2});
96+
BOOST_TEST_EQ(isqrt(uint128_t{9}), uint128_t{3});
97+
BOOST_TEST_EQ(isqrt(uint128_t{10}), uint128_t{3});
98+
BOOST_TEST_EQ(isqrt(uint128_t{15}), uint128_t{3});
99+
BOOST_TEST_EQ(isqrt(uint128_t{16}), uint128_t{4});
100+
BOOST_TEST_EQ(isqrt(uint128_t{99}), uint128_t{9});
101+
BOOST_TEST_EQ(isqrt(uint128_t{100}), uint128_t{10});
102+
BOOST_TEST_EQ(isqrt(uint128_t{101}), uint128_t{10});
103+
104+
// Exhaustive cross-check against the reference for every small n.
105+
for (std::uint64_t i {0}; i < 200; ++i)
106+
{
107+
BOOST_TEST_EQ(isqrt(uint128_t{i}), isqrt_ref(uint128_t{i}));
108+
}
109+
}
110+
111+
void test_uint128_isqrt_perfect_squares()
112+
{
113+
// Squares spanning the full 64-bit range, including the largest k whose
114+
// square still fits in 128 bits (k = 2^64 - 1).
115+
for (std::uint64_t k {0}; k < 10000; ++k)
116+
{
117+
const uint128_t kk {k};
118+
BOOST_TEST_EQ(isqrt(kk * kk), kk);
119+
120+
if (k > 0)
121+
{
122+
BOOST_TEST_EQ(isqrt(kk * kk - 1U), kk - 1U);
123+
BOOST_TEST_EQ(isqrt(kk * kk + 2U * kk), kk); // (k+1)^2 - 1
124+
}
125+
}
126+
127+
// Powers of two as bases: k = 2^i for i in [0, 63] - largest exact square
128+
// is (2^63)^2 = 2^126.
129+
for (int i {0}; i < 64; ++i)
130+
{
131+
const uint128_t k {uint128_t{1} << i};
132+
BOOST_TEST_EQ(isqrt(k * k), k);
133+
}
134+
135+
// Largest representable perfect square: (2^64 - 1)^2 = 2^128 - 2^65 + 1.
136+
const uint128_t k_max {(std::numeric_limits<std::uint64_t>::max)()};
137+
BOOST_TEST_EQ(isqrt(k_max * k_max), k_max);
138+
}
139+
140+
void test_uint128_isqrt_bit_boundaries()
141+
{
142+
// 2^(2k) has integer square root 2^k.
143+
for (int k {0}; k < 64; ++k)
144+
{
145+
const uint128_t n {uint128_t{1} << (2 * k)};
146+
BOOST_TEST_EQ(isqrt(n), uint128_t{1} << k);
147+
}
148+
149+
// 2^(2k+1) has integer square root floor(2^(k+0.5)) = floor(sqrt(2) * 2^k).
150+
// Check the invariant holds rather than hard-coding the value.
151+
for (int k {0}; k < 63; ++k)
152+
{
153+
check_invariant(uint128_t{1} << (2 * k + 1));
154+
}
155+
156+
// Just below and just above bit boundaries.
157+
for (int k {2}; k < 128; ++k)
158+
{
159+
const uint128_t boundary {uint128_t{1} << k};
160+
check_invariant(boundary - 1U);
161+
check_invariant(boundary);
162+
check_invariant(boundary + 1U);
163+
}
164+
}
165+
166+
void test_uint128_isqrt_extreme()
167+
{
168+
// (uint128 max). (2^64)^2 = 2^128 wraps, so isqrt(2^128 - 1) = 2^64 - 1.
169+
const uint128_t u128_max {(std::numeric_limits<uint128_t>::max)()};
170+
const uint128_t u64_max {(std::numeric_limits<std::uint64_t>::max)()};
171+
BOOST_TEST_EQ(isqrt(u128_max), u64_max);
172+
173+
// 2^128 - 2^65 + 1 = (2^64 - 1)^2 - exact square at the very top.
174+
BOOST_TEST_EQ(isqrt(u64_max * u64_max), u64_max);
175+
176+
// One above the largest representable square: still has isqrt = 2^64 - 1.
177+
BOOST_TEST_EQ(isqrt(u64_max * u64_max + 1U), u64_max);
178+
179+
// A handful of large hand-picked values, cross-checked against the bit-by-
180+
// bit reference.
181+
const uint128_t big_a {UINT64_C(0x0123456789ABCDEF), UINT64_C(0xFEDCBA9876543210)};
182+
const uint128_t big_b {UINT64_C(0xDEADBEEFCAFEBABE), UINT64_C(0x0123456789ABCDEF)};
183+
const uint128_t big_c {UINT64_C(0x8000000000000000), 0U};
184+
185+
BOOST_TEST_EQ(isqrt(big_a), isqrt_ref(big_a));
186+
BOOST_TEST_EQ(isqrt(big_b), isqrt_ref(big_b));
187+
BOOST_TEST_EQ(isqrt(big_c), isqrt_ref(big_c));
188+
189+
check_invariant(big_a);
190+
check_invariant(big_b);
191+
check_invariant(big_c);
192+
check_invariant(u128_max);
193+
}
194+
195+
void test_int128_isqrt()
196+
{
197+
BOOST_TEST_EQ(isqrt(int128_t{0}), int128_t{0});
198+
BOOST_TEST_EQ(isqrt(int128_t{1}), int128_t{1});
199+
BOOST_TEST_EQ(isqrt(int128_t{2}), int128_t{1});
200+
BOOST_TEST_EQ(isqrt(int128_t{100}), int128_t{10});
201+
BOOST_TEST_EQ(isqrt(int128_t{144}), int128_t{12});
202+
BOOST_TEST_EQ(isqrt(int128_t{INT64_C(1000000000000000000)}), int128_t{INT64_C(1000000000)});
203+
204+
// int128 max = 2^127 - 1. floor(sqrt) = floor(2^63.5) = 6074001000.7e9
205+
// Use the unsigned implementation as the source of truth.
206+
const int128_t i128_max {(std::numeric_limits<int128_t>::max)()};
207+
BOOST_TEST_EQ(isqrt(i128_max), static_cast<int128_t>(isqrt(static_cast<uint128_t>(i128_max))));
208+
209+
// Negative inputs are documented to return 0.
210+
BOOST_TEST_EQ(isqrt(int128_t{-1}), int128_t{0});
211+
BOOST_TEST_EQ(isqrt(int128_t{-100}), int128_t{0});
212+
BOOST_TEST_EQ(isqrt((std::numeric_limits<int128_t>::min)()), int128_t{0});
213+
}
214+
215+
void test_isqrt_against_ipow()
216+
{
217+
// isqrt(ipow(k, 2)) == k for any k whose square fits.
218+
for (std::uint64_t k {0}; k < 1000; ++k)
219+
{
220+
BOOST_TEST_EQ(isqrt(ipow(uint128_t{k}, 2U)), uint128_t{k});
221+
}
222+
223+
// isqrt is monotonically non-decreasing.
224+
uint128_t prev {0};
225+
226+
for (std::uint64_t i {0}; i < 1000; ++i)
227+
{
228+
const uint128_t curr {isqrt(uint128_t{i})};
229+
BOOST_TEST(curr >= prev);
230+
prev = curr;
231+
}
232+
}
233+
234+
#ifdef _MSC_VER
235+
# pragma warning(push)
236+
# pragma warning(disable : 4307) // integral constant overflow
237+
# pragma warning(disable : 4308) // negative integral constant converted to unsigned type
238+
#endif
239+
240+
void test_constexpr_isqrt()
241+
{
242+
constexpr uint128_t r1 {isqrt(uint128_t{0})};
243+
static_assert(r1 == uint128_t{0}, "isqrt(0) constexpr");
244+
245+
constexpr uint128_t r2 {isqrt(uint128_t{1})};
246+
static_assert(r2 == uint128_t{1}, "isqrt(1) constexpr");
247+
248+
constexpr uint128_t r3 {isqrt(uint128_t{100})};
249+
static_assert(r3 == uint128_t{10}, "isqrt(100) constexpr");
250+
251+
constexpr uint128_t r4 {isqrt(uint128_t{UINT64_C(1000000000000000000)})};
252+
static_assert(r4 == uint128_t{UINT64_C(1000000000)}, "isqrt(10^18) constexpr");
253+
254+
constexpr int128_t r5 {isqrt(int128_t{-5})};
255+
static_assert(r5 == int128_t{0}, "isqrt negative constexpr");
256+
257+
constexpr int128_t r6 {isqrt(int128_t{12321})};
258+
static_assert(r6 == int128_t{111}, "isqrt(12321) constexpr");
259+
}
260+
261+
#ifdef _MSC_VER
262+
# pragma warning(pop)
263+
#endif
264+
265+
int main()
266+
{
267+
test_uint128_isqrt_small();
268+
test_uint128_isqrt_perfect_squares();
269+
test_uint128_isqrt_bit_boundaries();
270+
test_uint128_isqrt_extreme();
271+
test_int128_isqrt();
272+
test_isqrt_against_ipow();
273+
test_constexpr_isqrt();
274+
275+
return boost::report_errors();
276+
}
277+
278+
#endif

0 commit comments

Comments
 (0)