FLINT3.7.0-dev

Multiplying polynomials over finite fields

Synopsis#include <flint/fq_poly.h>
void _fq_poly_mul(fq_struct * rop, const fq_struct * op1, slong len1, const fq_struct * op2, slong len2, const fq_ctx_t ctx)
void fq_poly_mul(fq_poly_t rop, const fq_poly_t op1, const fq_poly_t op2, const fq_ctx_t ctx)

Set rop to op1⋅op2\mathit{op1} \cdot \mathit{op2}, choosing the algorithm. The underscore version writes len1 + len2 - 1 coefficients, requires len1, len2 ≥1\ge 1 and does not allow aliasing.

For fq the classical algorithm is used if both lengths are below FQ_MUL_CLASSICAL_CUTOFF (6), the reordering algorithm _fq_poly_mul_reorder if the field degree is below 4, and Kronecker substitution _fq_poly_mul_KS otherwise.

void _fq_poly_mullow(fq_struct * rop, const fq_struct * op1, slong len1, const fq_struct * op2, slong len2, slong n, const fq_ctx_t ctx)
void fq_poly_mullow(fq_poly_t rop, const fq_poly_t op1, const fq_poly_t op2, slong n, const fq_ctx_t ctx)

Set rop to the product truncated to length nn, that is, the low nn coefficients of op1⋅op2\mathit{op1} \cdot \mathit{op2}. The underscore version requires 0<n≤len1+len2−10 < n \le \mathit{len1} + \mathit{len2} - 1 and len1, len2 ≥1\ge 1, writes nn coefficients and does not allow aliasing. The classical algorithm is used for nn below FQ_MULLOW_CLASSICAL_CUTOFF (6) or short inputs, and Kronecker substitution otherwise.

fq_poly_mul and fq_poly_mullow compute the product, respectively the truncated product, of two polynomials over Fq=Fp[y]/(f(y))\mathbb{F}_q = \mathbb{F}_p[y]/(f(y)), q=pdq = p^d. All algorithms except the classical one turn the problem into a single product of polynomials over Z\mathbb{Z} or over Z/pZ\mathbb{Z}/p\mathbb{Z}, so that they inherit the quasi-linear multiplication of fmpz_poly and nmod_poly (Multiplying polynomials over Z/nZ), and reduce modulo ff only once per output coefficient.

AlgorithmKronecker substitutionMultiplies polynomials over Z/nZ\mathbb{Z}/n\mathbb{Z} by packing their coefficients into large integers, multiplying the integers and unpacking; Harvey's KS2 and KS4 variants evaluate at ±2^b and ±2^(-b) to split the integer product into two or four smaller ones.

Elements as polynomials#

An element of Fq\mathbb{F}_q is represented by a polynomial of degree less than dd in the generator yy with coefficients in [0,p)[0, p), so a polynomial of length nn over Fq\mathbb{F}_q is a bivariate polynomial A(x,y)=∑i<n∑j<daijxiyjA(x, y) = \sum_{i < n} \sum_{j < d} a_{ij} x^i y^j over Z\mathbb{Z}. The product over Fq\mathbb{F}_q is obtained from the product C=ABC = AB in Z[x,y]\mathbb{Z}[x, y] by reducing every coefficient modulo pp and every coefficient of xkx^k, a polynomial of degree at most 2d−22d - 2 in yy, modulo ff. The coefficients of CC are bounded:

Lemma 1. Let AA, BB have lengths n1n_1, n2n_2 in xx, degree less than dd in yy and coefficients in [0,p)[0, p). Then every coefficient of C=ABC = AB is less than min⁡(n1,n2) d (p−1)2+1\min(n_1, n_2)\, d\, (p - 1)^2 + 1.

Proof. The coefficient of xkylx^k y^l in CC is ∑aijbk−i,l−j\sum a_{ij} b_{k-i, l-j}, with at most min⁡(n1,n2)\min(n_1, n_2) values of ii and at most dd values of jj, and every product is at most (p−1)2(p - 1)^2.

The algorithms#

Classical (fq_poly_mul_classical). Each output coefficient is a dot product over Fq\mathbb{F}_q, computed by _fq_vec_dot (via _gr_poly_mullow_classical): the products of representatives are summed without reduction and the sum is reduced once modulo pp and ff. The cost is O(n1n2)O(n_1 n_2) products of polynomials of degree less than dd plus n1+n2−1n_1 + n_2 - 1 reductions.

Kronecker substitution (fq_poly_mul_KS). Every coefficient ai(y)a_i(y) is packed into the integer ai(2b)a_i(2^b) with fq_bit_pack, where b=bits⁡(d)+bits⁡(min⁡(n1,n2))+2bits⁡(p)b = \operatorname{bits}(d) + \operatorname{bits}(\min(n_1, n_2)) + 2 \operatorname{bits}(p). By Lemma 1 the coefficients of the product of the packed integer polynomials, computed with _fmpz_poly_mul, are the integers ck(2b)c_k(2^b) with digits that do not overlap, so fq_bit_unpack recovers ck(y)c_k(y) and reduces it. The integer polynomials have length n1n_1 and n2n_2 and coefficients of about dbdb bits; _fmpz_poly_mul itself uses Kronecker substitution or multimodular FFTs, so the cost is quasi-linear in the total bit size ndlog⁡pn d \log p [Kro1882; GG2003, §8.4].

