Algorithm for computing the greatest common divisor
The binary GCD algorithm, also known as Stein's algorithm or the binary Euclidean algorithm,[1][2] is an algorithm that computes the greatest common divisor (GCD) of two nonnegative integers. Stein's algorithm uses simpler arithmetic operations than the conventional Euclidean algorithm; it replaces division with arithmetic shifts, comparisons, and subtraction.
Although the algorithm in its contemporary form was first published by the physicist and programmer Josef Stein in 1967,[3] it was known by the 2nd century BCE, in ancient China.[4]
Algorithm
The algorithm finds the GCD of two nonnegative numbers and by repeatedly applying these identities:
: everything divides zero, and is the largest number that divides .
: is a common divisor.
if is odd: is then not a common divisor.
if odd and .
As GCD is commutative (), those identities still apply if the operands are swapped: , if is odd, etc.
Implementation
While the above description of the algorithm is mathematically correct, performant software implementations typically differ from it in a few notable ways:
eschewing trial division by in favour of a single bitshift and the count trailing zeros primitive; this is functionally equivalent to repeatedly applying identity 3, but much faster;
expressing the algorithm iteratively rather than recursively: the resulting implementation can be laid out to avoid repeated work, invoking identity 2 at the start and maintaining as invariant that both numbers are odd upon entering the loop, which only needs to implement identities 3 and 4;
making the loop's body branch-free except for its exit condition (): in the example below, the exchange of and (ensuring ) compiles down to conditional moves;[5] hard-to-predict branches can have a large, negative impact on performance.[6][7]
The following is an implementation of the algorithm in Rust exemplifying those differences, adapted from uutils:
usestd::cmp::min;usestd::mem::swap;pubfngcd(mutu: u64,mutv: u64)-> u64{// Base cases: gcd(n, 0) = gcd(0, n) = nifu==0{returnv;}elseifv==0{returnu;}// Using identities 2 and 3:// gcd(2â± u, 2ÊČ v) = 2á” gcd(u, v) with u, v odd and k = min(i, j)// 2á” is the greatest power of two that divides both 2â± u and 2ÊČ vleti=u.trailing_zeros();u>>=i;letj=v.trailing_zeros();v>>=j;letk=min(i,j);loop{// u and v are odd at the start of the loopdebug_assert!(u%2==1,"u = {} should be odd",u);debug_assert!(v%2==1,"v = {} should be odd",v);// Swap if necessary so u †vifu>v{swap(&mutu,&mutv);}// Identity 4: gcd(u, v) = gcd(u, v-u) as u †v and u, v are both odd v-=u;// v is now evenifv==0{// Identity 1: gcd(u, 0) = u// The shift by k is necessary to add back the 2á” factor that was removed before the loopreturnu<<k;}// Identity 3: gcd(u, 2ÊČ v) = gcd(u, v) as u is oddv>>=v.trailing_zeros();}}
Note: The implementation above accepts unsigned (non-negative) integers; given that , the signed case can be handled as follows:
/// Computes the GCD of two signed 64-bit integers/// The result is unsigned and not always representable as i64: gcd(i64::MIN, i64::MIN) == 1 << 63pubfnsigned_gcd(u: i64,v: i64)-> u64{gcd(u.unsigned_abs(),v.unsigned_abs())}
Complexity
Asymptotically, the algorithm requires steps, where is the number of bits in the larger of the two numbers, as every two steps reduce at least one of the operands by at least a factor of . Each step involves only a few arithmetic operations ( with a small constant); when working with word-sized numbers, each arithmetic operation translates to a single machine operation, so the number of machine operations is on the order of , i.e. .
For arbitrarily large numbers, the asymptotic complexity of this algorithm is ,[8] as each arithmetic operation (subtract and shift) involves a linear number of machine operations (one per word in the numbers' binary representation).
If the numbers can be represented in the machine's memory, i.e. each number's size can be represented by a single machine word, this bound is reduced to:
The binary GCD algorithm can be extended in several ways, either to output additional information, deal with arbitrarily large integers more efficiently, or to compute GCDs in domains other than the integers.
In the case of large integers, the best asymptotic complexity is , with the cost of -bit multiplication; this is near-linear and vastly smaller than the binary GCD algorithm's , though concrete implementations only outperform older algorithms for numbers larger than about 64 kilobits (i.e. greater than 8Ă1019265). This is achieved by extending the binary GCD algorithm using ideas from the SchönhageâStrassen algorithm for fast integer multiplication.[13]
An algorithm for computing the GCD of two numbers was known in ancient China, under the Han dynasty, as a method to reduce fractions:
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.
^DamgĂ„rd, Ivan Bjerre; Frandsen, Gudmund Skovbjerg (12â15 August 2003). Efficient Algorithms for GCD and Cubic Residuosity in the Ring of Eisenstein Integers. 14th International Symposium on the Fundamentals of Computation Theory. Malmö, Sweden. pp. 109â117. doi:10.1007/978-3-540-45077-1_11.
^Agarwal, Saurabh; Frandsen, Gudmund Skovbjerg (13â18 June 2004). Binary GCD Like Algorithms for Some Complex Quadratic Rings. Algorithmic Number Theory Symposium. Burlington, VT, USA. pp. 57â71. doi:10.1007/978-3-540-24847-7_4.
^Agarwal, Saurabh; Frandsen, Gudmund Skovbjerg (20â24 March 2006). A New GCD Algorithm for Quadratic Number Rings with Unique Factorization. 7th Latin American Symposium on Theoretical Informatics. Valdivia, Chile. pp. 30â42. doi:10.1007/11682462_8.
^Wikström, Douglas (11â15 July 2005). On the l-Ary GCD-Algorithm in Rings of Integers. Automata, Languages and Programming, 32nd International Colloquium. Lisbon, Portugal. pp. 1189â1201. doi:10.1007/11523468_96.