FLINT3.7.0-dev

Transforms modulo one prime

Synopsis#include <flint/fft_small.h>
void sd_fft_trunc(sd_fft_ctx_t Q, double * d, ulong L, ulong itrunc, ulong otrunc)source

Truncated forward transform of length 2L2^L in place: with ff the polynomial with coefficients d[0], ..., d[itrunc - 1], sets d[sd_fft_ctx_trunc_index(L, i)] to a value congruent to f(wi)f(w_i) for 0≤i<otrunc0 \le i < \mathtt{otrunc}. The inputs must lie in (−3p,3p)(-3p, 3p), and so do the outputs.

d must be aligned to 32 bytes and have room for 2L2^L doubles. For L≤8L \le 8 the entries from itrunc on are set to zero and the whole transform is computed; for larger LL, itrunc and otrunc are rounded up to multiples of 256, the entries from itrunc to the next multiple of 256 are set to zero, and only the blocks below the rounded otrunc are written. Any 1≤itrunc,otrunc≤2L1 \le \mathtt{itrunc}, \mathtt{otrunc} \le 2^L is allowed. The evaluation points do not depend on LL, so if both truncations are at most 2L−12^{L-1} the result is that of depth L−1L - 1 at about the same cost; recovering the input with sd_ifft_trunc requires otrunc≥itrunc\mathtt{otrunc} \ge \mathtt{itrunc}. See Transforms modulo one prime.

void sd_ifft_trunc(sd_fft_ctx_t Q, double * d, ulong L, ulong trunc)source

Truncated inverse transform of length 2L2^L in place: if d[i] holds the value at position ii of the forward transform of a polynomial gg of length at most trunc, for i<trunci < \mathtt{trunc}, sets d[i] to a value congruent to 2Lgi2^L g_i for i<trunci < \mathtt{trunc}. The inputs must lie in (−2p,2p)(-2p, 2p); the outputs lie in (−3p,3p)(-3p, 3p).

For L≤8L \le 8 the whole inverse transform is computed and trunc is ignored, so all 2L2^L values must be present. For larger LL, trunc is rounded up to a multiple of 256 and gg may have that length. The outputs are not normalised; the multiplication drivers fold 2−L2^{-L} into the pointwise products.

void sd_fft_trunc_tr(sd_fft_ctx_t Q, double * d, ulong L, ulong itrunc, ulong otrunc)source
void sd_ifft_trunc_tr(sd_fft_ctx_t Q, double * d, ulong L, ulong trunc)source

The transposes of sd_fft_trunc and sd_ifft_trunc with the same parameters, in place: for all vectors aa and vv, ⟨sd_fft_trunc(a),v⟩=⟨a,sd_fft_trunc_tr(v)⟩\langle \mathtt{sd\_fft\_trunc}(a), v \rangle = \langle a, \mathtt{sd\_fft\_trunc\_tr}(v) \rangle, and likewise for the inverse transform. sd_fft_trunc_tr reads the first otrunc entries, indexed like the output of sd_fft_trunc, and sets dtd_t to a value congruent to ∑i<otruncd[sd_fft_ctx_trunc_index(L,i)] wit\sum_{i < \mathtt{otrunc}} d[\mathtt{sd\_fft\_ctx\_trunc\_index}(L, i)]\, w_i^t for 0≤t<itrunc0 \le t < \mathtt{itrunc}. Each costs the same as the transform it transposes.

sd_fft_trunc_tr accepts inputs in (−2p,2p)(-2p, 2p) and produces outputs in (−2p,2p)(-2p, 2p), so it can follow sd_fft_ctx_point_mul; sd_ifft_trunc_tr accepts and produces values in (−3p,3p)(-3p, 3p), like sd_fft_trunc. The truncations are rounded as for the untransposed transforms: for L>8L > 8 to multiples of 256 (sd_fft_trunc_tr first zeroes the entries from otrunc to the next multiple of 256), and for L≤8L \le 8 the full transform is computed (sd_fft_trunc_tr zeroes the entries from otrunc on, and sd_ifft_trunc_tr ignores trunc). The transposed inverse transform followed by a pointwise product and the transposed forward transform computes a transposed convolution; see Transposed transforms and Transposed transforms.

