Fast Fourier transform
The fast Fourier transform (FFT) computes the discrete Fourier transform of values in arithmetic operations, where the definition takes . The discrete Fourier transform evaluates a polynomial at the complex -th roots of unity, and the Cooley–Tukey algorithm does it by splitting the coefficients into those at even and odd positions, transforming each half recursively and combining the two with butterfly operations[1]. The transform turns convolution into pointwise multiplication, so the FFT multiplies polynomials and large integers in close to linear time[3]. Over a finite field with the right roots of unity the same algorithm is exact, the number-theoretic transform[6]. The idea goes back to Gauss; Cooley and Tukey's 1965 paper made it the standard algorithm of signal processing[2].
The discrete Fourier transform
Let , a primitive -th root of unity. The discrete Fourier transform of is
which is the value of the polynomial at . The inverse transform uses and divides by :
because is when and 0 otherwise. Computed from the definition, the transform takes multiplications.
The Cooley–Tukey algorithm
For a power of two, split the polynomial by the parity of the exponents, , where and have the even- and odd-indexed coefficients. The squares of the -th roots of unity are the -th roots of unity, each occurring twice, since and . So with and the transforms of length of and :
Each pair of outputs costs one multiplication by , the twiddle factor, and an addition and a subtraction: a butterfly. The cost satisfies , so , with exactly twiddle multiplications[3].
The transform from the definition, the recursive FFT, the inverse, and polynomial multiplication.
(* The discrete Fourier transform, directly in O(n^2) and by therecursive radix-2 Cooley-Tukey FFT in O(n log n). *)open Complexlet omega n k = polar 1. (-2. *. Float.pi *. float k /. float n) (* e^(-2 pi i k / n) *)let mults = ref 0let ( *! ) a b = incr mults; mul a blet dft a =let n = Array.length a inArray.init n (fun k -> let s = ref zero in Array.iteri (fun j x -> s := add !s (x *! omega n (j * k mod n))) a; !s)(* Split into even and odd positions, transform each half, and combinewith the butterflies X_k = E_k + w^k O_k, X_{k+n/2} = E_k - w^k O_k. *)let rec fft a =let n = Array.length a inif n = 1 then [| a.(0) |]else beginlet e = fft (Array.init (n / 2) (fun i -> a.(2 * i))) and o = fft (Array.init (n / 2) (fun i -> a.((2 * i) + 1))) inlet x = Array.make n zero infor k = 0 to (n / 2) - 1 dolet t = omega n k *! o.(k) inx.(k) <- add e.(k) t;x.(k + (n / 2)) <- sub e.(k) tdone;xend(* The inverse transform is the forward one on conjugates, scaled by 1/n. *)let ifft x = let n = float (Array.length x) in Array.map (fun z -> div (conj z) { re = n; im = 0. }) (fft (Array.map conj x))(* Multiplying polynomials: transform, multiply pointwise, transform back. *)let poly_mul a b =let len = Array.length a + Array.length b - 1 inlet n = let rec up n = if n >= len then n else up (2 * n) in up 1 inlet pad p = Array.init n (fun i -> if i < Array.length p then { re = float p.(i); im = 0. } else zero) inlet c = ifft (Array.map2 mul (fft (pad a)) (fft (pad b))) inArray.init len (fun i -> Float.to_int (Float.round c.(i).re))let schoolbook a b =let r = Array.make (Array.length a + Array.length b - 1) 0 inArray.iteri (fun i x -> Array.iteri (fun j y -> r.(i + j) <- r.(i + j) + (x * y)) b) a; r
Running it.
n mults (DFT) mults (FFT) max |diff|16 256 32 2.33e-1564 4096 192 6.28e-15256 65536 1024 4.01e-141024 1048576 5120 1.15e-13ifft (fft a) = a to within 5.00e-16 for n = 4096product of two degree-1999 polynomials with coefficients below 1000 equals schoolbook: true
The two transforms agree to within rounding error, and the number of multiplications drops from to : about two hundred times fewer at . Unrolling the recursion gives the in-place form in the figure, which first permutes the input into bit-reversed order and then runs stages of butterflies over the array[3].
Other factorizations
The splitting works for any factorization : the transform of length becomes transforms of length , twiddle multiplications, and transforms of length . Radix-4 and split-radix variants reduce the operation count further, and when and are coprime, Good's prime-factor algorithm needs no twiddle factors at all[10]. Libraries such as FFTW choose among these at run time by measuring which is fastest on the machine[7].
Convolution and multiplication
The product of two polynomials has as coefficients the convolution of their coefficient sequences, . Evaluating at the roots of unity turns the product into a pointwise one, the convolution theorem[3]:
with both sequences padded with zeros to a length at least the length of the product, so the cyclic convolution computed by the transform does not wrap around. Two polynomials of degree below are multiplied in operations, against by the schoolbook method and by Karatsuba. With floating-point arithmetic the result has to be rounded to integers, which is exact only while the rounding errors stay below one half; for large inputs or large coefficients this limits the method[11].
Exact transforms
The transform only needs a primitive -th root of unity and the inverse of , so it works in any ring that has them. Modulo a prime with , the multiplicative group is cyclic of order and contains a primitive -th root of unity[6]. The prime has roots of unity of every order up to , and the transform over it, the number-theoretic transform, computes convolutions exactly as long as the true coefficients are below :
An iterative, in-place number-theoretic transform, and multiplication of polynomials and decimal integers with it.
(* The number-theoretic transform: the same algorithm over Z/pZ withp = 998244353 = 119 * 2^23 + 1, where 3 generates the multiplicativegroup, so there are n-th roots of unity for every n = 2^k up to 2^23.Arithmetic is exact. This version is iterative and in place. *)let p = 998_244_353let rec pw b e = if e = 0 then 1 else let h = pw (b * b mod p) (e / 2) in if e land 1 = 1 then h * b mod p else hlet ntt a invert =let n = Array.length a in(* Reorder by bit-reversed index, so the butterflies can work in place. *)let j = ref 0 infor i = 1 to n - 1 dolet bit = ref (n lsr 1) inwhile !j land !bit <> 0 do j := !j lxor !bit; bit := !bit lsr 1 done;j := !j lor !bit;if i < !j then (let t = a.(i) in a.(i) <- a.(!j); a.(!j) <- t)done;let len = ref 2 inwhile !len <= n dolet w = pw 3 ((p - 1) / !len) inlet w = if invert then pw w (p - 2) else w inlet i = ref 0 inwhile !i < n dolet wk = ref 1 infor k = 0 to (!len / 2) - 1 dolet u = a.(!i + k) and v = a.(!i + k + (!len / 2)) * !wk mod p ina.(!i + k) <- (u + v) mod p;a.(!i + k + (!len / 2)) <- (u - v + p) mod p;wk := !wk * w mod pdone;i := !i + !lendone;len := 2 * !lendone;if invert then (let ni = pw n (p - 2) in Array.iteri (fun i x -> a.(i) <- x * ni mod p) a)let mul a b =let len = Array.length a + Array.length b - 1 inlet n = let rec up n = if n >= len then n else up (2 * n) in up 1 inlet fa = Array.init n (fun i -> if i < Array.length a then a.(i) else 0)and fb = Array.init n (fun i -> if i < Array.length b then b.(i) else 0) inntt fa false; ntt fb false;let c = Array.map2 (fun x y -> x * y mod p) fa fb inntt c true;Array.sub c 0 len(* Big integers as arrays of decimal digits, least significant first:multiply the digit polynomials, then propagate carries. *)let big_mul x y =let digits s = Array.init (String.length s) (fun i -> Char.code s.[String.length s - 1 - i] - 48) inlet c = mul (digits x) (digits y) inlet buf = Buffer.create (Array.length c + 1) and carry = ref 0 inlet out = Array.map (fun d -> let t = d + !carry in carry := t / 10; t mod 10) c inlet rest = ref [] inwhile !carry > 0 do rest := (!carry mod 10) :: !rest; carry := !carry / 10 done;List.iter (fun d -> Buffer.add_char buf (Char.chr (48 + d))) !rest;for i = Array.length out - 1 downto 0 do Buffer.add_char buf (Char.chr (48 + out.(i))) done;let s = Buffer.contents buf inlet k = ref 0 in while !k < String.length s - 1 && s.[!k] = '0' do incr k done;String.sub s !k (String.length s - !k)
Running it; the integer product agrees with Python's.
degree-49999 product: coefficients 0, 1, 12345, 49999, 75000, 99998 checked against the convolution sum, all equal: true123456789123456789123456789123456789123456789123456789123456* 987654321987654321987654321987654321987654321= 121932631356500531591068431825636332060204232172839501172838721791646821557078921322511021087943120853376
When the coefficients may be larger, the convolution is computed modulo several such primes and the results combined by the Chinese remainder theorem, as in the modular algorithms of computer algebra[8].
Integer multiplication
Writing integers as polynomials in a base , with their digits as coefficients, and evaluating the product at by propagating carries, reduces integer multiplication to polynomial multiplication. Schönhage and Strassen performed the transform over the ring , in which 2 is a root of unity and the twiddle multiplications are shifts, and multiplied -bit integers in bit operations[4]. Harvey and van der Hoeven reached in 2019, published in 2021, the bound Schönhage and Strassen had conjectured to be optimal[5]. Arbitrary-precision libraries such as GMP switch from schoolbook to Karatsuba, Toom–Cook and finally FFT-based multiplication as the operands grow.
Applications
In signal processing the transform computes the frequency spectrum of a sampled signal, and fast convolution implements digital filters and correlation. Image and audio compression use its real-valued relative, the discrete cosine transform. Spectral methods for differential equations differentiate in the frequency domain. In computer algebra, fast polynomial multiplication gives fast division through Newton iteration, fast multipoint evaluation and interpolation, and fast greatest common divisors[8].
History
Gauss used the method in 1805 to interpolate the orbits of asteroids, in work published after his death; Heideman, Johnson and Burrus traced this and several later rediscoveries[2]. Good described the prime-factor algorithm in 1958[10]. Cooley and Tukey published the radix-2 algorithm in 1965, when digital computers made it immediately useful[1], and Gentleman and Sande analysed its variants in 1966[9]. Pollard introduced transforms over finite fields in 1971[6], the year Schönhage and Strassen applied the FFT to integer multiplication[4].
see also
- Karatsuba multiplicationMultiplying two n-limb integers with three half-size multiplications instead of four, by reusing (a0+a1)(b0+b1) to obtain the middle term. That turns the schoolbook n^2 into about n^1.585, and it only pays above a threshold where the extra additions cost less than the multiplication saved.
- Horner's methodHorner's method evaluates a polynomial a_n x^n + ... + a_0 by nesting the multiplications, (...(a_n x + a_(n-1)) x + ...) x + a_0, which takes n multiplications and n additions, the fewest possible for a general polynomial. The intermediate values are the coefficients of the quotient on division by x - c, so the same loop performs synthetic division, and repeated divisions give the derivatives and the shifted polynomial p(x + c). It is backward stable, but near a multiple root the result can be dominated by rounding errors.
- Modular GCDComputing a polynomial GCD over Z by computing it modulo several machine-word primes and reconstructing by the Chinese remainder theorem, stopping once the modulus provably exceeds twice the largest coefficient the answer could have. The answer is small even when the road to it is not, which is why this is orders of magnitude faster than any remainder sequence.
further reading
- [1]J. W. Cooley, J. W. Tukey, “An algorithm for the machine calculation of complex Fourier series”, Mathematics of Computation 19 (1965).
- [2]M. T. Heideman, D. H. Johnson, C. S. Burrus, “Gauss and the history of the fast Fourier transform”, IEEE ASSP Magazine 1 (1984).
- [3]T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, ch. 30, MIT Press (3rd ed., 2009).
- [4]A. Schönhage, V. Strassen, “Schnelle Multiplikation großer Zahlen”, Computing 7 (1971).
- [5]D. Harvey, J. van der Hoeven, “Integer multiplication in time O(n log n)”, Annals of Mathematics 193 (2021).
- [6]J. M. Pollard, “The fast Fourier transform in a finite field”, Mathematics of Computation 25 (1971).
- [7]M. Frigo, S. G. Johnson, “The design and implementation of FFTW3”, Proceedings of the IEEE 93 (2005).
- [8]J. von zur Gathen, J. Gerhard, Modern Computer Algebra, ch. 8, Cambridge University Press (3rd ed., 2013).
- [9]W. M. Gentleman, G. Sande, “Fast Fourier transforms: for fun and profit”, AFIPS Fall Joint Computer Conference (1966).
- [10]I. J. Good, “The interaction algorithm and practical Fourier analysis”, Journal of the Royal Statistical Society B 20 (1958).
- [11]D. E. Knuth, The Art of Computer Programming, Vol. 2, §4.3.3, Addison-Wesley (3rd ed., 1997).
last updated