FLINT3.7.0-dev

Greatest common divisors

Synopsis#include <flint/nmod_poly.h>
slong _nmod_poly_gcd(nn_ptr G, nn_srcptr A, slong lenA, nn_srcptr B, slong lenB, nmod_t mod)source
void nmod_poly_gcd(nmod_poly_t G, const nmod_poly_t A, const nmod_poly_t B)source

Set G to a greatest common divisor of A and B. The underscore version requires \mathit{lenA} \ge \mathit{lenB} \ge 1, writes at most \mathit{lenB} coefficients, returns the length of G, does not make G monic and does not allow aliasing.

_nmod_poly_gcd uses the half-GCD algorithm if \mathit{lenB} is at least nmod_poly_gcd_hgcd_cutoff(mod), and the Euclidean algorithm otherwise, with Montgomery arithmetic when NMOD_POLY_GCD_EUCLIDEAN_USE_REDC_FAST holds.

slong _nmod_poly_hgcd(nn_ptr * M, slong * lenM, nn_ptr A, slong * lenA, nn_ptr B, slong * lenB, nn_srcptr a, slong lena, nn_srcptr b, slong lenb, nmod_t mod)source
slong nmod_poly_hgcd(nmod_poly_t m11, nmod_poly_t m12, nmod_poly_t m21, nmod_poly_t m22, nmod_poly_t A, nmod_poly_t B, const nmod_poly_t a, const nmod_poly_t b)source

The half-GCD step. For \deg a > \deg b, compute consecutive remainders A, B of the Euclidean remainder sequence of a, b with \deg A \ge \deg(a)/2 > \deg B and the matrix

M = \begin{pmatrix} m_{11} & m_{12} \\ m_{21} & m_{22} \end{pmatrix}, \qquad \begin{pmatrix} a \\ b \end{pmatrix} = M \begin{pmatrix} A \\ B \end{pmatrix},

the product of the matrices \left(\begin{smallmatrix} q_i & 1 \\ 1 & 0 \end{smallmatrix}\right) for the quotients q_i of these steps. Return \det M = \pm 1.

nmod_poly_hgcd throws FLINT_ERROR unless \deg a > \deg b; for b = 0 it returns the identity matrix. The underscore version requires \mathit{lena} > \mathit{lenb} \ge 1; A and B and the four arrays M[0], ..., M[3] (for m_{11}, m_{12}, m_{21}, m_{22}) need space for lena coefficients, and the actual lengths are written to lenA, lenB and lenM. It allows A == a and B == b. Below nmod_poly_hgcd_iter_recursive_cutoff(mod) the recursion is replaced by Euclidean steps. See Half-GCD.

slong _nmod_poly_xgcd(nn_ptr G, nn_ptr S, nn_ptr T, nn_srcptr A, slong lenA, nn_srcptr B, slong lenB, nmod_t mod)source
void nmod_poly_xgcd(nmod_poly_t G, nmod_poly_t S, nmod_poly_t T, const nmod_poly_t A, const nmod_poly_t B)source

Compute the GCD G of A and B and cofactors S, T with SA + TB = G. The non-underscore version makes G monic; for B \ne 0 the cofactors satisfy \deg S < \max(\deg B - \deg G, 1) and \deg T < \max(\deg A - \deg G, 1), and for B = 0 it returns S = \operatorname{lc}(A)^{-1}, T = 0. G, S and T must be distinct objects.

The underscore version requires \mathit{lenA} \ge \mathit{lenB} \ge 1 and space for \mathit{lenB} coefficients in G, \max(\mathit{lenB} - 1, 2) in S and \max(\mathit{lenA} - 1, 2) in T, all of which may be written; it returns the length of G, does not make G monic and does not allow aliasing. It uses the half-GCD algorithm if \mathit{lenB} \ge nmod_poly_xgcd_hgcd_cutoff(mod) and the Euclidean algorithm otherwise.

slong _nmod_poly_gcdinv(ulong * G, ulong * S, const ulong * A, slong lenA, const ulong * B, slong lenB, const nmod_t mod)source
void nmod_poly_gcdinv(nmod_poly_t G, nmod_poly_t S, const nmod_poly_t A, const nmod_poly_t B)source

Compute G = \gcd(A, B) and S with G \equiv SA \pmod B and \deg S < \deg B - \deg G, without the cofactor of B. If \gcd(A, B) = 1, then S = A^{-1} \bmod B. The non-underscore version requires \mathit{lenB} \ge 2 (otherwise it throws FLINT_ERROR), reduces A modulo B first, makes G monic and returns G = S = 0 if B divides A. The underscore version requires 1 \le \mathit{lenA} < \mathit{lenB}, writes at most lenA coefficients to G and \mathit{lenB} - 1 to S, returns the length of G and does not make G monic.

Over a field \mathbb{F}_p, every pair of polynomials has a greatest common divisor, unique up to a constant factor, and the non-underscore functions return the monic one. nmod_poly_gcd computes G = \gcd(A, B), nmod_poly_xgcd also the cofactors S, T with SA + TB = G, and nmod_poly_gcdinv only the cofactor S of A, which is what is needed for inverses modulo B. The modulus must be prime; over \mathbb{Z}/n\mathbb{Z} with composite n the Euclidean algorithm can meet a non-invertible leading coefficient, which throws an exception.

AlgorithmHalf-GCDThe half-gcd algorithm computes the quotients of the first half of the Euclidean remainder sequence from the top halves of the inputs, recursively, giving GCDs, extended GCDs and resultants in O(M(n) \log n) operations; with a correctness proof and FLINT's cutoffs for nmod_poly.

Algorithms#

