The FFT and Polynomial Multiplication in Θ(n lg n)
Objective
Understand how two polynomials of degree-bound n can be multiplied in Θ(n lg n) time instead of the obvious Θ(n²). The trick is not a cleverer way to multiply coefficients — it is a change of representation: polynomials in point-value form multiply in Θ(n) time, so the whole problem collapses to converting quickly between coefficient form and point-value form. CLRS shows that if the evaluation points are the complex roots of unity, that conversion is exactly the discrete Fourier transform (DFT) and its inverse, and the fast Fourier transform (FFT) computes both in Θ(n lg n) by a divide-and-conquer split into even-indexed and odd-indexed coefficients.
Use Cases
- Multiplying two polynomials given in coefficient form, and computing convolutions — the coefficient vector
cof the product is exactly the convolutiona ⊗ bof the two input coefficient vectors. CLRS calls polynomial multiplication and convolution "fundamental computational problems of considerable practical importance", which is why the chapter concentrates on efficient algorithms for them. - Evaluating a polynomial of degree-bound
nat allncomplexnth roots of unity at once, inΘ(n lg n)— versusΘ(n²)if you just call Horner's rulentimes. - Interpolating a polynomial back from
npoint-value pairs, when the points are the roots of unity: the inverse DFT does it inΘ(n lg n), versusO(n³)for solving the Vandermonde linear system directly orΘ(n²)for Lagrange's formula. - Hardware implementations: CLRS notes that many of the FFT's applications in signal processing require the utmost speed, so the FFT is often built as a circuit — and its divide-and-conquer structure gives a parallel circuit of depth only
Θ(lg n).
Deep Dive
Two representations of the same polynomial
A coefficient representation of a polynomial A(x) = Σ_{j=0}^{n-1} a_j x^j of degree-bound n is just the vector a = (a0, a1, ..., a_{n-1}). This form is convenient for some operations and terrible for others:
| operation | cost in coefficient form |
|---|---|
evaluate A(x0) at one point (Horner's rule) |
Θ(n) |
add two polynomials (c_j = a_j + b_j) |
Θ(n) |
| multiply two polynomials | Θ(n²) |
Evaluation at a single point is linear thanks to Horner's rule, which nests the multiplications:
plaintextA(x0) = a0 + x0*(a1 + x0*(a2 + ... + x0*(a_{n-2} + x0*(a_{n-1}))...))
Multiplication is the odd one out. The straightforward method multiplies each coefficient of a by each coefficient of b, so it costs Θ(n²). The resulting coefficient vector c is also called the convolution of the input vectors, written c = a ⊗ b.
A point-value representation of a polynomial of degree-bound n is a set of n point-value pairs {(x0, y0), (x1, y1), ..., (x_{n-1}, y_{n-1})} where all the x_k are distinct and y_k = A(x_k). A polynomial has many point-value representations, since any set of n distinct points can serve as the basis.
The two forms are genuinely equivalent — a polynomial in point-value form has a unique counterpart in coefficient form:
Theorem 30.1 (Uniqueness of an interpolating polynomial). For any set of
npoint-value pairs with allx_kdistinct, there is a unique polynomialA(x)of degree-boundnsuch thaty_k = A(x_k)for allk.
The proof writes y_k = A(x_k) as the matrix equation V(x0, ..., x_{n-1}) · a = y, where V is the Vandermonde matrix whose row k is (1, x_k, x_k², ..., x_k^{n-1}). That matrix's determinant is the product of (x_k - x_j) over all j < k, so it is nonzero — the matrix is invertible — exactly when the x_k are distinct, and a = V^{-1} y recovers the coefficients uniquely.
So evaluation (coefficient form → point-value form) and interpolation (point-value form → coefficient form) are well-defined inverse operations. The obvious algorithms for both are quadratic or worse: Horner's rule at n points is Θ(n²); interpolation by solving the Vandermonde system is O(n³) (Section 28.1) and by Lagrange's formula is Θ(n²) (Exercise 30.1-5).
Why point-value form makes multiplication trivial — and the degree-bound catch
In point-value form, both addition and multiplication are pointwise, provided both polynomials are evaluated at the same points. If C(x) = A(x)·B(x), then C(x_k) = A(x_k)·B(x_k) for every point x_k, so multiplying is just n scalar multiplications: Θ(n).
There is one catch. degree(C) = degree(A) + degree(B), so if A and B have degree-bound n, then C has degree-bound 2n — and 2n point-value pairs are needed to pin down a unique polynomial of degree-bound 2n, not n (Exercise 30.1-4). The fix is to work with extended point-value representations of 2n pairs each for A and B from the start, obtained by first padding both coefficient vectors with n high-order zero coefficients.
The strategy: evaluate, multiply pointwise, interpolate
The Θ(n) multiplication in point-value form only pays off if converting between representations is cheap. Any points can serve as evaluation points, but certain points allow conversion in only Θ(n lg n) time — the complex roots of unity, where evaluation is the DFT and interpolation is the inverse DFT. That gives the four-step algorithm (assuming n is an exact power of 2; if not, pad with high-order zero coefficients):
- Double degree-bound — create coefficient representations of
A(x)andB(x)as degree-bound2npolynomials by addingnhigh-order zero coefficients to each.Θ(n) - Evaluate — compute point-value representations of length
2nby applying the FFT of order2nto each, giving the values of both polynomials at the(2n)th roots of unity.Θ(n lg n) - Pointwise multiply — multiply the two value vectors componentwise to get a point-value representation of
C(x) = A(x)B(x)at each(2n)th root of unity.Θ(n) - Interpolate — apply the FFT to the
2npoint-value pairs to compute the inverse DFT, producing the coefficient representation ofC(x).Θ(n lg n)
Theorem 30.2. Two polynomials of degree-bound
n, with both input and output representations in coefficient form, can be multiplied inΘ(n lg n)time.
Complex roots of unity: the three properties that make it work
A complex nth root of unity is a complex number ω with ω^n = 1. There are exactly n of them, e^(2πik/n) for k = 0, 1, ..., n-1, interpreted through e^(iu) = cos(u) + i·sin(u). They sit equally spaced around the unit circle in the complex plane. The value
plaintextω_n = e^(2πi/n)
is the principal nth root of unity; all the others are powers of it, so the n roots are ω_n^0, ω_n^1, ..., ω_n^{n-1}. (CLRS notes that many authors instead define ω_n = e^(-2πi/n), the convention typically used for signal-processing applications; the underlying mathematics is substantially the same either way. Every formula here uses the book's e^(2πi/n).) They form a group under multiplication with the same structure as the additive group of integers modulo n: ω_n^j · ω_n^k = ω_n^{(j+k) mod n}, and ω_n^{-1} = ω_n^{n-1}.
Three lemmas carry the whole algorithm:
- Cancellation lemma (30.3). For integers
n > 0,k ≥ 0,d > 0:ω_{dn}^{dk} = ω_n^k. It follows directly from the definition, since(e^(2πi/dn))^{dk} = (e^(2πi/n))^k. - Corollary 30.4. For any even
n > 0:ω_n^{n/2} = ω_2 = -1. This is what makes each butterfly's second output a subtraction rather than a separate multiplication —ω_n^{k+n/2} = -ω_n^k. - Halving lemma (30.5). If
n > 0is even, the squares of thencomplexnth roots of unity are then/2complex(n/2)th roots of unity, with each one occurring exactly twice — becauseω_n^kandω_n^{k+n/2}have the same square. CLRS calls this lemma essential to the divide-and-conquer approach: it is what guarantees the recursive subproblems are only half as large. - Summation lemma (30.6). For any integer
n ≥ 1and nonzero integerknot divisible byn, the sumΣ_{j=0}^{n-1} (ω_n^k)^j = 0. The geometric-series formula gives((ω_n^k)^n - 1) / (ω_n^k - 1) = ((ω_n^n)^k - 1) / (ω_n^k - 1) = (1^k - 1) / (ω_n^k - 1) = 0, and the denominator is nonzero precisely becauseω_n^k = 1only whenkis divisible byn. This lemma is what makes the inverse DFT matrix work out.
The DFT and the FFT's even/odd split
Evaluating A(x) = Σ a_j x^j at the n complex nth roots of unity defines
plaintexty_k = A(ω_n^k) = Σ_{j=0}^{n-1} a_j · ω_n^{kj} for k = 0, 1, ..., n-1
The vector y = (y0, ..., y_{n-1}) is the discrete Fourier transform of a, written y = DFT_n(a). Computed straight from that definition it costs Θ(n²).
The FFT gets it to Θ(n lg n) by splitting A by coefficient index parity — even-indexed coefficients into one polynomial, odd-indexed into the other, each of degree-bound n/2:
plaintextA_even(x) = a0 + a2·x + a4·x² + ... + a_{n-2}·x^(n/2-1) A_odd(x) = a1 + a3·x + a5·x² + ... + a_{n-1}·x^(n/2-1)
which recombine as the key identity
plaintextA(x) = A_even(x²) + x · A_odd(x²) (equation 30.9)
So evaluating A at ω_n^0, ..., ω_n^{n-1} reduces to (1) evaluating A_even and A_odd at the squares (ω_n^0)², ..., (ω_n^{n-1})², then (2) combining with equation 30.9. And by the halving lemma, that list of squares is not n distinct values at all — it is the n/2 complex (n/2)th roots of unity, each appearing twice. The subproblems therefore have exactly the same form as the original, at half the size: one DFT_n becomes two DFT_{n/2}s.
java// A complex number, since the FFT evaluates at complex roots of unity.
record Complex(double re, double im) {
Complex plus(Complex o) { return new Complex(re + o.re, im + o.im); }
Complex minus(Complex o) { return new Complex(re - o.re, im - o.im); }
Complex times(Complex o) { return new Complex(re * o.re - im * o.im, re * o.im + im * o.re); }
}
// FFT(a, n) — n must be an exact power of 2, and a.length == n.
static Complex[] fft(Complex[] a, int n) {
if (n == 1) {
return a; // the DFT of 1 element is the element itself
}
// ωn = e^(2πi/n) = cos(2π/n) + i·sin(2π/n), the principal nth root of unity
Complex wn = new Complex(Math.cos(2 * Math.PI / n), Math.sin(2 * Math.PI / n));
Complex w = new Complex(1, 0); // running value of ωn^k, k = 0 at the start
Complex[] aEven = new Complex[n / 2];
Complex[] aOdd = new Complex[n / 2];
for (int j = 0; j < n / 2; j++) {
aEven[j] = a[2 * j]; // (a0, a2, ..., a_{n-2})
aOdd[j] = a[2 * j + 1]; // (a1, a3, ..., a_{n-1})
}
Complex[] yEven = fft(aEven, n / 2); // conquer: two DFTs of half the size
Complex[] yOdd = fft(aOdd, n / 2);
Complex[] y = new Complex[n];
for (int k = 0; k < n / 2; k++) { // at this point, w == ωn^k
Complex t = w.times(yOdd[k]); // the twiddled term, computed once (a butterfly)
y[k] = yEven[k].plus(t); // yk = y_even_k + ωn^k · y_odd_k
y[k + n / 2] = yEven[k].minus(t); // y_{k+n/2}, using ωn^{k+n/2} = -ωn^k
w = w.times(wn); // advance the running twiddle factor
}
return y;
}The base case is the n = 1 line: the DFT of one element is the element itself, since y0 = a0·ω_1^0 = a0. The combine loop is where equation 30.9 is applied. The recursive calls compute y_even_k = A_even(ω_{n/2}^k) and y_odd_k = A_odd(ω_{n/2}^k), which by the cancellation lemma equal A_even(ω_n^{2k}) and A_odd(ω_n^{2k}). So the first output line yields y_k = A_even(ω_n^{2k}) + ω_n^k·A_odd(ω_n^{2k}) = A(ω_n^k), and the second yields A(ω_n^{k+n/2}) — using ω_n^{k+n/2} = -ω_n^k and ω_n^{2k+n} = ω_n^{2k}. Because each factor ω_n^k appears in both its positive and negative forms, CLRS calls those factors twiddle factors.
Exclusive of the recursive calls, each invocation does Θ(n) work, so the recurrence is the familiar one:
plaintextT(n) = 2·T(n/2) + Θ(n) = Θ(n lg n)
by case 2 of the master theorem. Note the running-w optimization has a cost: CLRS points out that iteratively updating ω lets round-off errors accumulate, especially at larger sizes, and suggests directly precomputing a table of all n/2 values of ω_n^k when several FFTs will run on inputs of the same size.
Watch the divide step: the recursion tree for n = 8
Figure 30.5 arranges the input vectors of every recursive call into a tree — the initial call at the root, each node's two recursive calls as its left (even-indexed) and right (odd-indexed) children, down to 1-element leaves:
Read the leaves left to right and they come out in the order 0, 4, 2, 6, 1, 5, 3, 7 — in binary, 000, 100, 010, 110, 001, 101, 011, 111, which is exactly the sequence 000, 001, 010, 011, 100, 101, 110, 111 with the bits of each index reversed. That ordering is the bit-reversal permutation: element a_k moves to position rev(k), where rev(k) reverses the lg n bits of k. It falls out of the divide step directly — at the top level, indices whose low-order bit is 0 go left and whose low-order bit is 1 go right, and stripping off one more low-order bit at each level down produces the bit-reversed order at the leaves.
That matters because it means the recursion can be run bottom-up: start from the vector permuted into leaf order, combine adjacent pairs with one butterfly each to get n/2 two-element DFTs, combine those in pairs to get n/4 four-element DFTs, and so on until two (n/2)-element DFTs are combined into the final n-element DFT.
A worked trace: the DFT of (0, 1, 2, 3)
Take a = (0, 1, 2, 3), so n = 4 and ω_4 = e^(2πi/4) = i. Trace fft(a, 4) by hand:
| step | computation | result |
|---|---|---|
| divide | a_even = (a0, a2), a_odd = (a1, a3) |
(0, 2) and (1, 3) |
| conquer left | fft((0,2), 2): ω_2 = -1, so y0 = 0 + 2, y1 = 0 - 2 |
y_even = (2, -2) |
| conquer right | fft((1,3), 2): y0 = 1 + 3, y1 = 1 - 3 |
y_odd = (4, -2) |
combine k = 0 |
w = ω_4^0 = 1, t = 1·4 = 4; y0 = 2 + 4, y2 = 2 - 4 |
y0 = 6, y2 = -2 |
combine k = 1 |
w = ω_4^1 = i, t = i·(-2) = -2i; y1 = -2 + (-2i), y3 = -2 - (-2i) |
y1 = -2 - 2i, y3 = -2 + 2i |
So DFT_4(0, 1, 2, 3) = (6, -2-2i, -2, -2+2i). Checking directly against the definition y_k = Σ a_j ω_4^{kj} with ω_4 = i confirms each entry — e.g. y1 = 0 + 1·i + 2·i² + 3·i³ = i - 2 - 3i = -2 - 2i. Six element-level operations at the two leaves' combines plus four in the top combine, instead of the 16 products the definition's double sum would take.
Interpolation: the inverse DFT is the same algorithm, twice modified
Written as a matrix product, the DFT is y = V_n · a, where V_n is the Vandermonde matrix of powers of ω_n: its (k, j) entry is ω_n^{kj}, so the exponents form a multiplication table for the factors 0 through n-1. Interpolation is therefore a = V_n^{-1} · y, and the inverse has a strikingly simple closed form:
Theorem 30.7. For
j, k = 0, 1, ..., n-1, the(j, k)entry ofV_n^{-1}isω_n^{-kj} / n.
The proof shows V_n^{-1} V_n = I_n by computing the (k', k) entry as Σ_{j=0}^{n-1} ω_n^{j(k'-k)} / n, which is 1 when k' = k and 0 otherwise by the summation lemma — the lemma applies because k' - k lies strictly between -(n-1) and n-1 and so is never a nonzero multiple of n. Written out:
plaintexta_j = (1/n) · Σ_{k=0}^{n-1} y_k · ω_n^{-kj} for j = 0, 1, ..., n-1 (equation 30.11)
Compare that with the forward DFT y_k = Σ_j a_j ω_n^{kj} and the recipe for the inverse falls out: switch the roles of a and y, replace ω_n by ω_n^{-1}, and divide each element of the result by n. Nothing else about the FFT changes, so DFT_n^{-1} is also computable in Θ(n lg n). (CLRS leaves writing that pseudocode as Exercise 30.2-4; the modification is exactly the three changes above.)
Putting the forward and inverse transforms together gives the chapter's headline result about convolution:
Theorem 30.8 (Convolution theorem). For any two vectors
aandbof lengthn, wherenis an exact power of 2,a ⊗ b = DFT_{2n}^{-1}(DFT_{2n}(a) · DFT_{2n}(b)), whereaandbare padded with 0s to length2nand·denotes the componentwise product of two2n-element vectors.
End to end: multiplying (1 + 2x) by (3 + 4x)
Two degree-bound 2 polynomials, so pad both to length 2n = 4 and use ω_4 = i. The answer by hand is (1 + 2x)(3 + 4x) = 3 + 10x + 8x²; here is the same result through the four-step algorithm:
| step | from A(x) = 1 + 2x |
from B(x) = 3 + 4x |
combined |
|---|---|---|---|
| 1. double degree-bound | a = (1, 2, 0, 0) |
b = (3, 4, 0, 0) |
— |
2. evaluate at ω_4^k = 1, i, -1, -i |
(3, 1+2i, -1, 1-2i) |
(7, 3+4i, -1, 3-4i) |
— |
| 3. pointwise multiply | — | — | (21, -5+10i, 1, -5-10i) |
| 4. inverse DFT, divide by 4 | — | — | c = (3, 10, 8, 0) |
Step 4 spelled out for one entry: c0 = (21 + (-5+10i) + 1 + (-5-10i)) / 4 = 12 / 4 = 3. The final coefficient vector (3, 10, 8, 0) reads back as 3 + 10x + 8x² + 0x³ — the correct product, and the trailing zero confirms the padded degree-bound 4 was more than enough room.
FFT circuits: butterflies and Θ(lg n) depth
Section 30.3 recasts the same algorithm as hardware. Notice that the combine loop computes ω_n^k · y_odd_k twice — once for the sum, once for the difference — so a good optimizing compiler hoists it into a temporary, turning the two output lines into three:
plaintextt = ω · y_odd_k y_k = y_even_k + t y_{k+n/2} = y_even_k - t
This — multiply the twiddle factor by y_odd_k, store it in t, then add and subtract t from y_even_k — is a butterfly operation, named for the shape of its circuit diagram. (CLRS jokes it could equally have been called a "bowtie" operation.) The Java code above already writes it in this hoisted form.
In circuit terms the divide-and-conquer structure reads as: divide the n-element input into its n/2 even-indexed and n/2 odd-indexed elements; conquer by recursively computing two DFTs of size n/2; combine with n/2 butterfly operations using twiddle factors ω_n^0, ω_n^1, ..., ω_n^{n/2-1}. The base case FFT_1 does nothing at all (one input wire equals one output wire), so the smallest nontrivial circuit is FFT_2: a single butterfly whose twiddle factor is ω_2^0 = 1.
The full circuit begins with the bit-reversal permutation of the inputs, then runs lg n stages, each stage consisting of n/2 butterflies executed in parallel — the butterfly operations at a given level of recursion are independent of one another. For s = 1, 2, ..., lg n, stage s consists of n/2^s groups of butterflies with 2^{s-1} butterflies per group, and its twiddle factors are ω_m^0, ω_m^1, ..., ω_m^{m/2-1} where m = 2^s. Assuming each butterfly has constant depth, the whole circuit has depth Θ(lg n) while still performing Θ(n lg n) butterfly operations in total.
Trade-offs
- The 4th edition dropped the iterative FFT — don't go looking for it here. CLRS's preface states that the iterative FFT implementation was removed from the 4th edition and moved to the publisher's website; the printed Chapter 30 contains only the recursive divide-and-conquer
FFTprocedure plus the circuit view of Section 30.3. That is why this concept shows no iterative, in-place, bit-reversal-then-lg n-stages implementation: the material for it is in the text only as the circuit schema (Figures 30.4 and 30.6) and as Exercise 30.3-4, which asks the reader to writeBIT-REVERSE-PERMUTATIONthemselves. The bottom-up reading of the recursion tree in the Deep Dive is the conceptual bridge, not a substitute for that removed code. Θ(n lg n)only pays off past a crossover. The FFT route replaces a single tightΘ(n²)double loop over integer or double coefficients with four phases involving complex arithmetic, padding, and two full transforms. Nothing in the chapter claims it is faster at smalln— the asymptotic win is what is proven (Theorem 30.2), and the constant factors of complex multiply-add work against it untilnis large enough.nmust be an exact power of 2. TheFFTprocedure assumes it throughout, and the polynomial-multiplication procedure assumes it too, telling you to add high-order zero coefficients when it does not hold. CLRS states that strategies for handling sizes that are not exact powers of 2 are known but beyond the scope of the book — that is a genuine gap in this text, not an omission here.- Floating-point round-off is a real hazard, and largely unaddressed. Two separate warnings appear. First, interpolation is described as "a notoriously tricky problem from the point of view of numerical stability": the approaches given are mathematically correct, but small differences in the inputs or round-off during computation can cause large differences in the result. Second, the running-
ωupdate inside the combine loop accumulates round-off, especially at larger input sizes; several techniques to limit FFT round-off error have been proposed but are also beyond the book's scope. The one concrete mitigation offered is to precompute a table of alln/2values ofω_n^kwhen running several same-size FFTs. - Point-value form is not a universally better representation. It makes addition and multiplication
Θ(n), but it is worse at other things. Evaluating a point-value polynomial at a new point has no better approach known than converting back to coefficient form first and then evaluating there. Polynomial division by dividing the correspondingyvalues is explicitly flagged as wrong (Exercise 30.1-6). And an extended representation of2npairs must be chosen up front, before the multiplication, becausenpairs cannot pin down the degree-bound2nproduct. - The choice of
ω_nsign is a convention you have to keep straight. This chapter usesω_n = e^(2πi/n); many other authors, particularly in signal processing, defineω_n = e^(-2πi/n). CLRS says the underlying mathematics is substantially the same either way, but the transform values you compute will differ, so mixing sources without checking the convention will produce results that disagree entry by entry.