void sd_fft_ctx_point_mul(const sd_fft_ctx_t Q, double * a, const double * b, ulong m_, ulong depth)source
void sd_fft_ctx_point_sqr(const sd_fft_ctx_t Q, double * a, ulong m_, ulong depth)source

Set ai←maibia_i \gets m a_i b_i, respectively mai2m a_i^2, modulo pp for i<2depthi < 2^{\mathtt{depth}}, with 0≤m≤p0 \le m \le p and depth at least 4. Inputs in (−3p,3p)(-3p, 3p) give outputs in (−3p/2,3p/2)(-3p/2, 3p/2), valid inputs for sd_ifft_trunc.

sd_fft_trunc and sd_ifft_trunc are the number-theoretic transforms that all of fft_small is built on. They work in place on an array of doubles holding integers, modulo a prime p<250p < 2^{50} with 2L∣p−12^L \mid p - 1, and are truncated: only part of the input is nonzero and only part of the output is needed. Using them directly makes sense for convolutions modulo one of the context primes, or modulo another suitable prime with a context of its own.

What they compute#

Let w0,w1,w2,…w_0, w_1, w_2, \ldots be the roots of unity of sd_fft_ctx_w: the first 2L2^L of them are the 2L2^L-th roots of unity in bit-reversed order, wi=ωrev⁡L(i)w_i = \omega^{\operatorname{rev}_L(i)} for a primitive 2L2^L-th root ω\omega. For the polynomial f=∑j<itruncdjxjf = \sum_{j < \mathtt{itrunc}} d_j x^j formed by the first itrunc entries, sd_fft_trunc stores a value congruent to f(wi)f(w_i) at position sd_fft_ctx_trunc_index(L, i), for i<otrunci < \mathtt{otrunc}. That position is ii for L≤3L \le 3; for L≥4L \ge 4 each group of 16 values is stored as a transposed 4×44 \times 4 matrix, because the innermost kernel saves its final transposition. Pointwise operations do not care about the order, and the inverse transform expects the same one.

sd_ifft_trunc inverts the transform up to the factor 2L2^L: if the first trunc positions hold the values of the forward transform of a polynomial gg of length at most trunc, it replaces them by values congruent to 2Lg0,…,2Lgtrunc−12^L g_0, \ldots, 2^L g_{\mathtt{trunc}-1}. The truncated inverse needs no other values, because the missing evaluations are determined by the known zero coefficients [vdH2004].

sd_fft_ctx_point_mul multiplies the transforms pointwise and by a constant mm in one pass. Taking m=2−L mod pm = 2^{-L} \bmod p makes the inverse transform return the product itself.

sd_fft_trunc_tr and sd_ifft_trunc_tr are the transposes of the two transforms, with the same parameters: sd_fft_trunc_tr reads otrunc values on the evaluation side, in the order above, and writes itrunc coefficients ∑i<otruncviwit\sum_{i < \mathtt{otrunc}} v_i w_i^t, t<itrunct < \mathtt{itrunc}, where viv_i is the entry at position sd_fft_ctx_trunc_index(L, i). See Transposed transforms.

Requirements#

  • d must be aligned to 32 bytes and hold 2L2^L doubles.
  • Entries are integers. The forward transform accepts and returns entries in (−3p,3p)(-3p, 3p). The inverse transform accepts entries in (−2p,2p)(-2p, 2p), which sd_fft_ctx_point_mul produces from forward outputs, and returns entries in (−3p,3p)(-3p, 3p). Reduce with nmod_set_si or the vector reductions of machine_vectors.h to obtain residues.
  • Truncations are rounded: for L≤8L \le 8 the forward transform zeroes the entries from itrunc on and computes all 2L2^L values, and the inverse transform ignores trunc and needs all 2L2^L values. For L>8L > 8 both work on blocks of 256 entries: itrunc, otrunc and trunc are rounded up to multiples of 256, the forward transform zeroes the entries from itrunc to the next multiple of 256, and entries beyond the rounded output truncation are neither read nor written.
  • sd_fft_ctx_point_mul touches all 2L2^L entries of both arrays, so entries beyond the truncation must hold some finite values; it requires L≥4L \ge 4 and 0≤m≤p0 \le m \le p.
  • The transposed transforms have the rounding of the transforms they transpose. sd_ifft_trunc_tr accepts and returns entries in (−3p,3p)(-3p, 3p), like sd_fft_trunc; sd_fft_trunc_tr accepts entries in (−2p,2p)(-2p, 2p), like sd_ifft_trunc, and returns entries in (−2p,2p)(-2p, 2p).
  • The context must cover depth LL, which all four transforms ensure by calling sd_fft_ctx_fit_depth, and 2L2^L must divide p−1p - 1.

