← Contents

Euclid's GCD

Described, implemented in Lean, visualised, and proved correct. repo

Part III leaves the digit-by-digit work behind. This algorithm is a recursion on whole numbers, and it lives on Chapter 2: the subtraction it runs is verticalSub, and that chapter's correctness theorem is what convinces Lean the recursion stops. Two ideas are new here. It is the first algorithm whose termination Lean does not hand us for free, and its proof uses an induction shaped like the function rather than like a list.

1 Β· What the algorithm does

Picture two numbers, 48 and 36, as the sides of a rectangle. Now a tiling question about it: what is the largest square that paves the whole rectangle exactly, leaving no gaps and no overhang, every tile the same size?

Euclid's answer is to carve. If the rectangle is already a square, its side is the answer. If not, cut off the biggest square you can β€” one as wide as the shorter side β€” and ask the same question about the strip left over. Cutting a 36 Γ— 36 square from a 48 Γ— 36 rectangle leaves 12 Γ— 36, and in arithmetic that cut is just subtracting the shorter side from the longer. Keep carving, and the square you finally land on has the answer for its side.

Why does the answer survive each cut? A square of side s tiles a rectangle exactly when s divides both sides. And the common divisors of a and b are exactly the common divisors of a βˆ’ b and b: anything dividing both sides divides their difference, and anything dividing the difference and one side divides their sum. So each cut changes the rectangle but not the question's answer:

gcd(a, b) = gcd(a βˆ’ b, b)   whenever b ≀ a.

That single equation, applied until the two numbers agree, is the whole algorithm.

Why subtraction, not remainder. The fast Euclid you may have seen replaces gcd(a, b) with gcd(b, a mod b) β€” fewer steps, but a mod hides a whole division inside each step. We use the older subtractive form, gcd(a, b) = gcd(a βˆ’ b, b), because it is the one you can actually see: repeatedly carving the largest square tile off a rectangle. The correctness and the tiling picture fall out of the same equation. It is slower, and that cost is exactly what surfaces as the termination proof in Β§2.

2 Β· The implementation, in Lean

Just one recursive function this time, but it asks something the earlier chapters never did. Those walked down a list, so Lean could see for itself that the recursion stopped. Here the arguments are plain numbers that shrink by subtraction, and Lean won't take termination on trust: you have to give it a measure and show the measure falls at every call.

The 0-cases match the mathematical convention gcd 0 n = n β€” and they are load-bearing. Starting from two positive numbers the recursion never reaches a 0 (subtracting the smaller from the strictly larger keeps both positive), but a Lean function must be defined on every input, and without the guard gcdSub 0 5 would subtract 0 forever, making no progress.

3 Β· See it run β€” carving the rectangle

Step through the carving. Blue squares are already removed, the orange square is being removed now, and the faint region is still to do. The panel tracks the invariant that makes it all work: the gcd of the leftover rectangle's sides never changes. The last step re-tiles the whole rectangle with the answer.

Γ—

Try lopsided sides too, like 144 Γ— 5. The carving crawls, because subtractive Euclid peels off one small square at a time. That is the inefficiency the usual remainder version fixes, trading a long run of subtractions for a single a mod b. The proofs only care about correctness, though, and the slow version is the honest, geometric one to reason about.

4 Β· Before the proof β€” think like a verifier

Six chapters in, you know the routine: before reading the proof, try to predict its shape. What should the statement say? What one fact does each recursive step lean on? And, new this time, why does the recursion stop at all?

What should the theorem say?

PREDICT

The step that does all the work

Each recursive call replaces (a, b) by (a βˆ’ b, b). Why is that allowed to leave the answer unchanged?

PREDICT

Why does it stop?

The recursion doesn't consume a list β€” both arguments stay as long as they like. What convinces Lean it terminates?

PREDICT

Is this the right specification?

It is tempting to say the gcd is simply "a number that divides both a and b." But 1 divides both β€” and so does every other common divisor β€” so that test is passed by many numbers, most of them wrong. A correct specification needs two clauses: the result divides both sides, and it is the greatest such number (every common divisor divides it). That is exactly what gcdSub_tiles states; "divides both" alone would let a lazy algorithm return 1 every time and still look correct.

5 Β· The proof

Two theorems, and each comes in two parts. First the argument in plain words: read just these and you'll see why the algorithm is correct and why it deserves the name gcd. Then, folded underneath, the full Lean proof, for anyone who wants the machine-checked details. You can follow the whole chapter without opening a single dropdown.

The proof brings in one new tool: functional induction. Every recursive function Lean accepts comes with its own induction principle, shaped to match the function's case-split. induction a, b using gcdSub.induct hands us five goals, one per branch of the definition, and in the two recursive branches it gives us the inductive hypothesis right where the recursive call sits. Nothing is peeled off a list; the recursion itself is what we climb. Goal states in the tooltips are lightly abbreviated; the shapes are what matter.

6 Β· Test the ideas

The 0-cases

Starting from two positive numbers, can the recursion ever reach a 0?

CHOOSE

What functional induction buys

induction a, b using gcdSub.induct β€” what exactly does it produce?

CHOOSE

Chapter 2, cited twice

verticalSub_correct' appears in two different roles in this chapter. Which two?

CHOOSE

The price of subtraction

Roughly how many recursive steps does gcdSub take on gcd(1 000 000, 1)?

CHOOSE

Predict a run

What does gcdSub return on gcd(14, 21), and how does it get there?

PREDICT

Both halves of the tiling theorem

gcdSub_tiles proves two things: the answer divides both sides, and every common divisor divides the answer. Why isn't the first half enough on its own?

CHOOSE

Drop the equal-sides check?

Suppose you deleted the toNat a = toNat b branch, leaving only the two subtraction branches. What happens?

CHOOSE

7 Β· Write some Lean

A worksheet. Each cell has a starting goal with a gap for you to fill. Press Run (or Ctrl+Enter in the cell) and Lean compiles your code on the server. Run all checks every cell in turn, Reset all restores the blank cells, and every cell hides a reference solution you can reveal once you've tried. (Requires the checker server β€” see web/checker/README.md. In the listings digit literals may be shown as ⟨8,_⟩; the server spells them ⟨8, by omega⟩. The first run is slow: Lean is cold-loading Mathlib.)

The real, machine-checked proofs are exactly those in Β§5 β€” clone the repo and run them in VS Code.

← Divisibility Tests Contents The Cube-Root Trick β†’