module Data.Nat.Divisible.GCD where
Greatest common divisors🔗
The greatest common divisor of a pair of natural numbers is the largest number which divides them both. The definition we use is slightly unorthodox, since it requires only that the GCD be the greatest in the divisibility ordering, but the divisibility ordering is finer than the usual ordering, so this will turn out to imply they are greatest in magnitude, as well.
We start by defining what it means for a number to be a GCD — that is, we start by defining the graph of the function Only after will we show that such a function exists.
record is-gcd (x y : Nat) (gcd : Nat) : Type where
field
: gcd ∣ x
gcd-∣l : gcd ∣ y
gcd-∣r : ∀ {g'} → g' ∣ x → g' ∣ y → g' ∣ gcd
greatest
open is-gcd
: ∀ {x y z} → is-prop (is-gcd x y z)
is-gcd-is-prop .gcd-∣l = ∣-is-prop _ _ (p .gcd-∣l) (q .gcd-∣l) i
is-gcd-is-prop p q i .gcd-∣r = ∣-is-prop _ _ (p .gcd-∣r) (q .gcd-∣r) i
is-gcd-is-prop p q i .greatest a b = ∣-is-prop _ _ (p .greatest a b) (q .greatest a b) i
is-gcd-is-prop p q i
instance
: ∀ {x y z n} → H-Level (is-gcd x y z) (suc n)
H-Level-is-gcd = prop-instance is-gcd-is-prop
H-Level-is-gcd
: Nat → Nat → Type
GCD = Σ _ (is-gcd a b)
GCD a b
: ∀ {a b} → is-prop (GCD a b)
GCD-is-prop (_ , p) (_ , q) = Σ-prop-path! $
GCD-is-prop (q .greatest (p .gcd-∣l) (p .gcd-∣r)) (p .greatest (gcd-∣l q) (gcd-∣r q))
∣-antisym
GCD-magnitude: ∀ {x y g : Nat}
→ is-gcd x y (suc g)
→ ∀ {g'} → g' ∣ x → g' ∣ y
→ g' ≤ suc g
= m∣sn→m≤sn (gcd .greatest α β) GCD-magnitude gcd α β
The following observations may seem trivial, but it will come in super handy later: If then the GCD of and is Similarly, the GCD function is “symmetric”, meaning
: ∀ {x y} → x ∣ y → GCD x y
divides→GCD {x} {y} w = x , gcd where
divides→GCD : is-gcd x y x
gcd .gcd-∣l = ∣-refl
gcd .gcd-∣r = w
gcd .greatest g'∣x g'∣y = g'∣x
gcd
: ∀ {x y} → GCD x y → GCD y x
GCD-sym .fst = w .fst
GCD-sym w .snd .gcd-∣l = w .snd .gcd-∣r
GCD-sym w .snd .gcd-∣r = w .snd .gcd-∣l
GCD-sym w .snd .greatest g'∣y g'∣x = w .snd .greatest g'∣x g'∣y GCD-sym w
Euclid’s algorithm🔗
To compute greatest common divisors, we use the familiar (recursive) Euclidean algorithm: and otherwise. The difficulty comes in when we want not only to compute the numbers (in which case the definition above is perfectly cromulent), but also to show that they are greatest common divisors.
module Euclid where
private variable
: Nat n m k d d'
The base case can be established using our existing functions:
: is-gcd n 0 n
is-gcd-0 .gcd-∣l = ∣-refl
is-gcd-0 .gcd-∣r = ∣-zero
is-gcd-0 .greatest g'∣n g∣n = g'∣n is-gcd-0
The inductive step is a bit more complicated. We first have to establish (using the most tedious of arithmetic properties) the following properties of the divisibility relation:
- If is nonzero, and then
- If is nonzero, and then
These two facts, incarnated in the following helper lemmas lem₁
and lem₂
, will allow us to compute the
inductive step for GCD.
private
: fibre (_* d) (suc n) → fibre (_* d) (m % suc n) → fibre (_* d) m
lem₁ {d = d} {n} {m} (c₁ , p) (c₂ , q) = dm.q * c₁ + c₂ , r where
lem₁ module dm = DivMod (divide-pos m (suc n)) renaming (quot to q ; rem to r)
=
r (dm.q * c₁ + c₂) * d ≡⟨ *-distrib-+r (dm.q * c₁) _ _ ⟩
.q * c₁ * d + ⌜ c₂ * d ⌝ ≡⟨ ap! q ⟩
dm.q * c₁ * d ⌝ + dm.r ≡⟨ ap! (*-associative dm.q c₁ d) ⟩
⌜ dm.q * ⌜ c₁ * d ⌝ + dm.r ≡⟨ ap! p ⟩
dm.q * suc n + dm.r ≡˘⟨ is-divmod m (suc n) ⟩
dm
m ∎
: fibre (_* d) (suc n) → fibre (_* d) m → fibre (_* d) (m % suc n)
lem₂ {d = d} {n} {m} (c₁ , p) (c₂ , q) = c₂ - dm.q * c₁ , r where
lem₂ module dm = DivMod (divide-pos m (suc n)) renaming (quot to q ; rem to r)
= (c₂ - dm.q * c₁) * d ≡⟨ monus-distribr c₂ (dm.q * c₁) d ⟩
r .q * c₁ * d ⌝ ≡⟨ ap! (*-associative dm.q c₁ d ∙ ap (dm.q *_) p) ⟩
c₂ * d - ⌜ dm.q * suc n ≡⟨ ap! (q ∙ is-divmod m (suc n)) ⟩
⌜ c₂ * d ⌝ - dm.q * suc n + dm.r ⌝ - dm.q * suc n ≡⟨ ap! (+-commutative (dm.q * suc n) dm.r) ⟩
⌜ dm(dm.r + dm.q * suc n) - dm.q * suc n ≡⟨ monus-cancelr dm.r 0 (dm.q * suc n) ⟩
.r ∎ dm
That step is put together here: It says that, if ( is nonzero and) then too — so that it will suffice to compute the former if we want the latter.
: is-gcd (suc n) (m % suc n) d → is-gcd m (suc n) d
is-gcd-step .gcd-∣l = fibre→∣ (lem₁ (∣→fibre (x .gcd-∣l)) (∣→fibre (x .gcd-∣r)))
is-gcd-step x .gcd-∣r = x .gcd-∣l
is-gcd-step x .greatest g'∣m g'∣sucn =
is-gcd-step x .greatest g'∣sucn (fibre→∣ (lem₂ (∣→fibre g'∣sucn) (∣→fibre g'∣m))) x
Actually putting this together is a bit indirect, since we are not performing structural induction: Agda can’t see that the argument is less than We can, however, turn this into well-founded recursion, which demands only that the recursive call be less (in the sense of than the original input.
: ∀ y x → x < y → GCD y x
euclid-< = Wf-induction _ <-wf _ λ where
euclid-< → x , is-gcd-0
x rec zero p (suc y) p →
x rec let (d , step) = rec (suc y) p (x % suc y) (x%y<y x (suc y))
in d , is-gcd-step step
With a handy wrapper to put the arguments in the order our induction
worker euclid-<
expects, we can
create the euclid
function,
together with its numerical component gcd
, and a proof that the graph of gcd
is indeed is-gcd
.
: ∀ x y → GCD x y
euclid with ≤-split y x
euclid x y ... | inl y<x = euclid-< x y y<x
... | inr (inl x<y) = GCD-sym (euclid-< y x x<y)
... | inr (inr y=x) = divides→GCD (subst (x ∣_) (sym y=x) ∣-refl)
: Nat → Nat → Nat
gcd = Euclid.euclid x y .fst
gcd x y
: ∀ {m n d} → is-gcd m n d ≃ (gcd m n ≡ d)
is-gcd-graphs-gcd {m = m} {n} {d} = prop-ext!
is-gcd-graphs-gcd (λ x → ap fst $ GCD-is-prop (gcd m n , Euclid.euclid m n .snd) (d , x))
(λ p → subst (is-gcd m n) p (Euclid.euclid m n .snd))
: ∀ x y n k .⦃ _ : Positive k ⦄ → is-gcd (x * k) (y * k) (n * k) → is-gcd x y n
is-gcd-factor .gcd-∣l with (q , α) ← ∣→fibre (p .gcd-∣l) = fibre→∣ (q , *-injr k (q * n) x (*-associative q n k ∙ α))
is-gcd-factor x y n k p .gcd-∣r with (q , α) ← ∣→fibre (p .gcd-∣r) = fibre→∣ (q , *-injr k (q * n) y (*-associative q n k ∙ α))
is-gcd-factor x y n k p .greatest {g'} α β with (q , α) ← ∣→fibre α | (r , β) ← ∣→fibre β =
is-gcd-factor x y n k p {n = k} (p .greatest (fibre→∣ (q , sym (*-associative q g' k) ∙ ap (_* k) α)) (fibre→∣ (r , sym (*-associative r g' k) ∙ ap (_* k) β)))
∣-*-cancelr
: ∀ x y k → gcd (x * k) (y * k) ≡ gcd x y * k
gcd-factor = ap₂ gcd (*-zeror x) (*-zeror y) ∙ sym (*-zeror (gcd x y))
gcd-factor x y zero @(suc _) = sym (k∣gcd .snd) ∙ ap (_* k) (sym (Equiv.to is-gcd-graphs-gcd d')) where
gcd-factor x y k= Euclid.euclid (x * k) (y * k) .snd .greatest {k} (∣-*r {k} {x}) (∣-*r {k} {y})
k∣gcd
: is-gcd x y (k∣gcd .fst)
d' = is-gcd-factor x y (k∣gcd .fst) k (subst (is-gcd _ _) (sym (k∣gcd .snd)) (Euclid.euclid (x * k) (y * k) .snd)) d'
Euclid’s lemma🔗
|-*-coprime-cancel: ∀ n a b .⦃ _ : Positive n ⦄ .⦃ _ : Positive a ⦄
→ n ∣ a * b → is-gcd n a 1 → n ∣ b
= done where
|-*-coprime-cancel n a b div coprime : Nat → Prop lzero
E = el! (0 < x × n ∣ x * b)
E x
: Σ[ x ∈ Nat ] ((x ∈ E) × (∀ y → (0 < y) × (n ∣ y * b) → x ≤ y))
has = ℕ-well-ordered {P = E} (λ _ → auto) (inc (a , recover auto , div))
has
instance
_ : Positive (has .fst)
_ = has .snd .fst .fst
: ∀ x → x ∈ E → has .fst ∣ x
step (xe , d) with divmod q r α β ← divide-pos x (has .fst) =
step x let
: n ∣ (q * has .fst * b + r * b)
d' = subst (n ∣_) (ap (_* b) (recover α) ∙ *-distrib-+r (q * has .fst) r b) d
d'
: n ∣ r * b
d'' = ∣-+-cancel {n} {q * has .fst * b} {r * b} (subst (n ∣_) (sym (*-associative q (has .fst) b)) (|-*l-pres {a = q} (has .snd .fst .snd))) d'
d'' in case r ≡? 0 of λ where
(yes p) → fibre→∣ (q , sym (recover α ∙ ap (q * has .fst +_) p ∙ +-zeror (q * has .fst)))
(no ¬p) → absurd (<-irrefl
(≤-antisym (≤-trans ≤-ascend (recover β)) (has .snd .snd r (nonzero→positive ¬p , d'')))
(recover β))
: has .fst ≡ 1
almost = ∣-1 $ coprime .greatest {has .fst}
almost (step n (recover auto , ∣-*l))
(step a (recover auto , div))
: n ∣ b
done = subst (n ∣_) (ap (_* b) almost ∙ *-onel b) (has .snd .fst .snd) done