FLINT3.7.0-dev
Algorithm

Integer GCD

Cost
Euclid and Lehmer O(n2)O(n^2); half-GCD O(M(n)log⁡n)O(M(n) \log n) for nn-limb operands.

The greatest common divisor of two integers is computed by the Euclidean algorithm or one of its accelerations. For nn-limb operands the Euclidean algorithm takes O(nFLINT_BITS)O(n \mathtt{FLINT\_BITS}) division steps of decreasing size and O(n2)O(n^2) limb operations in total; Lehmer's algorithm keeps this complexity but replaces almost all multiprecision steps by word operations; and the half-GCD algorithm of Knuth and Schönhage reduces the cost to O(M(n)log⁡n)O(M(n) \log n). The same algorithms compute continued fractions, Bézout coefficients and rational reconstructions, which are all byproducts of the sequence of quotients. The polynomial analogue is described in Half-GCD.

The Euclidean algorithm as a product of matrices#

Starting from r0=a>r1=b>0r_0 = a > r_1 = b > 0, the Euclidean algorithm computes ri+1=ri−1−qirir_{i+1} = r_{i-1} - q_i r_i with qi=⌊ri−1/ri⌋q_i = \lfloor r_{i-1}/r_i \rfloor until rk+1=0r_{k+1} = 0, and then rk=gcd⁡(a,b)r_k = \gcd(a, b). In matrix form,

(ri−1ri)=(qi110)(riri+1),(ab)=Mi(riri+1),Mi=∏j≤i(qj110).\begin{pmatrix} r_{i-1} \\ r_i \end{pmatrix} = \begin{pmatrix} q_i & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} r_i \\ r_{i+1} \end{pmatrix}, \qquad \begin{pmatrix} a \\ b \end{pmatrix} = M_i \begin{pmatrix} r_i \\ r_{i+1} \end{pmatrix}, \quad M_i = \prod_{j \le i} \begin{pmatrix} q_j & 1 \\ 1 & 0 \end{pmatrix}.

The matrix MiM_i has nonnegative entries and determinant (−1)i(-1)^i, so its inverse is again an integer matrix, and its entries are the numerators and denominators of the convergents of the continued fraction a/b=[q1;q2,…,qk]a/b = [q_1; q_2, \ldots, q_k]. The second row of Mi−1M_i^{-1} gives the cofactors si,tis_i, t_i with sia+tib=ris_i a + t_i b = r_i; at i=ki = k these are Bézout coefficients. Each accelerated algorithm computes a product of many quotient matrices at once from leading parts of the operands, and applies it to the full operands.

The quotients that leading parts determine are characterised by a statement about continued fractions of real numbers. For a matrix MM as above, write M⋅y=(m11y+m12)/(m21y+m22)M \cdot y = (m_{11} y + m_{12})/(m_{21} y + m_{22}).

Lemma 1. Let M=∏j=1i(qj110)M = \prod_{j=1}^{i} \left(\begin{smallmatrix} q_j & 1 \\ 1 & 0 \end{smallmatrix}\right) with integers qj≥1q_j \ge 1. For every real y>1y > 1, the continued fraction expansion of x=M⋅yx = M \cdot y begins with q1,…,qiq_1, \ldots, q_i. In particular, all real numbers in M⋅YM \cdot Y, for a set YY of reals greater than 1, have these first ii partial quotients.

Proof. By induction on ii. For i=1i = 1, x=q1+1/yx = q_1 + 1/y with 0<1/y<10 < 1/y < 1, so ⌊x⌋=q1\lfloor x \rfloor = q_1. For i>1i > 1, x=q1+1/x′x = q_1 + 1/x' with x′=M′⋅yx' = M' \cdot y, where M′M' is the product of the last i−1i - 1 matrices. By induction x′x' begins with q2≥1q_2 \ge 1, and x′>q2≥1x' > q_2 \ge 1 since the rest of its expansion is positive; hence ⌊x⌋=q1\lfloor x \rfloor = q_1 and the expansion of xx continues with that of x′x'.

Corollary 2. Let a>b>0a > b > 0 and s≥0s \ge 0, and write a=ah2s+ala = a_h 2^s + a_l and b=bh2s+blb = b_h 2^s + b_l with 0≤al,bl<2s0 \le a_l, b_l < 2^s. Every partial quotient that is common to all real numbers in the open interval (ah/(bh+1),(ah+1)/bh)(a_h/(b_h + 1), (a_h + 1)/b_h) is a partial quotient of a/ba/b.

