26
$\begingroup$

I know that Euclid’s algorithm is the best algorithm for getting the GCD (great common divisor) of a list of positive integers. But in practice you can code this algorithm in various ways. (In my case, I decided to use Java, but C/C++ may be another option).

I need to use the most efficient code possible in my program.

In recursive mode, you can write:

static long gcd (long a, long b){
    a = Math.abs(a); b = Math.abs(b);
    return (b==0) ? a : gcd(b, a%b);
  }

And in iterative mode, it looks like this:

static long gcd (long a, long b) {
  long r, i;
  while(b!=0){
    r = a % b;
    a = b;
    b = r;
  }
  return a;
}

There is also the Binary algorithm for the GCD, which may be coded simply like this:

int gcd (int a, int b)
{
    while(b) b ^= a ^= b ^= a %= b;
    return a;
}
$\endgroup$
  • 3
    $\begingroup$ I think this is too subjective, and perhaps even better suited for StackOverflow. "Most efficient in practice" depends on many (even unpredictable) factors, such as the underlying architechture, memory hierarchy, size and form of the input etc. $\endgroup$ – Juho Apr 22 '12 at 18:35
  • 5
    $\begingroup$ This is the same algorithm expressed in recursive and iterative ways. I think their difference is negligible since Euclid algorithm converges pretty fast. Choose one that fits your preference. $\endgroup$ – pad Apr 22 '12 at 19:15
  • 6
    $\begingroup$ You might want to try profiling these two. Since the recursive version is a tail call, it is not unlikely that the compiler actually emits almost the same code. $\endgroup$ – Louis Apr 22 '12 at 19:25
  • 1
    $\begingroup$ this is wrong. should be while b != 0, and then return a. Otherwise it bugs out on division by zero. also don't use recursion if you have really big gcds....you get a pile of stack and function states...why not just go iterative? $\endgroup$ – Cris Stringfellow Nov 15 '12 at 12:34
  • 4
    $\begingroup$ Note that there are asymptotically faster GCD algorithms. E.g. en.wikipedia.org/wiki/Binary_GCD_algorithm $\endgroup$ – Neal Young Nov 21 '12 at 17:12
21
$\begingroup$

Your two algorithms are equivalent (at least for positive integers, what happens with negative integers in the imperative version depends on Java's semantics for % which I don't know by heart). In the recursive version, let $a_i$ and $b_i$ be the argument of the $i$th recursive call: $$\begin{gather*} a_{i+1} = b_i \\ b_{i+1} = a_i \mathbin{\mathrm{mod}} b_i \\ \end{gather*}$$

In the imperative version, let $a'_i$ and $b'_i$ be the values of the variables a and b at the beginning of the $i$th iteration of the loop. $$\begin{gather*} a'_{i+1} = b'_i \\ b'_{i+1} = a'_i \mathbin{\mathrm{mod}} b'_i \\ \end{gather*}$$

