; File: BinaryGCD.scm
; Author: Keith Schwarz (htiek@cs.stanford.edu)
;
; An algorithm for computing gcd(a, b) using the binary representations of a
; and b.  This algorithm has the same asymptotic complexity as Euclid's
; algorithm, but it can be implemented without division or modulus operations
; by instead relying on shifts.
;
; The algorithm is based on the following recursive structure, based on whether
; the input numbers are odd or even:
;
;   gcd(a, 0)           = a
;   gcd(2n, 2m)         = 2 gcd(n, m)
;   gcd(2n, 2m + 1)     = gcd(n, 2m + 1)
;   gcd(2n + 1, 2m)     = gcd(2n + 1, m)
;   gcd(2n + 1, 2m + 1) = gcd(2n - 2m, 2m + 1)
;
; We prove the correctness of each of these cases individually.  To do so, we
; rely on the fact that gcd(m, n) can be found by writing out each number as a
; product of primes.  The primes common to the representations of both m and n
; are then the gcd of the two numbers.  As an exception to the rule, if either
; number is 0, then the gcd is the other number.
;
; Given this, let us prove the first four cases correct.  First, gcd(a, 0) = a
; is true by the special case of the above rule.  Next, we can see that
; gcd(2n, 2m) = 2 gcd(n, m) must be true because if 2 is a common prime factor
; of the quantities, then the resulting gcd must be the gcd of the numbers with
; 2 removed as a factor from each times 2.  Finally, the two cases
;
;   gcd(2n, 2m + 1) = gcd(n, 2m + 1)
;   gcd(2n + 1, 2m) = gcd(2n + 1, m)
;
; are symmetric to one another.  Since in this case one of the numbers has two
; as a factor and the other does not, then one number has 2 in its prime
; factorization with multiplicity at least one, and the other number does not
; have 2 as a prime factor.  Consequently, 2 will not appear anywhere in the
; two numbers' gcd.  If we remove the 2 from the number that contains it as a
; prime factor, we therefore do not change the gcd.
;
; Finally, we must prove that
;
;   gcd(2n + 1, 2m + 1) = gcd(2n - 2m, 2m + 1)
;
; To see this, consider any divisor of 2n - 2m and 2m + 1.  We claim that this
; number must also be a divisor of 2n + 1 and 2m + 1.  To see this, call that
; number q and let
;
;   2m + 1  = aq
;   2n - 2m = bq
;
; then we have that
;
;   2n + 1 = 2n - 2m + 2m + 1 = aq + bq = (a + b)q
;
; so the number divides 2n + 1.  Similarly, any divisor of 2n + 1 and 2m + 1
; must be a divisor of 2n - 2m, since if
;
;   2n + 1 = cq
;   2m + 1 = dq
;
; then
;
;   2n - 2m = (2n + 1) - (2m + 1) = cq - dq = (c - d)q
;
; Thus gcd(2n + 1, 2m + 1) = gcd(2n - 2m, 2m + 1) as required.
;
; The binary GCD algorithm is quite fast; it always terminates after O(lg ab)
; steps, where a and b are the numbers whose GCD we want to compute.  To see
; this, we claim that after any two consecutive applications of the above
; rules, either a or b will have dropped by at least a factor of two.
; Consequently, we can only apply O(lg ab) rules before one of the terms
; drops below one and we can apply the first rule as our base case.  To prove
; this claim, consider all the rules in turn.
;
;  - gcd(2n, 2m) = 2 gcd(n, m).  Both n and m drop by a factor of two.
;  - gcd(2n, 2m + 1) = gcd(n, 2m + 1).  n drops by a factor of two.
;  - gcd(2n + 1, 2m) = gcd(2n + 1, m).  m drops by a factor of two.
;  - gcd(2n + 1, 2m + 1) = gcd(2n - 2m, 2m + 1).  This does not necessarily
;    decrease the first term by a factor of two directly.  However, at this
;    point the first term is even and the second odd, so we will apply the
;    second of the above rules to the term to drop it by a factor of two on
;    the next iteration.
;
; Consequently, we need only O(lg ab) steps to compute gcd(a, b), which is on
; par with Euclid's algorithm.

(define binary-gcd
(lambda (a b)
(cond ((< a b) (binary-gcd b a))       ; For simplicity, assume a >= b.
((zero? b) a)                    ; gcd(a, 0) = a
((and (even? a) (even? b))       ; gcd(2n, 2m) = 2 gcd(n, m)
(* 2 (binary-gcd (/ a 2) (/ b 2))))
((and (even? a) (odd? b))        ; gcd(2n, 2m + 1) = gcd(n, 2m + 1)
(binary-gcd (/ a 2) b))
((and (odd? a) (even? b))        ; gcd(2n + 1, 2m) = gcd(2n + 1, m)
(binary-gcd a (/ b 2)))
(else (binary-gcd (- a b) b))))) ; gcd(2n + 1, 2m + 1) =
;    gcd(2n - 2m, 2m + 1)