Proof. a/b>ah2s/((bh+1)2s)a/b > a_h 2^s / ((b_h + 1) 2^s) and a/b<(ah+1)2s/(bh2s)a/b < (a_h + 1) 2^s / (b_h 2^s), so a/ba/b lies in the interval.

Thus quotients can be computed from the top bits of aa and bb, as long as the computation checks that the quotients found are valid for the whole interval; by Lemma 1, it suffices to check that the transformed endpoints stay above 1.

Binary GCD#

Stein's binary algorithm [Ste1967] avoids divisions altogether: it removes common factors of 2, then repeatedly replaces the larger of two odd numbers by their difference with its factors of 2 removed. For single words this is the fastest method on current processors, since a subtraction, a count of trailing zeros and a shift are cheap. Stehlé and Zimmermann [SZ2004] gave a subquadratic "binary recursive" variant that works from the least significant bits; it is not used in FLINT.

Lehmer's algorithm#

Lehmer [Leh1938] observed that the first quotients of a/ba/b are almost always determined by the leading word of each operand. His algorithm runs the Euclidean algorithm on the leading words a^,b^\hat a, \hat b (taken from the same bit position of aa and bb), accumulating the quotient matrix in single words, stops as soon as a quotient might differ from that of the full numbers, and then applies the matrix to aa and bb with a few multiply-and-add passes. Each pass reduces the operands by about one word at a cost of O(n)O(n), so the total is O(n2)O(n^2) with a small constant.

Collins and Jebelean [Jeb1993b] showed that, with the remainders r^i\hat r_i and cofactors ui,viu_i, v_i of the single-word computation, the quotients q1,…,qiq_1, \ldots, q_{i} are those of a/ba/b if

r^i+1≥∣vi+1∣andr^i−r^i+1≥∣vi+1−vi∣\hat r_{i+1} \ge |v_{i+1}| \quad\text{and}\quad \hat r_i - \hat r_{i+1} \ge |v_{i+1} - v_i|

(with uu in place of vv for the other parity of ii). This is the interval condition of Corollary 2 written in terms of the cofactors. Using two words of each operand ("double-digit" Lehmer), as GMP does in mpn_hgcd2, roughly doubles the progress per pass.

The half-GCD algorithm#

The half-GCD of a>ba > b of nn limbs is a matrix MM such that (a,b)T=M(a′,b′)T(a, b)^T = M (a', b')^T, where a′,b′a', b' are consecutive remainders with about n/2n/2 limbs; the entries of MM then have about n/2n/2 limbs too. Knuth [Knu1970] and Schönhage [Sch1971] observed that it can be computed recursively from the top halves of aa and bb.

Algorithm 1: Half-GCD

Input: a>b>0a > b > 0 of nn limbs.

Output: a product MM of quotient matrices and (a′,b′)(a', b') with (a,b)T=M(a′,b′)T(a, b)^T = M (a', b')^T and a′a' of about n/2n/2 limbs.

  1. If nn is below a threshold, run Lehmer's algorithm until the remainders have n/2n/2 limbs and return.
  2. Let ss be about n/2n/2 limbs. Compute recursively the half-GCD M1M_1 of the top parts ah,bha_h, b_h (of about n/2n/2 limbs), and set (a1,b1)T=M1−1(a,b)T(a_1, b_1)^T = M_1^{-1} (a, b)^T; by Corollary 2, after removing the last one or two quotients if necessary, a1,b1a_1, b_1 are consecutive remainders of a,ba, b of about 3n/43n/4 limbs.
  3. Do one Euclidean step on (a1,b1)(a_1, b_1).
  4. Compute recursively the half-GCD M2M_2 of the top parts of the result, of about n/2n/2 limbs, and apply it as in step 2.
  5. Return M=M1QM2M = M_1 Q M_2, with QQ the matrix of step 3, and the remainders.

Each level does two recursive calls of half size and a constant number of products of n/2n/2-limb numbers and matrices, so the cost is H(n)=2H(n/2)+O(M(n))H(n) = 2H(n/2) + O(M(n)), which is O(M(n)log⁡n)O(M(n) \log n). The GCD is obtained by applying the half-GCD repeatedly, each time halving the size, at the same total cost. The correctness of the truncations in steps 2 and 4, which is delicate in Schönhage's original formulation, was simplified by Möller [Mol2008], whose version is the one in GMP. FLINT's own version formulates it with intervals as in Corollary 2, which makes the check of step 2 a comparison of the transformed endpoints with 1.

Contents and least common multiples of vectors#