Notice a resemblance? Your imperative version and your recursive version are calculating exactly the same values. Furthermore, they both end at the same time, when $a_i=0$ (resp. $a'_i=0$), so they perform the same number of iterations. So algorithmically speaking, there is no difference between the two. Any difference will be a matter of implementation, highly dependent on the compiler, the hardware it runs on, and quite possibly the operating system and what other programs are running concurrently.

The recursive version makes only tail recursive calls. Most compilers for imperative languages do not optimize these, and so it is likely that the code they generate will waste a little time and memory constructing a stack frame at each iteration. With a compiler that optimizes tail calls (compilers for functional languages almost always do), the generated machine code may well be the same for both (assuming you harmonize those calls to abs).

$\endgroup$
8
$\begingroup$

For numbers that are small, the binary GCD algorithm is sufficient.

GMP, a well maintained and real-world tested library, will switch to a special half GCD algorithm after passing a special threshold, a generalization of Lehmer's Algorithm. Lehmer's uses matrix multiplication to improve upon the standard Euclidian algorithms. According to the docs, the asymptotic running time of both HGCD and GCD is O(M(N)*log(N)), where M(N) is the time for multiplying two N-limb numbers.

Full details on their algorithm can be found here.

$\endgroup$
2
$\begingroup$

As I know Java doesn’t support tail recursion optimization in general, but you can test your Java implementation for it; if it doesn’t support it, a simple for-loop should be faster, otherwise recursion should be just as fast. On the other hand, these are bit optimizations, choose the code you think is easier and more readable.

I should also note that the fastest GCD algorithm is not Euclid’s algorithm, Lehmer’s algorithm is a bit faster.

$\endgroup$
  • $\begingroup$ Do you mean as far as I know? Do you mean the language specification does not mandate this optimisation (it would be surprising if it did), or that most implementations do not implement it? $\endgroup$ – PJTraill May 25 '18 at 21:59
1
$\begingroup$

First, don't use recursivity to replace a tight loop. It is slow. Don't rely on the compiler to optimize it out. Second, in your code, you call Math.abs() within every recursive calls, which is useless.

In your loop, you can easily avoid temporary variables and swapping a and b all the time.

int gcd(int a, int b){
    if( a<0 ) a = -a;
    if( b<0 ) b = -b;
    while( b!=0 ){
        a %= b;
        if( a==0 ) return b;
        b %= a;
    }
    return a;
}

Swapping using the a ^= b ^= a ^= b makes the source shorter but takes many instructions to execute. It will be slower than the boring swap with a temporary variable.

$\endgroup$
  • 3
    $\begingroup$ “Avoid recursivity. It is slow” — presented as general advice, this is bogus. It depends on the compiler. Usually, even with compilers that don't optimize recursion, it isn't slow, just stack-consuming. $\endgroup$ – Gilles 'SO- stop being evil' Jun 2 '15 at 20:48
  • 3
    $\begingroup$ But for short code like this, the difference is significant. Stack-consuming means writing to and reading from memory. That is slow. The code above runs on 2 registers. Recursivity also means doing calls, which is longer than a conditional jump. A recursive call is much harder for branch prediction and harder to inline. $\endgroup$ – Florian F Jun 2 '15 at 21:28
-2
$\begingroup$

For small numbers, % is quite an expensive operation, perhaps the simpler recursive

GCD[a,b] := Which[ 
   a==b , Return[a],
   b > a, Return[ GCD[a, b-a]],
   a > b, Return[ GCD[b, a-b]]
];

is quicker? (Sorry, Mathematica code and not C++)

$\endgroup$
  • $\begingroup$ It doesn't look right. For b==1, it should return 1. And GCD[2,1000000000] will be slow. $\endgroup$ – Florian F Aug 31 '14 at 20:55
  • $\begingroup$ Ah, yes, I made a mistake. Fixed (I think), and clarified. $\endgroup$ – Per Alexandersson Aug 31 '14 at 20:58
  • $\begingroup$ Normally, GCD[a,0] should also return a. Yours loops forever. $\endgroup$ – Florian F Aug 31 '14 at 21:19
  • $\begingroup$ I'm downvoting as your answer only contains code. We like to focus on ideas on this site. For instance, why is % an expensive operation? Speculation on a piece of code is not really a good answer for this site, in my opinion. $\endgroup$ – Juho Jun 2 '15 at 19:26
  • 1
    $\begingroup$ I think the idea that modulo is slower than subtraction can be considered folklore. It holds both for small integers (subtraction usually takes one cycle, modulo rarely does) and for large integers (subtraction is linear, I'm not sure what the best complexity is for modulo but it's definitely worse than that). Of course you also need to consider the number of necessary iterations. $\endgroup$ – Gilles 'SO- stop being evil' Jun 2 '15 at 20:46
-2
$\begingroup$

Euclid Algorithm is most efficient for calculating GCD:

Static long gcd(long a,long b)
{
if(b==0)
return a;
else
return gcd(,a%b);
}

example:-

Let A = 16, B = 10.
GCD(16, 10) = GCD(10, 16 % 10) = GCD(10, 6)
GCD(10, 6) = GCD(6, 10 % 6) = GCD(6, 4)
GCD(6, 4) = GCD(4, 6 % 4) = GCD(4, 2)
GCD(4, 2) = GCD(2, 4 % 2) = GCD(2, 0)


Since B = 0 so GCD(2, 0) will return 2. 
$\endgroup$
  • 4
    $\begingroup$ This doesn't answer the question. The asker presents two versions of Euclid and asks which is faster. You don't seem to have noticed that and just declare the recursive version to be the only Euclid's algorithm, and assert with no evidence whatsoever that it's faster than everything else. $\endgroup$ – David Richerby Oct 15 '16 at 10:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.