FLINT3.7.0-dev

Radix-2 and truncated transforms

Synopsis#include <flint/fft.h>
void fft_radix2(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2)source
void ifft_radix2(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2)source

fft_radix2 replaces aa by a^\hat a, the transform of length 2n2n with root 2w2^w. ifft_radix2 is its inverse up to scaling: it replaces a^\hat a by 2n⋅a2n \cdot a.

void fft_truncate(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_size_t trunc)source
void fft_truncate1(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_size_t trunc)source

Truncated FFTs: compute only a^k\hat a_k for k<k < trunc. fft_truncate assumes aj=0a_j = 0 for j≥j \ge trunc and uses those entries as scratch; fft_truncate1 reads all 2n2n inputs. Requires trunc even with 2≤trunc≤2n2 \le \mathit{trunc} \le 2n.

void ifft_truncate(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_size_t trunc)source
void ifft_truncate1(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_size_t trunc)source

Inverse truncated FFTs. Given a^k\hat a_k for k<k < trunc, they set entry jj to 2n⋅aj2n \cdot a_j for j<j < trunc. ifft_truncate assumes aj=0a_j = 0 for j≥j \ge trunc and uses those entries as scratch; ifft_truncate1 instead requires entry jj to hold 2n⋅aj2n \cdot a_j for j≥j \ge trunc on input, and overwrites it. Requires trunc even with 2≤trunc≤2n2 \le \mathit{trunc} \le 2n.

void fft_truncate_sqrt2(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_limb_t ** temp, mp_size_t trunc)source
void ifft_truncate_sqrt2(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_limb_t ** temp, mp_size_t trunc)source

As fft_truncate and ifft_truncate for the transform of length 4n4n with root 2 w\sqrt 2^{\,w} over the same ring; the inverse returns 4n⋅aj4n \cdot a_j. Requires trunc even with 2n<trunc≤4n2n < \mathit{trunc} \le 4n.

void fft_negacyclic(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_limb_t ** temp)source
void ifft_negacyclic(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2, mp_limb_t ** temp)source

The weighted transform of length 2n2n for negacyclic convolution. fft_negacyclic multiplies aja_j by φj\varphi^j, φ=2 w\varphi = \sqrt 2^{\,w}, and then applies fft_radix2, so that a^k=a(φ2rev(k)+1)\hat a_k = a(\varphi^{2 \mathrm{rev}(k) + 1}) are the values at the roots of x2n+1x^{2n} + 1. ifft_negacyclic applies ifft_radix2 and then multiplies entry jj by φ−j\varphi^{-j}, which gives 2n⋅aj2n \cdot a_j.

These are the transforms from which the convolutions of the module are built. They work in place on an array of coefficient pointers, in the ring Z/pZ\mathbb{Z}/p\mathbb{Z}, p=2nw+1p = 2^{nw} + 1, where multiplication by the root of unity 2w2^w is a shift. Most applications should use fft_convolution instead, which combines them correctly; the functions here are for building other transforms, for example a convolution with a different pointwise operation.

What is computed#

Let LL be the transform length, L=2nL = 2n for fft_radix2 and the truncated functions and L=4nL = 4n for the sqrt2 functions, let ω=2w\omega = 2^w, respectively ω=2 w\omega = \sqrt 2^{\,w}, a root of unity of order exactly LL in Z/pZ\mathbb{Z}/p\mathbb{Z}, and let rev(k)\mathrm{rev}(k) be the integer whose log⁡2L\log_2 L binary digits are those of kk in reverse order. For a(x)=∑j<Lajxja(x) = \sum_{j < L} a_j x^j, the forward transforms compute

a^k=a(ωrev(k)),0≤k<L,\hat a_k = a\bigl(\omega^{\mathrm{rev}(k)}\bigr), \qquad 0 \le k < L,

so that a^0,…,a^L/2−1\hat a_0, \ldots, \hat a_{L/2 - 1} are the values at the even powers of ω\omega and the second half the values at the odd powers. The inverse transforms take the values in this order and return L⋅ajL \cdot a_j; dividing by LL is a shift by log⁡2L\log_2 L bits (mpn_div_2expmod_2expp1 with d=log⁡2Ld = \log_2 L). Since ωrev(k)\omega^{\mathrm{rev}(k)} runs over all powers, pointwise products of two transforms are the transform of the cyclic product modulo xL−1x^L - 1, whatever the order.

The recursion is that of Gentleman and Sande [GS1966] (decimation in frequency): a layer of butterflies (ai,ai+L/2)↦(ai+ai+L/2,ωi(ai−ai+L/2))(a_i, a_{i + L/2}) \mapsto (a_i + a_{i + L/2}, \omega^i (a_i - a_{i + L/2})) followed by two transforms of half the length with root ω2\omega^2. The inverse transforms reverse these steps with the butterflies (x,y)↦(x+ω−iy,x−ω−iy)(x, y) \mapsto (x + \omega^{-i} y, x - \omega^{-i} y), which produce twice the inputs of the forward butterfly; this is where the factor LL comes from.

Truncation#

