130 lines
6.4 KiB
Markdown
130 lines
6.4 KiB
Markdown
---
|
||
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
||
author: Michael Abrash
|
||
date: '1997-07-01'
|
||
isbn: '1576101746'
|
||
publisher: The Coriolis Group
|
||
category: 'Web and Software Development: Game Development,Web and Software Development:
|
||
Graphics and Multimedia Development'
|
||
chapter: '10'
|
||
pages: 193-196
|
||
---
|
||
|
||
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.
|
||
|
||

|
||
|
||
**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%) |
|
||
|
||
Table: Table 10.1 Performance of GCD algorithm implementations.
|
||
|
||
**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.
|
||
|
||
**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));
|
||
}
|