Skip to content

Commit 48fd281

Browse files
vil02ZoomRmc
andauthored
feat: add extended_gcd.nim (#62)
* feat: add `extended_gcd.nim` * style: reformat code Co-authored-by: Zoom <ZoomRmc@users.noreply.github.com> * feat: use iterative implementation * docs: mention `extendedGCD*` in the module docs * docs: add sentence about more efficient ways to compute `gcd` * docs: remove truism * refactor: use `uint`s as input of `euclidIteration` * docs: add estimates of Bézout coefficients * docs: remove the note about more efficient ways * style: use single `import` Co-authored-by: Zoom <ZoomRmc@users.noreply.github.com> * style: use expression-based return and name elements in the tuple Co-authored-by: Zoom <ZoomRmc@users.noreply.github.com> * style: name elements in the tuple Co-authored-by: Zoom <ZoomRmc@users.noreply.github.com> * style: simplify calling `euclidIteration` * refactor: store `gcd` as `Natural` --------- Co-authored-by: Zoom <ZoomRmc@users.noreply.github.com>
1 parent 1314f1c commit 48fd281

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

maths/extended_gcd.nim

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
## Extended Euclidean algorithm
2+
3+
import std/math
4+
5+
6+
func updateCoefficients(t0, t1, q: int): (int, int) =
7+
(t1, t0 - q * t1)
8+
9+
10+
func euclidIteration(inA, inB: int): (Natural, int, int) =
11+
var (a, b) = (inA.abs().Natural, inB.abs().Natural)
12+
var (x0, x1) = (1, 0)
13+
var (y0, y1) = (0, 1)
14+
while b != 0:
15+
let q = int(a div b)
16+
(x0, x1) = updateCoefficients(x0, x1, q)
17+
(y0, y1) = updateCoefficients(y0, y1, q)
18+
(a, b) = (b, a mod b)
19+
20+
(a, x0, y0)
21+
22+
23+
func extendedGCD*(a, b: int): tuple[gcd: Natural; x, y: int] =
24+
## Implements the
25+
## [Extended Euclidean algorithm](https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm).
26+
## For given integers `a`, `b` it
27+
## computes their [`gcd`](https://en.wikipedia.org/wiki/Greatest_common_divisor)
28+
## and integers `x` and `y`, such that
29+
## `gcd = a * x + b * y`,
30+
## and furthermore:
31+
## - if `a != 0`, then `abs(y) <= abs(a div gcd)`,
32+
## - if `b != 0`, then `abs(x) <= abs(b div gcd)`.
33+
let (gcd, x, y) = euclidIteration(a, b)
34+
(gcd: gcd, x: math.sgn(a) * x, y: math.sgn(b) * y)
35+
36+
37+
when isMainModule:
38+
import std/[unittest, sequtils, strformat]
39+
suite "extendedGCD":
40+
const testCases = [
41+
(10, 15, 5, -1, 1),
42+
(-10, -15, 5, 1, -1),
43+
(32, 64, 32, 1, 0),
44+
(0, 0, 0, 0, 0),
45+
(7, 0, 7, 1, 0),
46+
(-8, 0, 8, -1, 0),
47+
(48, 60, 12, -1, 1),
48+
(98, 56, 14, -1, 2),
49+
(10, -15, 5, -1, -1),
50+
(997, 12345, 1, -3467, 280),
51+
(997, 1234567, 1, -456926, 369),
52+
].mapIt:
53+
let tc = (id: fmt"a={it[0]}, b={it[1]}", a: it[0], b: it[1],
54+
gcd: it[2].Natural, x: it[3], y: it[4])
55+
if tc.gcd != 0:
56+
assert tc.a mod int(tc.gcd) == 0
57+
assert tc.b mod int(tc.gcd) == 0
58+
if tc.b != 0:
59+
assert abs(tc.x) <= abs(tc.b div int(tc.gcd))
60+
else:
61+
assert abs(tc.x) == 1
62+
assert tc.y == 0
63+
if tc.a != 0:
64+
assert abs(tc.y) <= abs(tc.a div int(tc.gcd))
65+
else:
66+
assert abs(tc.y) == 1
67+
assert tc.x == 0
68+
else:
69+
assert tc.a == 0 and tc.b == 0
70+
assert tc.x == 0 and tc.y == 0
71+
assert int(tc.gcd) == tc.a * tc.x + tc.b * tc.y
72+
tc
73+
74+
for tc in testCases:
75+
test tc.id:
76+
checkpoint("returns expected result")
77+
check extendedGCD(tc.a, tc.b) == (tc.gcd, tc.x, tc.y)
78+
checkpoint("returns expected result when first argument negated")
79+
check extendedGCD(-tc.a, tc.b) == (tc.gcd, -tc.x, tc.y)
80+
checkpoint("returns expected result when second argument negated")
81+
check extendedGCD(tc.a, -tc.b) == (tc.gcd, tc.x, -tc.y)
82+
checkpoint("returns expected result when both arguments negated")
83+
check extendedGCD(-tc.a, -tc.b) == (tc.gcd, -tc.x, -tc.y)
84+
checkpoint("is symmetric")
85+
check extendedGCD(tc.b, tc.a) == (tc.gcd, tc.y, tc.x)

0 commit comments

Comments
 (0)