fft_truncate(ii, n, w, t1, t2, trunc) computes only a^0,…,a^trunc−1\hat a_0, \ldots, \hat a_{\mathit{trunc} - 1} of a polynomial with aj=0a_j = 0 for j≥truncj \ge \mathit{trunc}. ifft_truncate inverts it: from these trunc values it recovers La0,…,Latrunc−1L a_0, \ldots, L a_{\mathit{trunc} - 1}. Together they multiply polynomials whose product has at most trunc coefficients with about trunclog⁡2L\mathit{trunc} \log_2 L butterflies instead of Llog⁡2LL \log_2 L. fft_truncate1 computes the same outputs from a full input, and ifft_truncate1 is the general inverse step of van der Hoeven's algorithm: given a^k\hat a_k for k<trunck < \mathit{trunc} and LajL a_j for j≥truncj \ge \mathit{trunc} in the same array, it computes LajL a_j for j<truncj < \mathit{trunc}. In all four functions trunc must be even and between 2 and LL.

The entries at and beyond trunc are scratch space for fft_truncate and ifft_truncate, and their initial values are ignored, but they must exist. fft_truncate_sqrt2 and ifft_truncate_sqrt2 require L/2<trunc≤LL/2 < \mathit{trunc} \le L; for odd ww the first layer uses the twiddles 2 iw\sqrt 2^{\,iw} for odd ii (fft_butterfly_sqrt2), and for even ww the functions simply call fft_truncate and ifft_truncate with 2n2n and w/2w/2. For odd ww, a value trunc≤L/2\mathit{trunc} \le L/2 makes them access entries before the start of the array.

Negacyclic transforms#

fft_negacyclic evaluates a(x)a(x) at the roots of x2n+1x^{2n} + 1: it multiplies aja_j by φj\varphi^j, φ=2 w\varphi = \sqrt 2^{\,w}, which has order 4n4n, and then applies the transform of length 2n2n with root φ2=2w\varphi^2 = 2^w. Pointwise products of two such transforms are the transform of the product modulo x2n+1x^{2n} + 1, which ifft_negacyclic recovers, multiplied by 2n2n. With pieces of an integer as coefficients this is multiplication modulo 2N+12^N + 1; see _fft_mulmod_2expp1. These are the weighted transforms of Crandall and Fagin [CF1994] in a ring where the weights are shifts.

Data movement#

The functions never copy coefficients: a butterfly writes its results to the two scratch blocks *t1 and *t2 and then swaps the pointers with the two entries of ii. After a transform, the entries of ii and the scratch pointers therefore point into each other's original blocks. All blocks should be part of one allocation, and code must always access the coefficients through ii after a call, never through pointers saved before it. The sqrt2 and negacyclic functions need a third scratch block *temp.

The coefficients grow during a transform, since sums are not reduced: after kk layers of butterflies the top limb holds values up to about 2k2^k in absolute value, far within a limb. Reduce with mpn_normmod_2expp1 before using the values as reduced residues, for example before fft_mulmod_2expp1.

Example#

The transform of the polynomial xx of length 8 modulo p=264+1p = 2^{64} + 1 with root 2162^{16}. Position kk receives 216 rev(k)2^{16 \, \mathrm{rev}(k)} with rev(0),…,rev(7)=0,4,2,6,1,5,3,7\mathrm{rev}(0), \ldots, \mathrm{rev}(7) = 0, 4, 2, 6, 1, 5, 3, 7, so for example 264≡−12^{64} \equiv -1 (printed as 2642^{64}, top limb 1), 296≡−2322^{96} \equiv -2^{32} and 280≡−2162^{80} \equiv -2^{16}. The inverse transform returns 8x8x.

Examplefft_radix2_order.cchecked by make checkdoc
#include <flint/flint.h>
#include <flint/fft.h>
#include <flint/mpn_extras.h>

int main(void)
{
    /* length 2n = 8 transform modulo p = 2^64 + 1 with root 2^w = 2^16 */
    mp_size_t n = 4, limbs = 1, size = limbs + 1, i;
    flint_bitcnt_t w = 16;
    mp_limb_t mem[10 * 2], * ii[8], * t1, * t2;

    for (i = 0; i < 8; i++)
        ii[i] = mem + i * size;
    t1 = mem + 8 * size;
    t2 = mem + 9 * size;

    /* the polynomial x */
    for (i = 0; i < 8; i++)
        flint_mpn_zero(ii[i], size);
    ii[1][0] = 1;

    fft_radix2(ii, n, w, &t1, &t2);

    for (i = 0; i < 8; i++)
    {
        mpn_normmod_2expp1(ii[i], limbs);
        flint_printf("position %wd: %016wx %wu\n", i, ii[i][0], ii[i][1]);
    }

    /* the inverse transform returns 2n times the input */
    ifft_radix2(ii, n, w, &t1, &t2);
    for (i = 0; i < 8; i++)
    {
        mpn_normmod_2expp1(ii[i], limbs);
        flint_printf("%wu%s", ii[i][0], i < 7 ? " " : "\n");
    }
    return 0;
}
Output
position 0: 0000000000000001 0
position 1: 0000000000000000 1
position 2: 0000000100000000 0
position 3: ffffffff00000001 0
position 4: 0000000000010000 0
position 5: ffffffffffff0001 0
position 6: 0001000000000000 0
position 7: ffff000000000001 0
0 8 0 0 0 0 0 0

References#

  • [GS1966] W. M. Gentleman and G. Sande. Fast Fourier transforms – for fun and profit. AFIPS Fall Joint Computer Conference 29 (1966), 563–578.
  • [CF1994] R. Crandall and B. Fagin. Discrete weighted transforms and large-integer arithmetic. Mathematics of Computation 62 (1994), 305–324.
Cite this page

The FLINT team, “Radix-2 and truncated transforms (fft.h),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/fft/transforms.html (generated 2026-11-02).