Algorithm#

The transform is a fast Fourier transform [CT1965] over Z/pZ\mathbb{Z}/p\mathbb{Z} [Pol1971], computed as a tree of reductions of ff modulo x2k−wjx^{2^k} - w_j [Ber2008]: the butterfly (a,b)↦(a+sb,a−sb)(a, b) \mapsto (a + sb, a - sb) splits a residue modulo x2h−s2x^{2h} - s^2 into its residues modulo xh−sx^h - s and xh+sx^h + s. Two levels are merged into radix-4 butterflies. Above 256 points it is organised as Bailey's matrix algorithm [Bai1990]: the length 2k2^k blocks are viewed as a matrix of 2k12^{k_1} rows of 2k22^{k_2} blocks, k1=⌊k/2⌋k_1 = \lfloor k/2 \rfloor, and column transforms on strided blocks are followed by row transforms with twisted roots, each recursively. Within a block, fixed-size kernels keep the data in registers. The inverse transform runs the same structure backwards with Gentleman–Sande butterflies [GS1966]. Each butterfly uses the floating-point modular arithmetic of Modular arithmetic in floating point, with lazy reductions whose ranges are proved on the algorithm page.

AlgorithmFFTs modulo word-size primes in floating pointThe number-theoretic transforms of fft_small modulo primes p<250p < 2^{50}, on integers stored in doubles: the order of the roots, radix-4 butterflies with lazy reduction, blocking and truncation, and proofs that every value stays in range.

Transposed transforms#

A transform computed by a linear program of butterflies has a transpose computed by the same butterflies in reverse order, each replaced by its transpose, at exactly the same cost (the transposition principle, The transposition principle). The transpose of the butterfly (a,b)↦(a+wb,a−wb)(a, b) \mapsto (a + wb, a - wb) of the forward transform is (a,b)↦(a+b,w(a−b))(a, b) \mapsto (a + b, w(a - b)), the shape of the inverse butterflies with the forward twiddles, and conversely; truncation transposes into truncation, with the roles of itrunc and otrunc exchanged.

Their use is the transposed convolution. For a fixed aa of length ana_n, multiplication by aa of polynomials of length znz_n is, for a transform truncation T≥an+zn−1T \ge a_n + z_n - 1, the composition b↦I(m a^⋅F(b))b \mapsto I(m\,\hat a \cdot F(b)) of the forward transform FF of bb (itrunc =zn= z_n, otrunc =T= T), the pointwise product with m a^m\,\hat a, and the inverse transform II with truncation TT, followed by taking the first an+zn−1a_n + z_n - 1 coefficients. Its transpose is v↦FT(m a^⋅IT(v))v \mapsto F^T(m\,\hat a \cdot I^T(v)) for vv of length an+zn−1a_n + z_n - 1 padded with zeros to TT, and by Lemma 2 of Middle products and the transposition principle it computes zj=∑iaivi+jz_j = \sum_i a_i v_{i+j} for j<znj < z_n: a middle product, at the cost of a product of lengths ana_n and znz_n. _nmod_poly_mul_tr_mpn_ctx is this computation modulo several primes; see Transposed transforms.

Examples#

The positions of the first values of a transform of length 16, and the inverse transform:

Examplefft_small_sd_fft_order.cneeds fft_smallchecked by make checkdoc
#include <flint/flint.h>
#include <flint/nmod.h>
#include <flint/fft_small.h>

