abrash-black-book/10-02.md
2013-12-30 20:26:41 +11:00

237 lines
5.6 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Not so nowadays, though. Computers love boring work; they're very
patient and disciplined, and, besides, one human year = seven dog years
= two zillion computer years. So when we're faced with a problem that
has an obvious but exceedingly lengthy solution, we're apt to say, "Ah,
let the computer do that, it's fast," and go back to making paper
airplanes. Unfortunately, brute-force solutions tend to be slow even
when performed by modern-day microcomputers, which are capable of
several MIPS except when I'm late for an appointment and want to finish
a compile and run just one more test before I leave, in which case the
crystal in my computer is apparently designed to automatically revert to
1 Hz.)
The solution that I instantly came up with to finding the GCD is about
as brute- force as you can get: Divide both the larger integer (iL) and
the smaller integer (iS) by every integer equal to or less than the
smaller integer, until a number is found that divides both evenly, as
shown in Figure 10.1. This works, but it's a lousy solution, requiring
as many as iS\*2 divisions; *very* expensive, especially for large
values of iS. For example, finding the GCD of 30,001 and 30,002 would
require 60,002 divisions, which alone, disregarding tests and branches,
would take about 2 seconds on an 8088, and more than 50 milliseconds
even on a 25 MHz 486—a *very* long time in computer years, and not
insignificant in human years either.
Listing 10.1 is an implementation of the brute-force approach to GCD
calculation. Table 10.1 shows how long it takes this approach to find
the GCD for several integer pairs. As expected, performance is extremely
poor when iS is large.
![](images/10-01.jpg)\
**Figure 10.1**  *Using a brute-force algorithm to find a GCD.*
* * * * *
Integer pairs for which to find GCD
90 & 27
42 & 998
453 & 121
27432 & 165
27432 & 17550
* * * * *
**Listing 10.1**\
(Brute force)
60µs\
(100%)
110µs\
(100%)
311ms\
(100%)
426µs\
(100%)
43580µs\
(100%)
**Listing 10.2**\
(Subtraction)
25\
(42%)
72\
(65%)
67\
(22%)
280\
(66%)
72\
(0.16%)
**Listing 10.3**\
(Division: code recursive\
Euclid's algorithm)
20\
(33%)
33\
(30%)
48\
(15%)
32\
(8%)
53\
(0.12%)
**Listing 10.4**\
(C version of data recursive Euclid's algorithm; normal optimization)
12\
(20%)
17\
(15%)
25\
(8%)
16\
(4%)
26\
(0.06%)
**Listing 10.4**\
(/Ox = maximumoptimization)
12\
(20%)
16\
(15)
20\
(6%)
15\
(4%)
23\
(0.05%)
**Listing 10.5**\
(Assembly version of data recursive Euclid's algorithm)
10\
(17%)
10\
(9%)
15\
(5%)
10\
(2%)
17\
(0.04%)
**Note:** Performance of Listings 10.1 through 10.5 in finding the
greatest common divisors of various pairs of integers. Times are in
microseconds. Percentages represent execution time as a percentage of
the execution time of Listing 10.1 for the same integer pair. Listings
10.1-10.4 were compiled with Microsoft C /C++ except as noted, the
default optimization was used. All times measured with the Zen timer
(from Chapter 3) on a 20 MHz cached 386.
* * * * *
Table 10.1 Performance of GCD algorithm implementations.
* * * * *
**LISTING 10.1 L10-1.C**
/* Finds and returns the greatest common divisor of two positive
integers. Works by trying every integral divisor between the
smaller of the two integers and 1, until a divisor that divides
both integers evenly is found. All C code tested with Microsoft
and Borland compilers.*/
unsigned int gcd(unsigned int int1, unsigned int int2) {
unsigned int temp, trial_divisor;
/* Swap if necessary to make sure that int1 >= int2 */
if (int1 < int2) {
temp = int1;
int1 = int2;
int2 = temp;
}
/* Now just try every divisor from int2 on down, until a common
divisor is found. This can never be an infinite loop because
1 divides everything evenly */
for (trial_divisor = int2; ((int1 % trial_divisor) != 0) ||
((int2 % trial_divisor) != 0); trial_divisor—)
;
return(trial_divisor);
}
#### Wasted Breakthroughs {#Heading5}
Sedgewick's first solution to the GCD problem was pretty much the one I
came up with. He then pointed out that the GCD of iL and iS is the same
as the GCD of iL-iS and iS. This was obvious (once Sedgewick pointed it
out); by the very nature of division, any number that divides iL evenly
nL times and iS evenly nS times must divide iL-iS evenly nL-nS times.
Given that insight, I immediately designed a new, faster approach, shown
in Listing 10.2.
**LISTING 10.2 L10-2.C**
/* Finds and returns the greatest common divisor of two positive
integers. Works by subtracting the smaller integer from the
larger integer until either the values match (in which case
that's the gcd), or the larger integer becomes the smaller of
the two, in which case the two integers swap roles and the
subtraction process continues. */
unsigned int gcd(unsigned int int1, unsigned int int2) {
unsigned int temp;
/* If the two integers are the same, that's the gcd and we're
done */
if (int1 == int2) {
return(int1);
}
/* Swap if necessary to make sure that int1 >= int2 */
if (int1 < int2) {
temp = int1;
int1 = int2;
int2 = temp;
}
/* Subtract int2 from int1 until int1 is no longer the larger of
the two */
do {
int1 -= int2;
} while (int1 > int2);
/* Now recursively call this function to continue the process */
return(gcd(int1, int2));
}