Radix-2 and truncated transforms
#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)sourcevoid ifft_radix2(mp_limb_t ** ii, mp_size_t n, flint_bitcnt_t w, mp_limb_t ** t1, mp_limb_t ** t2)sourcefft_radix2 replaces by , the transform of length with root . ifft_radix2 is its inverse up to scaling: it replaces by .
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)sourcevoid 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)sourceTruncated FFTs: compute only for trunc. fft_truncate assumes for trunc and uses those entries as scratch; fft_truncate1 reads all inputs. Requires trunc even with .
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)sourcevoid 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)sourceInverse truncated FFTs. Given for trunc, they set entry to for trunc. ifft_truncate assumes for trunc and uses those entries as scratch; ifft_truncate1 instead requires entry to hold for trunc on input, and overwrites it. Requires trunc even with .
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)sourcevoid 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)sourceAs fft_truncate and ifft_truncate for the transform of length with root over the same ring; the inverse returns . Requires trunc even with .
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)sourcevoid 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)sourceThe weighted transform of length for negacyclic convolution. fft_negacyclic multiplies by , , and then applies fft_radix2, so that are the values at the roots of . ifft_negacyclic applies ifft_radix2 and then multiplies entry by , which gives .
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 , , where multiplication by the root of unity 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 be the transform length, for fft_radix2 and the truncated functions and for the sqrt2 functions, let , respectively , a root of unity of order exactly in , and let be the integer whose binary digits are those of in reverse order. For , the forward transforms compute
so that are the values at the even powers of and the second half the values at the odd powers. The inverse transforms take the values in this order and return ; dividing by is a shift by bits (mpn_div_2expmod_2expp1 with ). Since runs over all powers, pointwise products of two transforms are the transform of the cyclic product modulo , whatever the order.
The recursion is that of Gentleman and Sande [GS1966] (decimation in frequency): a layer of butterflies followed by two transforms of half the length with root . The inverse transforms reverse these steps with the butterflies , which produce twice the inputs of the forward butterfly; this is where the factor comes from.
Truncation#
fft_truncate(ii, n, w, t1, t2, trunc) computes only of a polynomial with for . ifft_truncate inverts it: from these trunc values it recovers . Together they multiply polynomials whose product has at most trunc coefficients with about butterflies instead of . 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 for and for in the same array, it computes for . In all four functions trunc must be even and between 2 and .
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 ; for odd the first layer uses the twiddles for odd (fft_butterfly_sqrt2), and for even the functions simply call fft_truncate and ifft_truncate with and . For odd , a value makes them access entries before the start of the array.
Negacyclic transforms#
fft_negacyclic evaluates at the roots of : it multiplies by , , which has order , and then applies the transform of length with root . Pointwise products of two such transforms are the transform of the product modulo , which ifft_negacyclic recovers, multiplied by . With pieces of an integer as coefficients this is multiplication modulo ; 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 layers of butterflies the top limb holds values up to about 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 of length 8 modulo with root . Position receives with , so for example (printed as , top limb 1), and . The inverse transform returns .
#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;
}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.
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).
@misc{flint-doc-fft-transforms,
author = {{The FLINT team}},
title = {{Radix-2 and truncated transforms (fft.h)}},
howpublished = {FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev},
year = {2026},
url = {https://flintlib.org/doc/fft/transforms.html},
note = {Generated 2026-11-02}
}