Substitution y=x2d−1y = x^{2d-1} (fq_poly_mul_univariate). Since the coefficients of CC have degree at most 2d−22d - 2 in yy, the map y↦x2d−1y \mapsto x^{2d - 1} sends AA and BB to univariate polynomials whose product determines CC: the coefficient ck(y)c_k(y) occupies the block of 2d−12d - 1 coefficients starting at (2d−1)k(2d - 1)k. The univariate product is computed with _fmpz_poly_mul for fq and with _nmod_poly_mul (which uses fft_small for long products) for fq_nmod, and each block is reduced modulo ff. Compared with Kronecker substitution into integers, no bit packing is needed and, over Z/pZ\mathbb{Z}/p\mathbb{Z}, the coefficients stay reduced, at the price of almost doubling the length.

Reordering (fq_poly_mul_reorder). The roles of xx and yy are exchanged: A=∑j<dyjAj(x)A = \sum_{j < d} y^j A_j(x) with Aj∈Z[x]A_j \in \mathbb{Z}[x], and the d2d^2 products AjBlA_j B_l are computed with fast integer polynomial multiplication and combined classically in the yy direction before the reduction modulo ff. This pays off for long polynomials over fields of small degree dd.

Zech logarithms. In fq_zech_poly the coefficients are discrete logarithms, so the classical algorithm uses table lookups only. The substitution algorithm first converts the coefficients to their polynomial representation with a table (eval_table), multiplies with _nmod_poly_mul, reduces the blocks modulo ff with word arithmetic (accumulating lazily when (d−1)(p−1)2+p(d - 1)(p - 1)^2 + p fits in a word), and converts the results back with dd Zech table lookups per coefficient.

Algorithm selection#

Modulemulmullowsqr
fq_polyclassical if both lengths are below 6, reordering if d<4d < 4, Kronecker substitution otherwiseclassical if n<6n < 6 or both lengths are below 6, Kronecker substitution otherwiseas mul
fq_nmod_polyclassical if a length is 1, if d=2d = 2 and both lengths are at most 2, or if bits⁡(p)⋅d⋅max⁡(n1,n2)≤8\operatorname{bits}(p) \cdot d \cdot \max(n_1, n_2) \le 8; substitution y=x2d−1y = x^{2d-1} otherwiseclassical if n<6n < 6 or both lengths are below 6, Kronecker substitution otherwiseclassical below length 6, Kronecker substitution otherwise
fq_zech_polyclassical if a length is below 2d+22d + 2; otherwise substitution y=x2d−1y = x^{2d-1} if _fq_zech_poly_mulmid_want_univariate predicts it to be faster, classical otherwiseas mul, with the work estimate for the truncated productas mul, with _fq_zech_poly_sqr_want_univariate

The threshold of fq_zech_poly (_fq_zech_poly_mul_univariate_threshold) is 9d−49d - 4 terms for q<217q < 2^{17} and decreases to 2d+22d + 2 for larger fields, where the tables no longer fit in the cache and lookups become expensive. fq_poly_mulhigh always uses the classical algorithm, while _fq_poly_mulhigh computes a full product by Kronecker substitution for inputs of length at least 6.

Example#

All algorithms give the same product. The program multiplies random polynomials of lengths 40 and 30 over F1013\mathbb{F}_{101^3} with each algorithm:

Examplefq_poly_mul_algorithms.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/fmpz.h>
#include <flint/fq.h>
#include <flint/fq_poly.h>

int main(void)
{
    fmpz_t p;
    fq_ctx_t ctx;
    fq_poly_t a, b, c, d;
    flint_rand_t state;

    fmpz_init_set_ui(p, 101);
    fq_ctx_init(ctx, p, 3, "y");               /* GF(101^3) */
    flint_rand_init(state);
    fq_poly_init(a, ctx);
    fq_poly_init(b, ctx);
    fq_poly_init(c, ctx);
    fq_poly_init(d, ctx);

    fq_poly_randtest(a, state, 40, ctx);
    fq_poly_randtest(b, state, 30, ctx);

    fq_poly_mul(c, a, b, ctx);
    fq_poly_mul_classical(d, a, b, ctx);
    flint_printf("classical:  %d\n", fq_poly_equal(c, d, ctx));
    fq_poly_mul_KS(d, a, b, ctx);
    flint_printf("KS:         %d\n", fq_poly_equal(c, d, ctx));
    fq_poly_mul_univariate(d, a, b, ctx);
    flint_printf("univariate: %d\n", fq_poly_equal(c, d, ctx));
    fq_poly_mul_reorder(d, a, b, ctx);
    flint_printf("reorder:    %d\n", fq_poly_equal(c, d, ctx));

    fq_poly_mullow(d, a, b, 20, ctx);
    fq_poly_truncate(c, 20, ctx);
    flint_printf("mullow:     %d\n", fq_poly_equal(c, d, ctx));

    fq_poly_clear(a, ctx);
    fq_poly_clear(b, ctx);
    fq_poly_clear(c, ctx);
    fq_poly_clear(d, ctx);
    flint_rand_clear(state);
    fq_ctx_clear(ctx);
    fmpz_clear(p);
    return 0;
}
Output
classical:  1
KS:         1
univariate: 1
reorder:    1
mullow:     1

References#

  • [Kro1882] L. Kronecker, "Grundzüge einer arithmetischen Theorie der algebraischen Grössen", Journal für die reine und angewandte Mathematik 92 (1882), 1–122
  • [GG2003] J. von zur Gathen and J. Gerhard, Modern Computer Algebra, second edition, Cambridge University Press (2003)
Cite this page

The FLINT team, “Multiplying polynomials over finite fields (fq_poly.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fq_poly/mul.html (generated 2026-11-02).