Skip to content

Commit

Permalink
Add extended Euclid Algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
hansonchar committed Feb 21, 2025
1 parent 7837d86 commit 394050b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 7 deletions.
43 changes: 36 additions & 7 deletions learning-lua/algo/Euclid-tests.lua
Original file line number Diff line number Diff line change
@@ -1,21 +1,50 @@
local gcd = require "algo.Euclid".gcd
local xgcd = require "algo.Euclid".xgcd

local function negative_test(a, b)
local DEBUG = require "algo.Debug":new(false)
local debugf = DEBUG.debugf


local function negative_test(f, a, b)
assert(a < 0 or b < 0)
local ok, err = pcall(gcd, a, b)
local ok, err = pcall(f, a, b)
assert(not ok)
assert(string.find(err, "input numbers must not be negative"))
end

-- edge cases
local function verify_xgcd(s, t, d, m, n)
debugf("%d * %d + %d * %d = %d", m, s, n, t, d)
assert(m * s + n * t == d)
end

-- gcd edge cases
assert(gcd(0, 0) == 0)
assert(gcd(1, 0) == 1)
assert(gcd(0, 1) == 1)

-- negative is not allowed
negative_test(0, -1)
negative_test(-1, 0)
-- negative is not allowed for gcd
negative_test(gcd, 0, -1)
negative_test(gcd, -1, 0)

-- positive tests
-- gcd positive tests
assert(gcd(123, 54) == 3)
assert(gcd(54, 123) == 3)

-- negative is not allowed for xgcd
negative_test(xgcd, 0, -1)
negative_test(xgcd, -1, 0)

-- xgcd edge cases
local d, m, n = xgcd(0, 0)
verify_xgcd(0, 0, d, m, n)
assert(d == 0 and m == 0 and n == 0)

verify_xgcd(1, 0, xgcd(1, 0))
verify_xgcd(0, 1, xgcd(0, 1))

-- xgcd positive tests
verify_xgcd(240, 46, xgcd(240, 46))
verify_xgcd(46, 240, xgcd(46, 240))
verify_xgcd(123, 54, xgcd(123, 54))
verify_xgcd(54, 123, xgcd(54, 123))

41 changes: 41 additions & 0 deletions learning-lua/algo/Euclid.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,47 @@ function Euclid.gcd(a, b)
end
end

---(See Euclid.pdf on the idea behind this specific implementation.)
---@param n number
---@param d number
---@param npair table the pair of numbers for `n`.
---@param dpair table the pair of numbers for `d`.
---@return number d greatest common divisor of the `s` and `t` parameters passed to `Euclid.xgcd(s, t)`.
---@return number m integer with the least magnitude such that `m*s + n*t = d`.
---@return number n integer with the least magnitude such that `m*s + n*t = d`.
local function _xgcd(n, d, npair, dpair)
if d == 0 then
return n, npair[1], npair[2]
end
local q = math.floor(n / d)
local r = n % d
local dpair_1, dpair_2 = table.unpack(dpair)
dpair[1], dpair[2] = npair[1] - q * dpair_1, npair[2] - q * dpair_2
npair[1], npair[2] = dpair_1, dpair_2
return _xgcd(d, r, npair, dpair)
end

---Computes the greatest common divisor of `s` and `t` using Extended Euclid algorithm.
---Returns the greatest common divisor of `s` and `t`,
---and integers `m` and `n` such that `ms + nt = d`.
---@param s number non-negative integer.
---@param t number non-negative integer.
---@return number d greatest common divisor of `s` and `t`.
---@return number m integer with the least magnitude such that `m*s + n*t = d`.
---@return number n integer with the least magnitude such that `m*s + n*t = d`.
function Euclid.xgcd(s, t)
assert(s >= 0 and t >= 0, "input numbers must not be negative")
if s == 0 and t == 0 then
return 0, 0, 0 -- edge case.
end
if s < t then
local d, m, n = _xgcd(t, s, {1, 0}, {0, 1})
return d, n, m
else
return _xgcd(s, t, {1, 0}, {0, 1})
end
end

return Euclid

-- To check tail recursiveness:
Expand Down
Binary file added learning-lua/algo/Euclid.pdf
Binary file not shown.

0 comments on commit 394050b

Please sign in to comment.