(1 + 2x + 3x²)(4 + 5x): three terms times two terms. Watch the six products fill a grid and collect into four coefficients.
Multiplying two polynomials
Every term times every term, collected by power
static int products = 0; void poly_mul(const int *a, int na, const int *b, int nb, int *c){ for (int k = 0; k < na + nb - 1; k++) c[k] = 0; for (int i = 0; i < na; i++) { for (int j = 0; j < nb; j++) { c[i + j] += a[i] * b[j]; products++; } }} int main(void){ const int a[3] = { 1, 2, 3 }; const int b[2] = { 4, 5 }; int c[4]; poly_mul(a, 3, b, 2, c); printf("c:"); for (int k = 0; k < 4; k++) printf(" %d", c[k]); printf(", %d products\n", products); return 0;}a = 1 + 2x + 3x²: a[i] is the coefficient of xⁱ.
- products
- 0
Output
Describe this picture
The picture is a grid of 3 rows and 2 columns: the rows are labelled with the terms of a, 1, 2x and 3x², and the columns with the terms of b, 4 and 5x. Under it is a strip of 4 boxes, c, the coefficient of xᵏ in box k. Each grid cell is tied to the box of its power: the cells of one anti-diagonal, running from upper right to lower left, share a power and a box. Under the grid is a line of text with the current product or sum. Under the picture are the program poly-mul.c, with the line about to run marked, one sentence on the current step, a readout “products”, and the output. The clip plays once, about 16 seconds. The strip is cleared to 0 0 0 0. Then the six products are made row by row, and each fills its cell and flies down into its box: 1 × 4 = 4 into c[0], 1 × 5x = 5x and 2x × 4 = 8x into c[1], 2x × 5x = 10x² and 3x² × 4 = 12x² into c[2], and 3x² × 5x = 15x³ into c[3]. The strip ends at 4, 13, 22 and 15. While the program prints c, each anti-diagonal lights in turn with its sum: c[1] = 5 + 8 = 13 and c[2] = 10 + 12 = 22. The clip rests on the output c: 4 13 22 15, 6 products. Watch only: there is no control. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as multiply.step.
Every term times every term
To multiply two polynomials, multiply every term of one by every term of the other and collect the products by power. For a = 1 + 2x + 3x² and b = 4 + 5x, whose terms are 4 and 5x:
| Term of a | First term of b | Second term of b |
|---|---|---|
| 1 | 4 | 5x |
| 2x | 8x | 10x² |
| 3x² | 12x² | 15x³ |
Six products, but only four powers: the two x terms add to 13x, and the two x² terms to 22x². The product is 4 + 13x + 22x² + 15x³. It does not have m · n terms: like terms collect.
The code and its cost
The double loop
With dense coefficient arrays, term i of a times term j of b lands on power i + j:
void poly_mul(const int *a, int na, const int *b, int nb, int *c)
{
for (int k = 0; k < na + nb - 1; k++)
c[k] = 0;
for (int i = 0; i < na; i++) {
for (int j = 0; j < nb; j++) {
c[i + j] += a[i] * b[j];
products++;
}
}
}
The product of a degree-(na − 1) and a degree-(nb − 1) polynomial has degree na + nb − 2, so na + nb − 1 coefficients. The program prints
c: 4 13 22 15, 6 products
m terms times n terms take m · n products.
A convolution
Collect the products by power instead of by row: coefficient k gathers every a[i] · b[j] with i + j = k, that is
c[k] = Σᵢ a[i] · b[k − i],
over the i for which both indices are in range. That is the convolution of the two coefficient arrays: multiplying polynomials and filtering a signal are the same computation. In the grid, each sum runs along an anti-diagonal. The Toeplitz matrices lesson writes the same sum as a matrix product.
Sparse polynomials
For polynomials stored as lists of terms, multiply b by one term of a at a time, and add the partial products with the merge of adding polynomials:
struct pnode *poly_mul(const struct pnode *a, const struct pnode *b)
{
struct pnode *result = NULL;
for (const struct pnode *p = a; p != NULL; p = p->next)
result = poly_add(result, times_term(b, p->coeff, p->exp));
return result;
}
times_term builds a new list, b’s terms each multiplied by one term: the coefficients multiply and the exponents add. The program poly-mul-sparse.c multiplies (x¹⁰⁰ + 1)(x¹⁰⁰ − 1):
product: +1x^200 -1x^0
That takes 4 products, and the two x¹⁰⁰ terms cancel inside poly_add. With dense arrays of 101 coefficients each, the double loop would make 101 × 101 = 10,201 products, nearly all of them 0.
Faster methods
For dense polynomials of n coefficients the double loop takes n² products. Two faster methods exist. Split each polynomial into a low half and a high half; the product needs the three half-size products lo × lo, hi × hi and (lo + hi) × (lo + hi), instead of four, and repeating the split down to single terms takes about n^1.585 products. This is Karatsuba’s method, a later lesson. Or evaluate both polynomials with the FFT at as many points as the product has coefficients, 2n − 1, rounded up to a power of two in practice, multiply the values, and convert back, about n log n steps: fast convolution.
Running it yourself
gcc -std=c11 -Wall -Wextra -pedantic poly-mul.c -o poly-mul
./poly-mul
gcc -std=c11 -Wall -Wextra -pedantic poly-mul-sparse.c -o poly-mul-sparse
./poly-mul-sparse
Worked example
A square. (1 + x + x² + x³)² makes 4 × 4 = 16 products. The anti-diagonals of the 4 × 4 grid have 1, 2, 3, 4, 3, 2 and 1 cells, so the coefficients are 1 2 3 4 3 2 1.
One coefficient. The coefficient of x² in the program’s product: a[0] · b[2] + a[1] · b[1] + a[2] · b[0] = 1 · 0 + 2 · 5 + 3 · 4 = 22, since b has no x² term.
Large polynomials. Two polynomials of degree 1,023 have 1,024 coefficients each: 1,024² = 1,048,576 products by the double loop. Splitting in halves ten times, down to single terms, takes 3¹⁰ = 59,049 products.
Two checks. How many coefficients does a degree-4 by degree-2 product have? (7.) How many products for 5 terms by 3 terms? (15.)
Where it’s used
Computer algebra. Multiplying symbolic polynomials, with term lists for sparse ones.
Large integers. A number’s digits are the coefficients of a polynomial evaluated at x = 10; multiplying very large integers is multiplying polynomials and carrying, with the fast methods for numbers of thousands of digits.
Filtering. Filtering a signal is the same convolution (convolution), done fast with the FFT for long filters (fast convolution).
Reference card
| Code or idea | Meaning |
|---|---|
c[i + j] += a[i] * b[j] | term i times term j lands on power i + j |
| m · n | products for m terms times n terms |
| na + nb − 1 | coefficients of the dense product |
| c[k] = Σ a[i] · b[k − i] | the convolution of the coefficient arrays |
poly_add(result, times_term(b, …)) | sparse: one partial product per term of a |
| about n^1.585, about n log n | three-product split; FFT |
Back to adding polynomials.
Platform: C11, gcc on x86-64 Linux: int 4 bytes, double 8, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.