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 GG to a greatest common divisor of AA and BB. The underscore version requires lenA≥lenB≥1\mathit{lenA} \ge \mathit{lenB} \ge 1, writes at most lenB\mathit{lenB} coefficients, returns the length of GG, does not make GG monic and does not allow aliasing.

_nmod_poly_gcd uses the half-GCD algorithm if lenB\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\deg a > \deg b, compute consecutive remainders A,BA, B of the Euclidean remainder sequence of a,ba, b with deg⁡A≥deg⁡(a)/2>deg⁡B\deg A \ge \deg(a)/2 > \deg B and the matrix

M=(m11m12m21m22),(ab)=M(AB),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 (qi110)\left(\begin{smallmatrix} q_i & 1 \\ 1 & 0 \end{smallmatrix}\right) for the quotients qiq_i of these steps. Return det⁡M=±1\det M = \pm 1.

nmod_poly_hgcd throws FLINT_ERROR unless deg⁡a>deg⁡b\deg a > \deg b; for b=0b = 0 it returns the identity matrix. The underscore version requires lena>lenb≥1\mathit{lena} > \mathit{lenb} \ge 1; A and B and the four arrays M[0], ..., M[3] (for m11,m12,m21,m22m_{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 GG of AA and BB and cofactors S,TS, T with SA+TB=GSA + TB = G. The non-underscore version makes GG monic; for B≠0B \ne 0 the cofactors satisfy deg⁡S<max⁡(deg⁡B−deg⁡G,1)\deg S < \max(\deg B - \deg G, 1) and deg⁡T<max⁡(deg⁡A−deg⁡G,1)\deg T < \max(\deg A - \deg G, 1), and for B=0B = 0 it returns S=lc⁡(A)−1S = \operatorname{lc}(A)^{-1}, T=0T = 0. GG, SS and TT must be distinct objects.

The underscore version requires lenA≥lenB≥1\mathit{lenA} \ge \mathit{lenB} \ge 1 and space for lenB\mathit{lenB} coefficients in GG, max⁡(lenB−1,2)\max(\mathit{lenB} - 1, 2) in SS and max⁡(lenA−1,2)\max(\mathit{lenA} - 1, 2) in TT, all of which may be written; it returns the length of GG, does not make GG monic and does not allow aliasing. It uses the half-GCD algorithm if lenB≥\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)G = \gcd(A, B) and SS with G≡SA(modB)G \equiv SA \pmod B and deg⁡S<deg⁡B−deg⁡G\deg S < \deg B - \deg G, without the cofactor of BB. If gcd⁡(A,B)=1\gcd(A, B) = 1, then S=A−1 mod BS = A^{-1} \bmod B. The non-underscore version requires lenB≥2\mathit{lenB} \ge 2 (otherwise it throws FLINT_ERROR), reduces AA modulo BB first, makes GG monic and returns G=S=0G = S = 0 if BB divides AA. The underscore version requires 1≤lenA<lenB1 \le \mathit{lenA} < \mathit{lenB}, writes at most lenA coefficients to GG and lenB−1\mathit{lenB} - 1 to SS, returns the length of GG and does not make GG monic.

Over a field Fp\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)G = \gcd(A, B), nmod_poly_xgcd also the cofactors S,TS, T with SA+TB=GSA + TB = G, and nmod_poly_gcdinv only the cofactor SS of AA, which is what is needed for inverses modulo BB. The modulus must be prime; over Z/nZ\mathbb{Z}/n\mathbb{Z} with composite nn 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)O(M(n) \log n) operations; with a correctness proof and FLINT's cutoffs for nmod_poly.

Algorithms#

The Euclidean algorithm replaces (A,B)(A, B) by (B,A mod B)(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)O(\deg B) operations, for O(deg⁡Adeg⁡B)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 FLINT_BITS−2\mathtt{FLINT\_BITS} - 2 bits) and deg⁡B≥31\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×22 \times 2 matrix of polynomials, so that the whole remainder sequence costs O(M(n)log⁡n)O(M(n) \log n) operations. nmod_poly_hgcd exposes one such step: it returns the matrix MM and the two consecutive remainders A,BA, B of the sequence that straddle half the degree of the input.

nmod_poly_gcd uses the half-GCD algorithm for lenB≥\mathit{lenB} \ge nmod_poly_gcd_hgcd_cutoff(mod), between 470 and 2199 depending on the bit size of nn, and nmod_poly_xgcd for lenB≥\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/p1 - 1/p.

nmod_poly_gcdinv returns the inverse of AA modulo BB together with the GCD, so that one call both tests and inverts. Modulo 7, x2+1x^2 + 1 is invertible modulo x3+x+1x^3 + x + 1 but not modulo x3+x2+x+1=(x+1)(x2+1)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-11-02).