Multiplying polynomials over finite fields
#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)Set rop to \mathit{op1} \cdot \mathit{op2}, choosing the algorithm. The underscore version writes len1 + len2 - 1 coefficients, requires len1, len2 \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 n, that is, the low n coefficients of \mathit{op1} \cdot \mathit{op2}. The underscore version requires 0 < n \le \mathit{len1} + \mathit{len2} - 1 and len1, len2 \ge 1, writes n coefficients and does not allow aliasing. The classical algorithm is used for n 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 \mathbb{F}_q = \mathbb{F}_p[y]/(f(y)), q = p^d. All algorithms except the classical one turn the problem into a single product of polynomials over \mathbb{Z} or over \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 f only once per output coefficient.
Elements as polynomials#
An element of \mathbb{F}_q is represented by a polynomial of degree less than d in the generator y with coefficients in [0, p), so a polynomial of length n over \mathbb{F}_q is a bivariate polynomial A(x, y) = \sum_{i < n} \sum_{j < d} a_{ij} x^i y^j over \mathbb{Z}. The product over \mathbb{F}_q is obtained from the product C = AB in \mathbb{Z}[x, y] by reducing every coefficient modulo p and every coefficient of x^k, a polynomial of degree at most 2d - 2 in y, modulo f. The coefficients of C are bounded:
Lemma 1. Let A, B have lengths n_1, n_2 in x, degree less than d in y and coefficients in [0, p). Then every coefficient of C = AB is less than \min(n_1, n_2)\, d\, (p - 1)^2 + 1.
Proof. The coefficient of x^k y^l in C is \sum a_{ij} b_{k-i, l-j}, with at most \min(n_1, n_2) values of i and at most d values of j, and every product is at most (p - 1)^2.
The algorithms#
Classical (fq_poly_mul_classical). Each output coefficient is a dot product over \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 p and f. The cost is O(n_1 n_2) products of polynomials of degree less than d plus n_1 + n_2 - 1 reductions.
Kronecker substitution (fq_poly_mul_KS). Every coefficient a_i(y) is packed into the integer a_i(2^b) with fq_bit_pack, where 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 c_k(2^b) with digits that do not overlap, so fq_bit_unpack recovers c_k(y) and reduces it. The integer polynomials have length n_1 and n_2 and coefficients of about db bits; _fmpz_poly_mul itself uses Kronecker substitution or multimodular FFTs, so the cost is quasi-linear in the total bit size n d \log p [Kro1882; GG2003, §8.4].
Substitution y = x^{2d-1} (fq_poly_mul_univariate). Since the coefficients of C have degree at most 2d - 2 in y, the map y \mapsto x^{2d - 1} sends A and B to univariate polynomials whose product determines C: the coefficient c_k(y) occupies the block of 2d - 1 coefficients starting at (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 f. Compared with Kronecker substitution into integers, no bit packing is needed and, over \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 x and y are exchanged: A = \sum_{j < d} y^j A_j(x) with A_j \in \mathbb{Z}[x], and the d^2 products A_j B_l are computed with fast integer polynomial multiplication and combined classically in the y direction before the reduction modulo f. This pays off for long polynomials over fields of small degree d.
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 f with word arithmetic (accumulating lazily when (d - 1)(p - 1)^2 + p fits in a word), and converts the results back with d Zech table lookups per coefficient.
Algorithm selection#
| Module | mul | mullow | sqr |
|---|---|---|---|
fq_poly | classical if both lengths are below 6, reordering if d < 4, Kronecker substitution otherwise | classical if n < 6 or both lengths are below 6, Kronecker substitution otherwise | as mul |
fq_nmod_poly | classical if a length is 1, if d = 2 and both lengths are at most 2, or if \operatorname{bits}(p) \cdot d \cdot \max(n_1, n_2) \le 8; substitution y = x^{2d-1} otherwise | classical if n < 6 or both lengths are below 6, Kronecker substitution otherwise | classical below length 6, Kronecker substitution otherwise |
fq_zech_poly | classical if a length is below 2d + 2; otherwise substitution y = x^{2d-1} if _fq_zech_poly_mulmid_want_univariate predicts it to be faster, classical otherwise | as mul, with the work estimate for the truncated product | as mul, with _fq_zech_poly_sqr_want_univariate |
The threshold of fq_zech_poly (_fq_zech_poly_mul_univariate_threshold) is 9d - 4 terms for q < 2^{17} and decreases to 2d + 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 \mathbb{F}_{101^3} with each algorithm:
#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;
}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)
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-10-20).
@misc{flint-doc-fq-poly-mul,
author = {{The FLINT team}},
title = {{Multiplying polynomials over finite fields (fq\_poly.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fq_poly/mul.html},
note = {Generated 2026-10-20}
}