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 , choosing the algorithm. The underscore version writes len1 + len2 - 1 coefficients, requires len1, len2 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 , that is, the low coefficients of . The underscore version requires and len1, len2 , writes coefficients and does not allow aliasing. The classical algorithm is used for 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 , . All algorithms except the classical one turn the problem into a single product of polynomials over or over , so that they inherit the quasi-linear multiplication of fmpz_poly and nmod_poly (Multiplying polynomials over Z/nZ), and reduce modulo only once per output coefficient.
Elements as polynomials#
An element of is represented by a polynomial of degree less than in the generator with coefficients in , so a polynomial of length over is a bivariate polynomial over . The product over is obtained from the product in by reducing every coefficient modulo and every coefficient of , a polynomial of degree at most in , modulo . The coefficients of are bounded:
Lemma 1. Let , have lengths , in , degree less than in and coefficients in . Then every coefficient of is less than .
Proof. The coefficient of in is , with at most values of and at most values of , and every product is at most .
The algorithms#
Classical (fq_poly_mul_classical). Each output coefficient is a dot product over , 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 and . The cost is products of polynomials of degree less than plus reductions.
Kronecker substitution (fq_poly_mul_KS). Every coefficient is packed into the integer with fq_bit_pack, where . By Lemma 1 the coefficients of the product of the packed integer polynomials, computed with _fmpz_poly_mul, are the integers with digits that do not overlap, so fq_bit_unpack recovers and reduces it. The integer polynomials have length and and coefficients of about bits; _fmpz_poly_mul itself uses Kronecker substitution or multimodular FFTs, so the cost is quasi-linear in the total bit size [Kro1882; GG2003, §8.4].
Substitution (fq_poly_mul_univariate). Since the coefficients of have degree at most in , the map sends and to univariate polynomials whose product determines : the coefficient occupies the block of coefficients starting at . 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 . Compared with Kronecker substitution into integers, no bit packing is needed and, over , the coefficients stay reduced, at the price of almost doubling the length.
Reordering (fq_poly_mul_reorder). The roles of and are exchanged: with , and the products are computed with fast integer polynomial multiplication and combined classically in the direction before the reduction modulo . This pays off for long polynomials over fields of small degree .
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 with word arithmetic (accumulating lazily when fits in a word), and converts the results back with Zech table lookups per coefficient.
Algorithm selection#
| Module | mul | mullow | sqr |
|---|---|---|---|
fq_poly | classical if both lengths are below 6, reordering if , Kronecker substitution otherwise | classical if or both lengths are below 6, Kronecker substitution otherwise | as mul |
fq_nmod_poly | classical if a length is 1, if and both lengths are at most 2, or if ; substitution otherwise | classical if 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 ; otherwise substitution 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 terms for and decreases to 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 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-11-02).
@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-11-02}
}