Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Bits & Machine Numbers  ›  Lesson

GCD and Extended Euclid

GCD & Extended Euclid 28 minEuclid's remainders keep a receipt of how to build them, and that receipt is the modular inverse
You're building a piece ofQuarry — the record encoder
This piece — mod_inverse(): Undoes the multiply that hides sequential record ids, using an inverse from extended Euclid.
Scenario Quarry never shows sequential ids to the outside world. It multiplies each by a secret odd key mod 2³². When a URL comes back in, the id scrambler multiplies by the key's inverse, computed once with extended Euclid, and the real record id reappears.
Your task
Build mod_inverse(a, m) with extended Euclid, without pow(a, -1, m). Return the x in 0 … m − 1 with a·x % m == 1, or None when gcd(a, m) != 1. Example: mod_inverse(17, 60) → 53.

GCD and Extended Euclid

In short: Euclid's algorithm replaces (a, b) with (b, a % b) until b is 0, because both pairs share the same common divisors, and that pair shrinks fast enough to finish in O(log min(a, b)) time and O(1) space. Run the same replacement on two tracked coefficients (extended Euclid) and you get Bézout's identity a·x + b·y = gcd(a, b) for free, which gives a's modular inverse whenever gcd(a, m) = 1 — exactly the tool for undoing a multiply modulo m.

Four boxes (48,18) -> (18,12) -> (12,6) -> (6,0) connected by arrows labelled a % b, with the last box highlighted green as the gcd

Quarry shows record ids to the outside world in URLs and API responses. Sequential ids leak information. If yesterday's record was 1,000 and today's is 1,850, anyone can see how fast your data is growing. The usual cure is to scramble each id before showing it. Multiply it by a secret key modulo 2³², which in a 32-bit word is just "multiply and let it wrap":

python

The public ids look random. But Quarry needs the real id back when a URL comes in, so it has to undo a multiplication modulo 2³². That needs a number INV with KEY · INV ≡ 1 (mod 2³²), the key's modular inverse. The algorithm that finds it is more than two thousand years old and still runs in O(log m) steps. It also answers whether an inverse exists at all.

Euclid's algorithm

The greatest common divisor gcd(a, b) is the largest number dividing both. Euclid's observation: any number that divides both a and b also divides a − q·b for any q, and in particular a % b. So the pairs (a, b) and (b, a % b) have exactly the same common divisors. Keep replacing the pair until the second number is 0. The first number is then the gcd:

python
What does this print?
a, b, steps = 1234, 322, 0
while b:
    a, b = b, a % b
    steps += 1
print(a, steps)

Why so few steps? After two iterations the larger number has at least halved: if b ≤ a/2 then a % b < b ≤ a/2, and otherwise a % b = a − b < a/2. So the loop is O(log min(a, b)). The worst case is two consecutive Fibonacci numbers, where every quotient is 1 and the numbers shrink as slowly as possible. Even then it takes only about log base φ of n steps, where φ ≈ 1.618:

digits in n worst-case steps
5 24
10 48
20 96
21 100.5

Each extra decimal digit costs fewer than 5 more steps, so numbers up to 20 digits never need more than 100. Quarry's 32-bit keys have at most 10 digits, so the loop never runs more than about 48 times.

Extended Euclid: keeping the receipts

Every remainder Euclid produces is a combination of the two original numbers. It starts with a = 1·a + 0·b and b = 0·a + 1·b, and each new remainder is "two rows up minus q times one row up". So if you apply the same rule to the coefficients, you always know how to write the current remainder as a·x + b·y. When the remainder reaches the gcd, you have Bézout's identity: a·x + b·y = gcd(a, b).

Step through a = 60, b = 17. The arrows show the two cells each new value is built from, and every row satisfies 60·x + 17·y = r:

watch it move · grid

Every row keeps the promise 60·x + 17·y = r. When r reaches the gcd, x and y are the Bézout coefficients, and y is 17's inverse mod 60.

The first two rows say 60 = 60·1 + 17·0 and 17 = 60·0 + 17·1. 6 by 4 table. At row 1, column 1 — value 17. 6 of 24 cells filled.

1 / 6

In code you only need the last two rows at any moment:

step through it
1a, b = 60, 17
2r0, r1 = a, b
3x0, x1 = 1, 0
4y0, y1 = 0, 1
5while r1:
6 q = r0 // r1
7 r0, r1 = r1, r0 - q * r1
8 x0, x1 = x1, x0 - q * x1
9 y0, y1 = y1, y0 - q * y1
10print(r0, x0, y0)

Modular inverses

