Skip to content

Commit 5de2480

Browse files
committed
stdlib: Add isqrt (integer square root) to core::num
Returns floor(sqrt(x)) for u256, matching Vyper's isqrt builtin and OpenZeppelin's Math.sqrt: seed with 1 << (log2(x) / 2) via branchy bit-length estimate, run exactly seven Newton iterations, then round down with min(z, x / z). Deterministic gas, no input-dependent loops.
1 parent d10352c commit 5de2480

2 files changed

Lines changed: 115 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Tests for `core::num::isqrt` — floor of the integer square root of a u256.
2+
3+
use core::num::isqrt
4+
5+
#[test]
6+
fn isqrt_small_values() {
7+
assert(isqrt(0) == 0)
8+
assert(isqrt(1) == 1)
9+
assert(isqrt(2) == 1)
10+
assert(isqrt(3) == 1)
11+
assert(isqrt(4) == 2)
12+
assert(isqrt(5) == 2)
13+
assert(isqrt(8) == 2)
14+
assert(isqrt(9) == 3)
15+
assert(isqrt(15) == 3)
16+
assert(isqrt(16) == 4)
17+
}
18+
19+
// For a perfect square k*k, the floor contract pins the results at the
20+
// square and its immediate neighbors: k*k - 1 -> k - 1, k*k -> k, k*k + 1 -> k.
21+
#[test]
22+
fn isqrt_perfect_squares_small() {
23+
let k: u256 = 10
24+
assert(isqrt(k * k - 1) == k - 1)
25+
assert(isqrt(k * k) == k)
26+
assert(isqrt(k * k + 1) == k)
27+
}
28+
29+
#[test]
30+
fn isqrt_perfect_squares_mid() {
31+
let k: u256 = 1000003
32+
assert(isqrt(k * k - 1) == k - 1)
33+
assert(isqrt(k * k) == k)
34+
assert(isqrt(k * k + 1) == k)
35+
}
36+
37+
#[test]
38+
fn isqrt_perfect_squares_huge() {
39+
// k = 2^127; k*k = 2^254 still fits in a u256.
40+
let k: u256 = 170141183460469231731687303715884105728
41+
assert(isqrt(k * k - 1) == k - 1)
42+
assert(isqrt(k * k) == k)
43+
assert(isqrt(k * k + 1) == k)
44+
}
45+
46+
#[test]
47+
fn isqrt_u256_max() {
48+
// floor(sqrt(2^256 - 1)) == 2^128 - 1
49+
assert(isqrt(u256::max()) == 340282366920938463463374607431768211455)
50+
}
51+
52+
// Arbitrary values cross-checked against precomputed floor square roots.
53+
#[test]
54+
fn isqrt_arbitrary_values() {
55+
assert(isqrt(1000000000000000000) == 1000000000)
56+
assert(isqrt(999999999999999999) == 999999999)
57+
assert(isqrt(12345678901234567890) == 3513641828)
58+
// 2^255
59+
assert(isqrt(57896044618658097711785492504343953926634992332820282019728792003956564819968) == 240615969168004511545033772477625056927)
60+
}

ingots/core/src/num.fe

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,61 @@ pub const fn sign_extend(word: u256, _ mask: u256, _ sign_bit: u256) -> u256 {
919919
}
920920
}
921921

922+
/// Returns floor(sqrt(x)), i.e. the largest `z` such that `z * z <= x`.
923+
#[arithmetic(unchecked)]
924+
pub fn isqrt(_ x: u256) -> u256 {
925+
if x == 0 {
926+
return 0
927+
}
928+
929+
// Seed with `1 << (log2(x) / 2)`, a power of two within a factor of two
930+
// of the true root, so a fixed number of Newton iterations suffices.
931+
let mut y = x
932+
let mut z: u256 = 1
933+
if y >= 1 << 128 {
934+
y >>= 128
935+
z <<= 64
936+
}
937+
if y >= 1 << 64 {
938+
y >>= 64
939+
z <<= 32
940+
}
941+
if y >= 1 << 32 {
942+
y >>= 32
943+
z <<= 16
944+
}
945+
if y >= 1 << 16 {
946+
y >>= 16
947+
z <<= 8
948+
}
949+
if y >= 1 << 8 {
950+
y >>= 8
951+
z <<= 4
952+
}
953+
if y >= 1 << 4 {
954+
y >>= 4
955+
z <<= 2
956+
}
957+
if y >= 1 << 2 {
958+
z <<= 1
959+
}
960+
961+
// Newton's method doubles the number of correct bits per step, so seven
962+
// iterations refine the seed past the 128 bits a root can occupy.
963+
z = (z + x / z) >> 1
964+
z = (z + x / z) >> 1
965+
z = (z + x / z) >> 1
966+
z = (z + x / z) >> 1
967+
z = (z + x / z) >> 1
968+
z = (z + x / z) >> 1
969+
z = (z + x / z) >> 1
970+
971+
// Newton converges from above and can settle on `floor + 1`; subtract
972+
// one exactly when `z` overshoots (`z > x / z`) to round down to the floor.
973+
let q = x / z
974+
z - ((z > q) as u256)
975+
}
976+
922977
const fn pow_checked<T>(_ base: own T, _ exp: own T) -> T
923978
where T: IntWord, T: Mul<Output = T>
924979
{

0 commit comments

Comments
 (0)