The Euclidean algorithm replaces (A, B) by (B, A \bmod B) until the remainder is zero. In the typical case each step lowers the degree by one and costs O(\deg B) operations, for O(\deg A \deg B) in total. FLINT's implementation calls _nmod_poly_rem, which has a dedicated routine for quotients of length 2, and for moduli with at least half the bits of a word (but at most \mathtt{FLINT\_BITS} - 2 bits) and \deg B \ge 31 it runs in Montgomery arithmetic (_nmod_poly_gcd_euclidean_redc_fast), where the long chains of dependent multiplications are cheaper.

The half-GCD algorithm [ThullYap1990] uses the fact that the quotients of the first half of the Euclidean algorithm depend only on the top half of the coefficients. It computes them recursively from the top coefficients and applies them to the full polynomials as a 2 \times 2 matrix of polynomials, so that the whole remainder sequence costs O(M(n) \log n) operations. nmod_poly_hgcd exposes one such step: it returns the matrix M and the two consecutive remainders A, B of the sequence that straddle half the degree of the input.

nmod_poly_gcd uses the half-GCD algorithm for \mathit{lenB} \ge nmod_poly_gcd_hgcd_cutoff(mod), between 470 and 2199 depending on the bit size of n, and nmod_poly_xgcd for \mathit{lenB} \ge nmod_poly_xgcd_hgcd_cutoff(mod), between 72 and 388; the cofactors make the Euclidean algorithm more expensive, so the extended version switches earlier.

Examples#

Two random polynomials of degree 2500 modulo a 50-bit prime, multiplied by a common factor of degree 500; both algorithms find the factor, made monic, and the cofactors of nmod_poly_xgcd satisfy Bézout's identity:

Examplenmod_poly_gcd_large.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/ulong_extras.h>
#include <flint/nmod_poly.h>

int main(void)
{
    flint_rand_t state;
    nmod_poly_t a, b, c, g, h, s, t, u;
    ulong p = n_nextprime(UWORD(1) << 49, 1);

    flint_rand_init(state);
    nmod_poly_init(a, p); nmod_poly_init(b, p); nmod_poly_init(c, p);
    nmod_poly_init(g, p); nmod_poly_init(h, p);
    nmod_poly_init(s, p); nmod_poly_init(t, p); nmod_poly_init(u, p);

    nmod_poly_rand_monic(c, state, 501);
    nmod_poly_rand(a, state, 2501);
    nmod_poly_rand(b, state, 2501);
    nmod_poly_mul(a, a, c);
    nmod_poly_mul(b, b, c);

    nmod_poly_gcd(g, a, b);               /* half-GCD at this length */
    nmod_poly_gcd_euclidean(h, a, b);
    flint_printf("deg gcd = %wd\n", nmod_poly_degree(g));
    flint_printf("hgcd == euclidean: %d\n", nmod_poly_equal(g, h));
    flint_printf("gcd == c: %d\n", nmod_poly_equal(g, c));

    nmod_poly_xgcd(h, s, t, a, b);
    nmod_poly_mul(s, s, a);
    nmod_poly_mul(t, t, b);
    nmod_poly_add(u, s, t);
    flint_printf("s a + t b == g: %d\n", nmod_poly_equal(u, h));

    nmod_poly_clear(a); nmod_poly_clear(b); nmod_poly_clear(c);
    nmod_poly_clear(g); nmod_poly_clear(h);
    nmod_poly_clear(s); nmod_poly_clear(t); nmod_poly_clear(u);
    flint_rand_clear(state);
    return 0;
}
Output
deg gcd = 500
hgcd == euclidean: 1
gcd == c: 1
s a + t b == g: 1

The common factor is found exactly because the random cofactors are coprime, which happens with probability 1 - 1/p.

nmod_poly_gcdinv returns the inverse of A modulo B together with the GCD, so that one call both tests and inverts. Modulo 7, x^2 + 1 is invertible modulo x^3 + x + 1 but not modulo x^3 + x^2 + x + 1 = (x + 1)(x^2 + 1):

Examplenmod_poly_gcdinv.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/nmod_poly.h>

int main(void)
{
    nmod_poly_t a, b, g, s, t;

    nmod_poly_init(a, 7);
    nmod_poly_init(b, 7);
    nmod_poly_init(g, 7);
    nmod_poly_init(s, 7);
    nmod_poly_init(t, 7);

    nmod_poly_set_str(a, "3 7  1 0 1");          /* x^2 + 1 */
    nmod_poly_set_str(b, "4 7  1 1 0 1");        /* x^3 + x + 1 */
    nmod_poly_gcdinv(g, s, a, b);
    flint_printf("g = "); nmod_poly_print_pretty(g, "x");
    flint_printf(", s = "); nmod_poly_print_pretty(s, "x");
    nmod_poly_mulmod(t, s, a, b);
    flint_printf(", s a mod b = "); nmod_poly_print_pretty(t, "x");
    flint_printf("\n");

    nmod_poly_set_str(b, "4 7  1 1 1 1");        /* (x + 1)(x^2 + 1) */
    nmod_poly_gcdinv(g, s, a, b);
    flint_printf("g = "); nmod_poly_print_pretty(g, "x");
    flint_printf(", s = "); nmod_poly_print_pretty(s, "x");
    flint_printf("\n");

    nmod_poly_clear(a);
    nmod_poly_clear(b);
    nmod_poly_clear(g);
    nmod_poly_clear(s);
    nmod_poly_clear(t);
    return 0;
}
Output
g = 1, s = 6*x, s a mod b = 1
g = x^2+1, s = 1

Guides#

References#

  • [ThullYap1990] Thull, K. and Yap, C. : A Unified Approach to HGCD Algorithms for Polynomials and Integers, (1990)
Cite this page

The FLINT team, “Greatest common divisors (nmod_poly.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/nmod_poly/gcd.html (generated 2026-10-20).