The greatest common divisor of two non-zero integers is a great example to illustrate the power of loops. Everyone learns about the concept of a greatest common divisor when faced with a fraction that is not in reduced form.
Consider the fraction \(\frac{2}{4}\), which is the same as \(\frac{1}{2}\). The fraction \(\frac{2}{4}\) can be reduced, because the numerator and denominator both have greatest common factor of 2. That is, \(\frac{2}{4} = \frac{1 \cdot 2}{2 \cdot 2}\). So the factor of 2 can be canceled from both the numerator and the denominator.
Euclid (the mathematician from classic times and author of Elements)
is credited with having come up with a clever algorithm for how to
compute the greatest common divisor efficiently. It is written as
follows, where \(a \bmod b\) means a % b
in C#.
It is common in mathematics to list functions as one or more cases. The way you read this is as follows:
a
and b
is the
same as computing the greatest common divisor of b
and the
remainder of a
divided by b
.b
is zero, the result is a
. This makes
sense because a
divides itself and 0.To gain some appreciation of how the definition always allows you to compute the greatest common divisor, it is worthwhile to try it out for a couple of numbers where you know the greatest common divisor. For example, we already know that the greatest common divisor of 10 and 15 is 5. Let’s use Euclid’s method to verify this:
Notice that in the example above, the first number (10) was smaller than the second (15), and the first transformation just swapped the numbers, so the larger number was first. Thereafter the first number is always larger.
Now that we’ve gotten the preliminaries out of the way and have a basic mathematical explanation for how to calculate the greatest common divisor, we’ll take a look at how to translate this into code using the machinery of while loops that you’ve recently learned.
The way GCD is formulated above is, indeed, the most clever way to calculate the greatest common divisor. Yet the way we learn about the greatest common divisor in elementary school (at least at first) is to learn how to factor the numbers a and b, often in a brute force way. So for example, when calculating the greatest common divisor of 10 and 15, we can immediately see it, because we know that both of these numbers are divisible by 5 (e.g. 5 * 2 = 10 and 5 * 3 = 15). So the greatest common divisor is 5.
But if we had something more tricky to do like 810 and 729, we might have to think a bit more.
Before we learn to find the factors of numbers, we will often just “try” numbers until we get the greatest common divisor. This sort of trial process can take place in a loop, where we start at 1 and end at min(a, b). Why the minimum? We know that none of the values after the minimum can divide both a and b (in integer division), because no larger number can divide a smaller positive number. The smaller number would be the (non-zero) remainder.
Now take a look at a basic version of GCD:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /// Return the greatest common divisor of positive numbers.
public static int GreatestCommonDivisor (int a, int b)
{
int n = Math.Min (a, b);
int gcd = 1, i = 1;
while (i <= n) {
if (a % i == 0 && b % i == 0) {
gcd = i;
}
i++;
}
return gcd;
}
|
This code works as follows:
Math.Min(a, b)
.
This is how to compute the minimum of any two
values in C#. Technically, we don’t need to use the minimum of a and b,
but there is no
point in doing any more work than necessary.i
as the loop index, starting at 1.gcd
will hold the largest currently known common divisor.
We start with 1, which divides any integer, and we will
look for a higher value that also divides a and b.while (i <= n)
is used to indicate that we are
iterating the values of
i
until the minimum of a
and b
(computed earlier) is reached.if (a % i == 0 && b % i == 0)
is used to check whether we have found a
new value that replaces our previous candidate for the GCD.
A value can only be
a candidate for the GCD if it divides a and b without a remainder.
The modulus
operator %
is our way of determining whether there is a
remainder from the
division operation a / i
or b / i
.i++
is our way of going to the next value of i
to be tested as the new GCD.So this gives you a relatively straightforward way of calculating the greatest common divisor. While simple, it is not necessarily the most efficient way of determining the GCD. If you think about what is going on, this loop could run a significant number of times. For example, if you were calculating the GCD two very large numbers, say, one billion (1,000,000,000) and two billion (2,000,000,000) it is painfully evident that you would consider a large number of values (a billion, in fact) before obtaining the candidate GCD, which we know is 1,000,000,000.
The subtraction method (also attributable to Euclid) to compute the Greatest Common Divisor works as follows:
a
and b
in the right order.a
and b
are swapped as the first step to ensure that \(a > b\), after
which we can repeatedly divide to get the GCD.b
is to repeatedly subtract it (from a) until a
is no longer greater than b
.a
and
b
in the right order. Instead, we just write similar loops to
allow for the possibility of either order.a
and b
bump into one another, thereby meaning that we
have computed the GCD.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static int GreatestCommonDivisor (int a, int b)
{
int c;
while (a != b) {
while (a > b) {
c = a - b;
a = c;
}
while (b > a) {
c = b - a;
b = c;
}
}
return a;
}
|
A look at the source code more or less follows the above explanation.
Let’s start by looking at the inner loop at line 5, while (a >
b)
. In this loop, we are repeatedly subtracting b
from a
,
which we know we can do, because a
started out as being larger
than b
. At the end of loop a
is reduced to either
b
, in which case b
exactly divided the earlier a
,
and b
is the GCD, orb
, namely
\(a \bmod b\) (or in C# terms a % b
), and the process continues....The loop on line 9 is similar to the loop in line 5. For the same
reasons as we already explained, b
ends up equal to a
,
which is the GCD, or b
ends up as
\(b \bmod a\).
As discussed above, if a
and b
end up as the same number,
that is the GCD. On the other hand,
the first GCD algorithm example showed how remainders may need to be to
be calculated over and over. The outer loop in this version keeps this up
until a
and b
are reduced to equal
values. At this point the inner loops would make no further changes,
and the common value is the GCD.
As an exercise to the reader, you may want to consider adding some
Console.WriteLine()
statements to print the values of a
and
b
within each loop, and after both loops have executed. It will
allow you to see in visual terms how this method does its work.
There are several ways to code the shorter Euclidean algorithm at the beginning of this
GCD section. It is a repetitive pattern,
and a loop can be used. There are two parameters, a
and b
, to the gcd,
and they can be successively changed, suggesting a loop. What is the continuation
condition? You stop when b
is 0, so you continue while b != 0
.
The parameters a
and b
need to be replaced by b
and a % b
.
One extra variable needs to be introduced to make this double change work. The simplest
is to introduce a variable r
for the remainder. Check and see for yourself that you
need an extra variable like r
:
/// Return the greatest comon divisor of nonnegative numbers,
/// not both 0.
public static int GreatestCommonDivisor (int a, int b)
{
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
More verbose demonstration code, that prints the progress each time through loop is in g_c_d_remainder_loop/g_c_d_remainder_loop.cs.
The first statement of Euclid’s algorithm said (in C#) when
gcd(a, b) = gcd(b, a % b)
It is saying the result of the function with one set of parameters is equal to calling the function with another set of parameters. If we put this into a C# function definition, it would mean the instructions for the function say to call itself. This is a broadly useful technique called recursion, where a function calls itself inside its definition. We don’t expect you to master this technique immediately but do feel that it is important you at least hear about it and see its tremendous power:
/// Return the greatest comon divisor of nonnegative numbers,
/// not both 0.
public static int GreatestCommonDivisor (int a, int b)
{
if (b == 0) { // base
return a; // case
} else {
return GreatestCommonDivisor (b, a % b); // recursion
}
}
In g_c_d_euclid_recursive/g_c_d_euclid_recursive.cs is a wordier demonstration version that prints to the screen the progress at each recursive call.
The recursive version of the gcd
function refers to itself
by calling itself. Though this seems circular, you can see
from the examples that it works very well. The important point is that
the calls to the same function are not completely the same:
Successive calls have smaller second numbers, and the second
number eventually reaches 0, and in that case
there is a direct final answer. Hence the function is not really circular.
This recursive version is a much more direct translation of the original mathematical algorithm than the looping version!
The general idea of recursion is for a function to call itself with simpler parameters, until a simple enough place is reached, where the answer can be directly calculated.
[1] | The original brute-force gcd approach always goes through all the integers
between 1 and min(a, b) . There is a way to stop the first time
the real gcd is reached. How can you arrange that? |