The content of a vector (x1,…,xn)(x_1, \ldots, x_n), the GCD of its entries, can be computed as a chain of n−1n - 1 GCDs, but the chain is dominated by the latency of the GCDs when the entries are small. The running GCD gg can only decrease, and after a few entries it is usually the content. Testing each entry for divisibility by gg first, and computing a GCD only when the test fails, replaces most GCDs by divisibility tests: a test fails only when gg loses a prime factor, so at most once per prime factor of gg, counted with multiplicity. The tests are independent of each other, so they run at the throughput of the multiplier.

For word-size xx and g=2khg = 2^k h with hh odd, g∣xg \mid x if and only if the low kk bits of xx are zero and xh−1 mod B≤⌊(B−1)/h⌋x h^{-1} \bmod B \le \lfloor (B - 1)/h \rfloor, where BB is the word base: one multiplication and one comparison. If the test fails, the high word tt of (xh−1 mod B)⋅h(x h^{-1} \bmod B) \cdot h satisfies x≡−Bt(modh)x \equiv -B t \pmod h, so gcd⁡(x,h)=gcd⁡(t,h)\gcd(x, h) = \gcd(t, h) starts from an operand already reduced modulo hh. An entry of several words is reduced modulo a word-size hh with independent multiplications, by Hensel remainders or by folding its words with precomputed powers Bi mod hB^i \bmod h. For a multi-word gg, which is kept odd, the entries are tested by Hensel divisibility tests, or for long gg by division with remainder.

The order of the entries matters when the content is 1 or the entries are large. For a polynomial, the leading and trailing coefficients are the most likely to differ from the others in their factors, so the entries at both ends of the vector come first. Single-word entries come before larger ones, so that gg usually fits in a word before the large entries are reduced; among the large entries, the two shortest come first. Once gg has settled, the remaining tests can be split between threads.

The least common multiple uses the same idea. The running LCM LL can only grow, and when the entries are denominators that are all 1, all equal or all divisors of a common denominator, every entry divides LL. Each entry dd is therefore first tested for d∣Ld \mid L, and LL is updated only when the test fails. While LL fits in a word the tests need no division, and afterwards the small entries are tested in batches with one remainder computation of LL per batch.

Compared with chained GCDs and LCMs (fmpz_gcd, fmpz_lcm), for vectors of 1000 entries on one core of a Cascade Lake Xeon:

EntriesContentSpeedupEntriesSpeedup
62 bitsrandom (content 1)1.0all 15.0
62 bits16-bit content57equal, 62 bits18
62 bitscontent of half the bits38divisors of a 62-bit DD6.1
62 bits6-bit cofactors17di=id_i = i6.6
128 bits16-bit content2.2di=i!d_i = i!3.4
640 bitscontent of half the bits2.7random, 62 bits3.2

The gains are small where every entry needs a multiprecision GCD or division anyway: random entries with content 1, large entries with a word-size content, and random multi-word denominators.

Use in FLINT#

fmpz_gcd, fmpz_gcd_ui, fmpz_gcd3. An inline operand reduces the problem to GMP's mpn_gcd_1, which divides the large operand by the small one once and finishes with a binary GCD on words. Two large operands go to flint_mpz_gcd, that is flint_mpn_gcd, which sets aside the common power of two and calls _flint_mpn_gcd_inplace, a port of GMP's mpn_gcd [GMP] that uses FLINT's multiplication and division: Lehmer's algorithm with double-word steps (_flint_mpn_hgcd2) for small operands and Möller's half-GCD for large ones. Unlike GMP, each Lehmer step applies its word matrix to both operands in a single pass, and the 2×22 \times 2 matrix products of the half-GCD, whose entries share operands, are computed for large operands in transform space with fft_small (Arithmetic in transform space), so that each operand is transformed once and each result is transformed back once.

fmpz_xgcd, fmpz_xgcd_canonical_bezout, fmpz_gcdinv, fmpz_invmod. flint_mpz_gcdext and flint_mpz_invert, built on _flint_mpn_gcdext_inplace, the port of GMP's mpn_gcdext on the same half-GCD with cofactor tracking; unlike GMP, it accumulates the matrices of consecutive half-GCD steps from right to left before applying them to the cofactors, instead of updating the full-length cofactors after every step. For word-size operands, GMP's mpn_gcdext_1 (fmpz_xgcd_canonical_bezout) or FLINT's n_gcdinv (fmpz_gcdinv, fmpz_invmod) are used (GCD, LCM and Bézout coefficients). The modular inverses of mpn_mod call _flint_mpn_gcdext_inplace directly.