a·x ≡ 1 (mod m) means a·x + m·y = 1 for some whole number y, which is Bézout's identity with gcd 1. So:

  • An inverse exists exactly when gcd(a, m) = 1. If some d > 1 divides both, every a·x + m·y is a multiple of d and can never equal 1.
  • Extended Euclid finds it. Run it on (a, m) and take the coefficient of a, reduced into 0 … m − 1.

For the scrambler the modulus is 2³², whose only prime factor is 2. So a key has an inverse exactly when it's odd. Quarry's key is odd, and its inverse unscrambles every id:

python

When the modulus is prime there's a second route, Fermat's little theorem, which says a**(p-1) ≡ 1 (mod p) for a not divisible by p. So a**(p-2) is the inverse, and last lesson's mod_pow computes it. That doesn't work for 2³², which isn't prime. Extended Euclid works for any modulus.

What does this print?
print(pow(6, -1, 35), 6 * pow(6, -1, 35) % 35)

The coefficient extended Euclid returns can be negative. Forget to fix that and a "key" of −7 goes into the scrambler:

broken — fix it

It prints -7 1. −7 is a correct inverse mathematically, but a scrambler key has to be a number between 0 and m − 1.

Now run the table-builder yourself. Each frame adds one row of [q, r, x, y]. Try (35, 6), then a pair with a common factor such as (84, 36), and watch the last non-zero r become the gcd:

your code, drawn · grid

Your extended Euclid, one row per division. Every row satisfies a·x + b·y = r.

Interview corner — which offsets can the clock reach?

Quarry's replay tool can move a log cursor forward by a entries or back by b entries, as many times as you like in any order. Starting from entry 0, can it land exactly on entry t? Every sequence of moves reaches a·i − b·j for some counts, and by Bézout those are exactly the multiples of gcd(a, b). So the answer is a single test:

python

Brute-force searching over move sequences would never be sure when to stop. Bézout answers it immediately, and extended Euclid even gives the counts i and j.

Quick practice

What does this print?
a, b = 48, 18
while b:
    a, b = b, a % b
print(a)
broken — fix itZeroDivisionError

It crashes instead of returning 6. Swap which variable the while condition watches.


🎯 Your turn

Write mod_inverse(a, m) using extended Euclid, without pow(a, -1, m). Return the x in 0 … m − 1 with a·x % m == 1, or None if a has no inverse mod m (because gcd(a, m) != 1). m >= 2.

  • mod_inverse(17, 60) → 53
  • mod_inverse(6, 9) → None, since both are divisible by 3

Hint — run extended Euclid on (a, m), tracking only the coefficient of a. If the final remainder isn't 1, return None. Otherwise return that coefficient % m.

Then press ▶ Run, try the Live App chips, and hit ✓ Check. ✅

Practice — level up

0/6 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upEuclid's algorithm

Write gcd(a, b) for non-negative integers using Euclid's algorithm: replace (a, b) by (b, a % b) until b is 0. Don't use math.gcd. gcd(0, 0) is 0.

gcdeuclid
DrillWhen do all the timers line up?

Quarry runs several maintenance timers. Timer i fires every periods[i] seconds, and they all fire together at time 0. Write lcm_all(periods) returning the next time they all fire together, the least common multiple of the periods. An empty list gives 1.

lcmgcd
BuildExtended Euclid

Write ext_gcd(a, b) returning [g, x, y] where g = gcd(a, b) and a·x + b·y == g. Use the iterative table method: each new r, x and y is 'two rows up minus q times one row up'. Return exactly the coefficients that method produces.

extended-euclidbezout
BossSolve a·x ≡ b (mod m)

Write solve_congruence(a, b, m) returning the smallest x in 0 … m − 1 with a·x % m == b % m, or None if there is none. a need not be coprime to m. For example 6·x ≡ 4 (mod 10) has solutions even though 6 has no inverse mod 10. m >= 1. Aim for extended Euclid, not a search.

modular-inverseextended-euclid
CapstoneWhen do two schedules first meet?

Two Quarry shards compact on repeating schedules. Shard A compacts at every time t with t % m1 == r1, and shard B at every t with t % m2 == r2 (0 <= r1 < m1, 0 <= r2 < m2). Write first_common_slot(r1, m1, r2, m2) returning the first time t >= 0 when both compact together, or None if they never do. The periods need not be coprime, and they can be large, so don't search.

extended-euclidnumber-theory-algorithms
ExplainWhy the scrambler key must be odd

Quarry scrambles ids with public = id * KEY % 2**32. A teammate proposes KEY = 3_000_000_000, which is even. Explain what goes wrong with an even key, using gcd, and why every odd key works. What property of the modulus is doing the work?

mod_inverse.py