int main(void)
{
    mpn_ctx_struct * R = get_default_mpn_ctx();
    sd_fft_ctx_struct * Q = R->ffts + 0;      /* p = 63*2^44 + 1 */
    nmod_t mod = Q->mod;
    ulong L = 4, N = 16, i, j, k, fx, x, inv16;
    ulong f[3] = { 1, 2, 3 };                  /* f = 1 + 2x + 3x^2 */
    double * d = flint_aligned_alloc(32, N * sizeof(double));

    for (i = 0; i < 3; i++)
        d[i] = f[i];

    /* forward transform: f(w_i) lands at position trunc_index(L, i) */
    sd_fft_trunc(Q, d, L, 3, N);

    for (i = 0; i < N; i++)
    {
        x = nmod_set_si((slong) sd_fft_ctx_w(Q, i), mod);
        fx = 0;
        for (j = 3; j-- > 0; )
            fx = nmod_add(nmod_mul(fx, x, mod), f[j], mod);
        k = sd_fft_ctx_trunc_index(L, i);
        if (nmod_set_si((slong) d[k], mod) != fx)
            flint_printf("mismatch at i = %wu\n", i);
        if (i < 6)
            flint_printf("w_%wu: position %2wu\n", i, k);
    }

    /* inverse transform: returns 16 f, to be divided by 2^L */
    sd_ifft_trunc(Q, d, L, N);
    inv16 = nmod_inv(16, mod);
    for (i = 0; i < 4; i++)
        flint_printf("%wu ", nmod_mul(nmod_set_si((slong) d[i], mod), inv16, mod));
    flint_printf("\n");

    flint_aligned_free(d);
    flint_cleanup_master();
    return 0;
}
Output
w_0: position  0
w_1: position  4
w_2: position  8
w_3: position 12
w_4: position  1
w_5: position  5
1 2 3 0

A product of polynomials of lengths 300 and 200 modulo the prime with truncated transforms of length 2102^{10}, of which only the first 512 values are computed:

Examplefft_small_sd_fft_mul.cneeds fft_smallchecked by make checkdoc
#include <flint/flint.h>
#include <flint/nmod.h>
#include <flint/nmod_vec.h>
#include <flint/nmod_poly.h>
#include <flint/fft_small.h>

int main(void)
{
    sd_fft_ctx_struct * Q = get_default_mpn_ctx()->ffts + 0;
    nmod_t mod = Q->mod;
    slong an = 300, bn = 200, zn = an + bn - 1, i;
    ulong L = 10, m;
    nn_ptr a = _nmod_vec_init(an), b = _nmod_vec_init(bn);
    nn_ptr c = _nmod_vec_init(zn), z = _nmod_vec_init(zn);
    double * x = flint_aligned_alloc(32, 1024 * sizeof(double));
    double * y = flint_aligned_alloc(32, 1024 * sizeof(double));

    for (i = 0; i < an; i++)
        a[i] = nmod_pow_ui(i + 1, 7, mod);
    for (i = 0; i < bn; i++)
        b[i] = nmod_pow_ui(i + 2, 9, mod);

    /* residues in [0, p) are valid inputs; the unused tail is zeroed
       because sd_fft_ctx_point_mul reads all 2^L entries */
    for (i = 0; i < 1024; i++)
        x[i] = y[i] = 0.0;
    for (i = 0; i < an; i++)
        x[i] = (double) a[i];
    for (i = 0; i < bn; i++)
        y[i] = (double) b[i];

    /* only 512 of the 1024 values are needed, since zn <= 512 */
    sd_fft_trunc(Q, x, L, an, 512);
    sd_fft_trunc(Q, y, L, bn, 512);

    /* x = x * y / 2^L pointwise, then the inverse transform */
    m = nmod_inv(nmod_pow_ui(2, L, mod), mod);
    sd_fft_ctx_point_mul(Q, x, y, m, L);
    sd_ifft_trunc(Q, x, L, 512);

    for (i = 0; i < zn; i++)
        z[i] = nmod_set_si((slong) x[i], mod);

    _nmod_poly_mul(c, a, an, b, bn, mod);
    flint_printf("product modulo p: %s\n",
                 _nmod_vec_equal(z, c, zn) ? "agrees" : "WRONG");

    _nmod_vec_clear(a);
    _nmod_vec_clear(b);
    _nmod_vec_clear(c);
    _nmod_vec_clear(z);
    flint_aligned_free(x);
    flint_aligned_free(y);
    flint_cleanup_master();
    return 0;
}
Output
product modulo p: agrees

