Skip to content

Commit 1377fb6

Browse files
authored
Merge pull request #80 from cuviper/karatsuba-sqrt
implement Karatsuba Square Root
2 parents 3fbe47f + 6c3bec6 commit 1377fb6

3 files changed

Lines changed: 53 additions & 9 deletions

File tree

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ categories = ["algorithms", "science", "no-std"]
88
license = "MIT OR Apache-2.0"
99
repository = "https://github.com/rust-num/num-integer"
1010
name = "num-integer"
11-
version = "0.1.46"
11+
version = "0.1.47"
1212
readme = "README.md"
1313
exclude = ["/ci/*", "/.github/*"]
1414
edition = "2018"

‎RELEASES.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# Release 0.1.47 (2025-08-11)
2+
3+
- [Implement Karatsuba Square Root for 128-bit `Roots::sqrt`][80]
4+
- Miscellaneous other improvements to docs and testing.
5+
6+
**Contributors**: @durin42, @cuviper, @mikem8891
7+
8+
[80]: https://github.com/rust-num/num-integer/pull/80
9+
110
# Release 0.1.46 (2024-02-07)
211

312
- [Upgrade to 2018 edition, **MSRV 1.31**][51]

‎src/roots.rs‎

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,44 @@ fn log2<T: PrimInt>(x: T) -> u32 {
196196
bits::<T>() - 1 - x.leading_zeros()
197197
}
198198

199+
/// 128-bit Karatsuba Square Root, using b = 2³²
200+
///
201+
/// Reference:
202+
/// Paul Zimmermann. Karatsuba Square Root. [Research Report] RR-3805, INRIA. 1999, pp.8.
203+
/// <https://inria.hal.science/inria-00072854/en/>
204+
#[inline]
205+
fn karatsuba_sqrt(n: u128) -> u128 {
206+
// Algorithm SqrtRem(n = a₃b³ + a₂b² + a₁b + a₀)
207+
// Input: 0 ≤ aᵢ < b with a₃ ≥ b/4
208+
// Output: (s,r) such that s² ≤ n = s² + r < (s+1)²
209+
debug_assert!(n.leading_zeros() < 2);
210+
let a0 = n as u32 as u128;
211+
let a1 = (n >> 32) as u32 as u128;
212+
let a23 = n >> 64;
213+
214+
// (s',r') ← SqrtRem(a₃b + a₂)
215+
let s1 = (a23 as u64).sqrt() as u128;
216+
let r1 = a23 - s1 * s1;
217+
218+
// (q,u) ← DivRem(r'b + a₁, 2s')
219+
let (q, u) = ((r1 << 32) | a1).div_rem(&(2 * s1));
220+
221+
// s ← s'b + q
222+
let mut s = (s1 << 32) + q;
223+
224+
// r ← ub + a₀ - q²
225+
// if r < 0 then
226+
// r ← r + 2s - 1
227+
// s ← s - 1
228+
//
229+
// but to avoid negatives, we compare and adjust before subtraction,
230+
// and in this case we don't care about the actual remainder.
231+
if ((u << 32) | a0) < q * q {
232+
s -= 1;
233+
}
234+
s
235+
}
236+
199237
macro_rules! unsigned_roots {
200238
($T:ident) => {
201239
impl Roots for $T {
@@ -275,17 +313,14 @@ macro_rules! unsigned_roots {
275313
fn sqrt(&self) -> Self {
276314
fn go(a: $T) -> $T {
277315
if bits::<$T>() > 64 {
278-
// 128-bit division is slow, so do a bitwise `sqrt` until it's small enough.
316+
// 128-bit division is slow in the Babylonian method,
317+
// so use 64-bit sqrt and Karatsuba if needed.
279318
return if a <= core::u64::MAX as $T {
280319
(a as u64).sqrt() as $T
281320
} else {
282-
let lo = (a >> 2u32).sqrt() << 1;
283-
let hi = lo + 1;
284-
if hi * hi <= a {
285-
hi
286-
} else {
287-
lo
288-
}
321+
let shift = a.leading_zeros() / 2;
322+
let n = a << (shift * 2);
323+
(karatsuba_sqrt(n as u128) >> shift) as $T
289324
};
290325
}
291326

0 commit comments

Comments
 (0)