Euclid's algorithm: GCD (iterative + recursive), LCM, the extended Euclid algorithm returning Bezout coefficients, and a coprimality check
Demonstrates: the modulo recurrence, returning a record of {g, x, y}, and using the builtin gcd() as an oracle in tests.
A Soma program from the algorithms part of the verified corpus: 81 lines, cells GcdLcm, GcdLcmTests. It passes soma check with soma 3.2.4.
How it is built
The cell GcdLcm declares 5 signals in its face: gcd_iter(a: Int, b: Int) -> Int, gcd_rec(a: Int, b: Int) -> Int, lcm(a: Int, b: Int) -> Int, egcd(a: Int, b: Int) -> Map and coprime(a: Int, b: Int) -> Bool. Handlers: gcd_iter (line 18), gcd_rec (line 29), lcm (line 34), egcd (line 40) and coprime (line 62).
The file also holds 1 test cell (GcdLcmTests) with 10 assertions, run by soma test.
The source
gcd_lcm.cell
// Euclid's algorithm: GCD (iterative + recursive), LCM, the extended Euclid
// algorithm returning Bezout coefficients, and a coprimality check.
// Demonstrates: the modulo recurrence, returning a record of {g, x, y}, and
// using the builtin gcd() as an oracle in tests.
//
// soma run examples/corpus/algorithms/gcd_lcm.cell gcd_iter 48 36
// soma test examples/corpus/algorithms/gcd_lcm.cell
cell GcdLcm {
face {
signal gcd_iter(a: Int, b: Int) -> Int
signal gcd_rec(a: Int, b: Int) -> Int
signal lcm(a: Int, b: Int) -> Int
signal egcd(a: Int, b: Int) -> Map
signal coprime(a: Int, b: Int) -> Bool
}
on gcd_iter(a: Int, b: Int) {
let x = abs(a)
let y = abs(b)
while y != 0 {
let t = y
y = x - floor(x / y) * y // x mod y
x = t
}
return x
}
on gcd_rec(a: Int, b: Int) {
if b == 0 { return abs(a) }
return gcd_rec(b, a - floor(a / b) * b)
}
on lcm(a: Int, b: Int) {
if a == 0 || b == 0 { return 0 }
return abs(a * b) / gcd_iter(a, b)
}
// extended Euclid: returns {g, x, y} with a*x + b*y = g
on egcd(a: Int, b: Int) {
let old_r = a
let r = b
let old_s = 1
let s = 0
let old_t = 0
let t = 1
while r != 0 {
let q = floor(old_r / r)
let nr = old_r - q * r
old_r = r
r = nr
let ns = old_s - q * s
old_s = s
s = ns
let nt = old_t - q * t
old_t = t
t = nt
}
return Bezout { g: old_r, x: old_s, y: old_t }
}
on coprime(a: Int, b: Int) {
return gcd_iter(a, b) == 1
}
}
cell test GcdLcmTests {
rules {
assert gcd_iter(48, 36) == 12
assert gcd_iter(48, 36) == gcd(48, 36)
assert gcd_rec(1071, 462) == 21
assert gcd_iter(17, 5) == 1
assert lcm(4, 6) == 12
assert lcm(21, 6) == 42
assert coprime(14, 15)
assert !coprime(14, 21)
assert egcd(240, 46).g == 2
assert 240 * egcd(240, 46).x + 46 * egcd(240, 46).y == 2
}
}What the toolchain says
$ soma check gcd_lcm.cell
✓ All checks passed.$ soma test gcd_lcm.cell
test GcdLcmTests ...
✓ assert gcd_iter(48, 36) == 12
✓ assert gcd_iter(48, 36) == gcd(48, 36)
✓ assert gcd_rec(1071, 462) == 21
✓ assert gcd_iter(17, 5) == 1
✓ assert lcm(4, 6) == 12
✓ assert lcm(21, 6) == 42
✓ assert coprime(14, 15)
✓ assert !coprime(14, 21)
✓ assert egcd(240, 46).g == 2
✓ assert 240 * egcd(240, 46).x + 46 * egcd(240, 46).y == 2
10 tests: 10 passed, 0 failed$ soma verify gcd_lcm.cell
Verifying gcd_lcm.cell...
GcdLcm/termination:
⚠ handler `gcd_iter`: while-loop without provable termination bound (write `while [loop_bound(N)] cond { … }`, or a for-loop over a bounded range)
⚠ handler `gcd_rec`: recursive call without provable decreasing argument
⚠ `lcm` calls `GcdLcm.gcd_iter`, which may not terminate
⚠ handler `egcd`: while-loop without provable termination bound (write `while [loop_bound(N)] cond { … }`, or a for-loop over a bounded range)
⚠ `coprime` calls `GcdLcm.gcd_iter`, which may not terminate
0 passed, 5 warnings, 0 failures
Temporal: 0 passed, 0 failed
VERIFY OK$ soma run gcd_lcm.cell gcd_iter 48 36
12