Pular para o conteúdo

Conheça Walt Disney World

Binary GCD algorithm

The binary GCD algorithm, also known as Stein's algorithm, is an algorithm that computes the greatest common divisor of two nonnegative integers. It gains a measure of efficiency over the ancient Euclidean algorithm by replacing divisions and multiplications with shifts, which are cheaper when operating on the binary representation used by modern computers. This is particularly critical on embedded platforms that have no direct processor support for division. Although the algorithm was first published by the Israeli physicist and programmer Josef Stein in 1967,[1] it may have been known in 1st-century China.[2]

Contents

Algorithm

The algorithm reduces the problem of finding the GCD by repeatedly applying these identities:

  1. gcd(0, v) = v, because everything divides zero, and v is the largest number that divides v. Similarly, gcd(u, 0) = u. gcd(0, 0) is not typically defined, but it is convenient to set gcd(0, 0) = 0.
  2. If u and v are both even, then gcd(uv) = 2·gcd(u/2, v/2), because 2 is a common divisor.
  3. If u is even and v is odd, then gcd(uv) = gcd(u/2, v), because 2 is not a common divisor. Similarly, if u is odd and v is even, then gcd(uv) = gcd(uv/2).
  4. If u and v are both odd, and u ≥ v, then gcd(uv) = gcd((u − v)/2, v). If both are odd and u < v, then gcd(uv) = gcd((v − u)/2, u). These are combinations of one step of the simple Euclidean algorithm, which uses subtraction at each step, and an application of step 3 above. The division by 2 results in an integer because the difference of two odd numbers is even.[3]
  5. Repeat steps 2–4 until u = v, or (one more step) until u = 0. In either case, the GCD is 2kv, where k is the number of common factors of 2 found in step 2.

The algorithm requires O((log2 uv)2) worst-case time[citation needed], or in other words time proportional to the square of the number of bits in u and v together. Although each step reduces at least one of the operands by at least a factor of 2, the subtract and shift operations do not take constant time for very large integers (although they're still quite fast in practice, requiring about one operation per word of the representation).

An extended version of binary GCD, analogous to the extended Euclidean algorithm, is given in The Art of Computer Programming,[4] along with pointers to other versions.

Implementation

Recursive version in C++

Following is a recursive implementation of the algorithm in C++. The implementation is similar to the description of the algorithm given above. It use two arguments u and v. All but one of the recursive calls are tail recursive.

unsigned int gcd(unsigned int u, unsigned int v)
{
  // simple cases (termination)
  if (u == v)
    return u;
  if (u == 0)
    return v;
  if (v == 0)
    return u;
 
  // look for factors of 2
  if (~u & 1) // u is even
    if (v & 1) // v is odd
      return gcd(u >> 1, v);
    else // both u and v are even
      return gcd(u >> 1, v >> 1) << 1;
  if (~v & 1) // u is odd, v is even
    return gcd(u, v >> 1);
 
  // reduce larger argument
  if (u > v)
    return gcd((u - v) >> 1, v);
  return gcd((v - u) >> 1, u);
}

Iterative version in C

Following is an implementation of the algorithm in C, taking two (non-negative) integer arguments u and v. It first removes all common factors of 2 using identity 2, then computes the GCD of the remaining numbers using identities 3 and 4, and combines these to form the final answer.

 typedef unsigned long long uint64;
 uint64 gcd(uint64 u, uint64 v)
 {
     int shift;
 
     /* GCD(0,x) := x */
     if (u == 0 || v == 0)
       return u | v;
 
     /* Let shift := lg K, where K is the greatest power of 2
        dividing both u and v. */
     for (shift = 0; ((u | v) & 1) == 0; ++shift) {
         u >>= 1;
         v >>= 1;
     }
 
     while ((u & 1) == 0)
       u >>= 1;
 
     /* From here on, u is always odd. */
     do {
         while ((v & 1) == 0)  /* Loop X */
           v >>= 1;
 
         /* Now u and v are both odd, so diff(u, v) is even.
            Let u = min(u, v), v = diff(u, v)/2. */
         if (u < v) {
             v -= u;
         } else {
             uint64 diff = u - v;
             u = v;
             v = diff;
         }
         v >>= 1;
     } while (v != 0);
 
     return u << shift;
 }

Efficiency

Brigitte Vallée proved that binary GCD can be about 60% more efficient (in terms of the number of bit operations) on average than the Euclidean algorithm.[1][2].[5] However, although this algorithm outperforms the traditional Euclidean algorithm, its asymptotic performance is the same, and it is considerably more complex thanks to the availability of division instruction in all modern microprocessors.

In addition, real computers operate on more than one bit at a time, and even assembly language binary GCD implementations have to compete against carefully designed hardware circuits for integer division. Overall, Knuth (1998) reports a 15% gain over Euclidean GCD,[2] and according to one comparison, the greatest gain was about 60%, while on some popular architectures even good implementations of binary GCD were marginally slower than the Euclidean algorithm.[6]

In general, with implementations of binary GCD similar to the above C code, the gain in speed over the Euclidean algorithm is always less in practice than in theory. The reason is that the code uses many data-dependent branches. Many branches may be removed by computing min(a,b) and |a-b| using mixtures of Boolean algebra and arithmetic.

The only data-dependent branch that these techniques do not remove is the loop condition marked Loop X, which can be replaced by a single count trailing zeros (CTZ) operation and shift. Depending on platform, CTZ may be performed either by a single hardware instruction, by an equivalent instruction sequence, or with the aid of a lookup table.[6]

Historical description

An algorithm for computing the GCD of two numbers was described in the ancient Chinese mathematics book The Nine Chapters on the Mathematical Art. The original algorithm was used to reduce a fraction. The description reads:

"If possible halve it; otherwise, take the denominator and the numerator, subtract the lesser from the greater, and do that alternately to make them the same. Reduce by the same number."

This just looks like a normal Euclidian algorithm, but the ambiguity lies in the phrase "if possible halve it". The traditional interpretation is that this only works when 'both' numbers to begin with are even, under which the algorithm is just a slightly inferior Euclidean algorithm (for using subtraction instead of division). But the phrase may as well mean dividing by 2 should 'either' of the numbers become even, in which case it is the binary GCD algorithm.

See also

Notes

  1. ^ J. Stein: Computational problems associated with Racah algebra. Journal of Computational Physics, Vol. 1, No. 3, pp. 397–405, 1967. ISSN 0021-9991
  2. ^ a b Donald Knuth, The Art of Computer Programming, Volume 2: Seminumerical Algorithms (3rd Edition). Addison-Wesley.
  3. ^ In fact, the algorithm might be improved by the observation that if both u and v are odd, then exactly one of u + v or uv must be divisible by four. Specifically, assuming u ≥ v, if ((u xor vand 2) = 2, then gcd(uv) = gcd((u + v)/4, v), and otherwise gcd(uv) = gcd((u − v)/4, v).
  4. ^ Knuth (1998), answer to exercise 39 of section 4.5.2, p. 646
  5. ^ Notes on Programming by Alexander Stepanov
  6. ^ a b Faster implementations of binary GCD algorithm (download GCD.zip)

References

External links

Personal tools
  • Log in / create account
Namespaces
Variants
Actions
Navigation
Toolbox
Print/export