_fmpz_vec_content, _fmpz_vec_lcm and related functions. flint_mpn_gcd_vec_1 and flint_mpn_gcd_vec compute contents as in Contents and least common multiples of vectors, of single limbs and of multi-limb values given by pointer. _fmpz_vec_content, _fmpz_vec_content_chained, _fmpz_vec_lcm and _fmpz_vec_lcm_strided read the entries of an fmpz vector in place, small entries in blocks and large entries by pointer to their limbs, and use threads only for long vectors of large entries. They give the contents and canonical forms of fmpz_poly, fmpq_poly, fmpz_mat, fmpz_mpoly, fmpq_mpoly and fmpz_mpoly_q, and the common denominators of _fmpq_vec_get_fmpz_vec_fmpz and the fmpq_mat_get_fmpz_mat functions, whose numerators _fmpq_vec_get_fmpz_vec_scaled forms without divisions for denominators equal to the LCM or to 1.

n_gcd, n_xgcd, n_gcdinv. Word GCDs: n_gcd calls GMP's binary mpn_gcd_11; n_xgcd and n_gcdinv are FLINT's own extended Euclidean algorithms with special handling of small quotients.

fmpz_xgcd_partial. FLINT's own Lehmer algorithm with the Jebelean condition of Lehmer's algorithm, which stops when the remainder falls below a bound LL and returns the last two remainders and cofactors. It is the partial Euclidean algorithm of Shanks's NUCOMP and NUDUPL for binary quadratic forms in qfb, and is also used by Cornacchia's algorithm in ecpp.

_fmpq_hgcd and _fmpq_ball_get_cfrac. FLINT's own integer half-GCD in the interval formulation of Lemma 1: it maintains a matrix MM and a pair (xa,xb)(x_a, x_b) representing the interval M⋅(xa/(xb+1),(xa+1)/xb)M \cdot (x_a/(x_b + 1), (x_a + 1)/x_b), and generates quotients valid for the whole interval. It uses single Euclidean steps when the remaining size is below 4 limbs, a Lehmer algorithm with a two-limb half-GCD (_uiui_hgcd) up to 500 limbs, and above that recursion on the top halves as in Algorithm 1. It computes continued fractions (fmpq_get_cfrac), the simplest rational between two numbers (fmpq_simplest_between), Dedekind sums (fmpq_dedekind_sum) and rational reconstruction (fmpq_reconstruct_fmpz_2, which also has its own Lehmer loop for the final steps). Continued fractions of rational numbers and Rational reconstruction describe these applications.

void fmpz_gcd(fmpz_t f, const fmpz_t g, const fmpz_t h)
void fmpz_xgcd_partial(fmpz_t co2, fmpz_t co1, fmpz_t r2, fmpz_t r1, const fmpz_t L)
void _fmpz_vec_content(fmpz_t res, const fmpz * vec, slong len)
void _fmpz_vec_lcm(fmpz_t res, const fmpz * vec, slong len)
slong fmpq_get_cfrac(fmpz * c, fmpq_t rem, const fmpq_t x, slong n)
int fmpq_reconstruct_fmpz_2(fmpq_t res, const fmpz_t a, const fmpz_t m, const fmpz_t N, const fmpz_t D)

Referenced from#

References#

  • [Ste1967] J. Stein. Computational problems associated with Racah algebra. Journal of Computational Physics 1 (1967), 397–405. https://doi.org/10.1016/0021-9991(67)90047-2
  • [SZ2004] D. Stehlé and P. Zimmermann. A binary recursive gcd algorithm. Algorithmic Number Theory (ANTS VI), LNCS 3076 (2004), 411–425. https://doi.org/10.1007/978-3-540-24847-7_31
  • [Leh1938] D. H. Lehmer, "Euclid's algorithm for large numbers", American Mathematical Monthly 45:4 (1938), 227–233.
  • [Jeb1993b] T. Jebelean. Improving the multiprecision Euclidean algorithm. DISCO '93, LNCS 722 (1993), 45–58.
  • [Knu1970] D. E. Knuth, "The analysis of algorithms", Actes du Congrès International des Mathématiciens (Nice, 1970), tome 3, Gauthier-Villars (1971), 269–274
  • [Sch1971] A. Schönhage, "Schnelle Berechnung von Kettenbruchentwicklungen", Acta Informatica 1 (1971), 139–144
  • [Mol2008] N. Möller. On Schönhage's algorithm and subquadratic integer GCD computation. Mathematics of Computation 77 (2008), 589–607. https://doi.org/10.1090/S0025-5718-07-02017-0
  • [GMP] T. Granlund and the GMP development team. GNU MP: The GNU Multiple Precision Arithmetic Library, version 6.3.0. Manual, 2023. https://gmplib.org/manual/
Cite this page

The FLINT team, “Integer GCD,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/integers/gcd.html (generated 2026-11-02).