@@ -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+
199237macro_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