The transposed product zj=∑iaivi+jz_j = \sum_i a_i v_{i+j}, j<400j < 400, of aa of length 300 and vv of length 699 modulo the prime, with transforms of length 2102^{10} truncated to 768 points:

Examplefft_small_sd_fft_transposed.cneeds fft_smallchecked by make checkdoc
#include <flint/flint.h>
#include <flint/nmod.h>
#include <flint/nmod_vec.h>
#include <flint/fft_small.h>

int main(void)
{
    sd_fft_ctx_struct * Q = get_default_mpn_ctx()->ffts + 0;
    nmod_t mod = Q->mod;
    slong an = 300, zn = 400, vn = an + zn - 1, i, j;
    ulong L = 10, T = 768, m, s;
    nn_ptr a = _nmod_vec_init(an), v = _nmod_vec_init(vn);
    nn_ptr z = _nmod_vec_init(zn);
    double * x = flint_aligned_alloc(32, 1024 * sizeof(double));
    double * y = flint_aligned_alloc(32, 1024 * sizeof(double));
    int ok = 1;

    for (i = 0; i < an; i++)
        a[i] = nmod_pow_ui(i + 1, 7, mod);
    for (i = 0; i < vn; i++)
        v[i] = nmod_pow_ui(i + 2, 9, mod);

    for (i = 0; i < 1024; i++)
        x[i] = y[i] = 0.0;
    for (i = 0; i < an; i++)
        x[i] = (double) a[i];
    for (i = 0; i < vn; i++)
        y[i] = (double) v[i];

    /* a is transformed as for a product of length T */
    sd_fft_trunc(Q, x, L, an, T);

    /* the transposed pipeline: ifft_tr, pointwise product, fft_tr */
    m = nmod_inv(nmod_pow_ui(2, L, mod), mod);
    sd_ifft_trunc_tr(Q, y, L, T);
    sd_fft_ctx_point_mul(Q, y, x, m, L);
    sd_fft_trunc_tr(Q, y, L, zn, T);

    for (j = 0; j < zn; j++)
    {
        s = 0;
        for (i = 0; i < an; i++)
            s = nmod_addmul(s, a[i], v[i + j], mod);
        z[j] = nmod_set_si((slong) y[j], mod);
        ok = ok && (z[j] == s);
    }
    flint_printf("transposed product: %s\n", ok ? "agrees" : "WRONG");

    _nmod_vec_clear(a);
    _nmod_vec_clear(v);
    _nmod_vec_clear(z);
    flint_aligned_free(x);
    flint_aligned_free(y);
    flint_cleanup_master();
    return 0;
}
Output
transposed product: agrees

Guides#

References#

  • [vdH2004] J. van der Hoeven, "Truncated FFTs", ISSAC 2004 notes, https://www.texmacs.org/joris/issac04/issac04.html
  • [CT1965] J. W. Cooley and J. W. Tukey. An algorithm for the machine calculation of complex Fourier series. Mathematics of Computation 19 (1965), 297–301.
  • [Pol1971] J. M. Pollard. The fast Fourier transform in a finite field. Mathematics of Computation 25 (1971), 365–374.
  • [Ber2008] D. J. Bernstein, "Fast multiplication and its applications", in Algorithmic Number Theory, MSRI Publications 44 (2008), 325–384
  • [Bai1990] D. H. Bailey. FFTs in external or hierarchical memory. The Journal of Supercomputing 4 (1990), 23–35.
  • [GS1966] W. M. Gentleman and G. Sande. Fast Fourier transforms – for fun and profit. AFIPS Fall Joint Computer Conference 29 (1966), 563–578.
Cite this page

The FLINT team, “Transforms modulo one prime (fft_small.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft_small/sd_fft.html (generated 2026-11-02).