Two polynomials as chains of terms, largest exponent first. Watch them merge into one chain, with two pairs of terms cancelling out.
Adding polynomials term by term
Two sorted chains of terms merged, with cancelling pairs removed
struct pnode *poly_add(struct pnode *a, struct pnode *b){ struct pnode dummy; struct pnode *tail = &dummy; while (a != NULL && b != NULL) { if (a->exp > b->exp) { tail->next = a; tail = a; a = a->next; } else if (b->exp > a->exp) { tail->next = b; tail = b; b = b->next; } else { struct pnode *na = a->next; struct pnode *nb = b->next; a->coeff += b->coeff; free(b); if (a->coeff == 0.0) { free(a); } else { tail->next = a; tail = a; } a = na; b = nb; } } tail->next = (a != NULL) ? a : b; return dummy.next;}int main(void){ const struct term ta[4] = { { 5, 4 }, { 3, 2 }, { -2, 1 }, { 1, 0 } }; const struct term tb[4] = { { 2, 3 }, { -3, 2 }, { 4, 1 }, { -1, 0 } }; struct pnode *a = from_terms(ta, 4); struct pnode *b = from_terms(tb, 4); print_poly("A", a); print_poly("B", b); struct pnode *s = poly_add(a, b); print_poly("A + B", s); free_poly(s); return 0;}A = 5x⁴ + 3x² − 2x + 1 as terms, largest exponent first.
- rounds
- 0
- nodes freed
- 0
Output
Describe this picture
The picture is three rows, each a head with a chain of nodes to its right, joined by arrows: A, what is left of the first list; B, what is left of the second; and A + B, the sum built so far. Each node shows one term, as 5x^4. Over the rows is a lane with the current round. Under the picture are the lines of polyadd.c the clip runs, with the line about to run marked, one sentence on the current step, two readouts (the rounds of the merge and the nodes freed), and the output. The clip plays once, about 16 seconds. First the two lists are built, A = 5x⁴ + 3x² − 2x + 1 and B = 2x³ − 3x² + 4x − 1, and printed. Then each round lights the two front terms. Round 1 compares exponents 4 and 3, and 5x⁴ slides from A’s row into the sum; round 2 moves 2x³ from B. In round 3 both fronts are x² terms: A’s becomes 0x², toned as a warning, B’s is freed, and then A’s is freed too, so the pair is gone. Round 4 adds −2x and 4x: A’s node becomes 2x¹, B’s is freed, and 2x¹ joins the sum. Round 5 cancels 1 and −1 the same way as round 3. Both rows are now empty, the readouts show 5 rounds and 5 nodes freed, and the clip rests on the output A + B: +5x^4 +2x^3 +2x^1, with three nodes left of eight. 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 add.step.
A merge by exponent
A = 5x⁴ + 3x² − 2x + 1 and B = 2x³ − 3x² + 4x − 1 are stored as lists of term nodes, as in storing a polynomial, largest exponent first:
A: +5x^4 +3x^2 -2x^1 +1x^0
B: +2x^3 -3x^2 +4x^1 -1x^0
Adding them is the merge of merging lists, ordered by falling exponent, with a third case: when the two exponents are equal, the terms are like terms and their coefficients add.
The code and its cost
The merge
poly_add builds the sum on a node dummy on the stack, with tail on the last node so far. While both lists have terms, the term with the larger exponent goes next:
if (a->exp > b->exp) {
tail->next = a;
tail = a;
a = a->next;
} else if (b->exp > a->exp) {
tail->next = b;
tail = b;
b = b->next;
}
No new node is made: the sum reuses the nodes of A and B, so after the call a and b must not be used again.
Equal exponents
Otherwise both nodes hold the same power of x:
struct pnode *na = a->next;
struct pnode *nb = b->next;
a->coeff += b->coeff;
free(b);
if (a->coeff == 0.0) {
free(a);
} else {
tail->next = a;
tail = a;
}
a = na;
b = nb;
A’s node takes the sum and B’s node is freed. If the sum is 0, A’s node is freed too, and nothing is linked. Keeping a term with coefficient 0 would not change the polynomial’s value, but the list would stop being the polynomial’s terms, and every later sum or product would walk past it. na and nb keep the next nodes, since a freed node’s next must not be read.
The coefficients here are small whole numbers, so a->coeff == 0.0 is exact. With measured coefficients, a program treats a sum within a small tolerance of 0 as 0.
The rounds
| Round | Exponents | What happens |
|---|---|---|
| 1 | 4 and 3 | 5x⁴ is linked |
| 2 | 2 and 3 | 2x³ is linked |
| 3 | 2 and 2 | 3 − 3 = 0: both nodes freed |
| 4 | 1 and 1 | −2 + 4 = 2: A’s node kept, B’s freed |
| 5 | 0 and 0 | 1 − 1 = 0: both nodes freed |
Both lists are then empty, so tail->next = (a != NULL) ? a : b; attaches NULL, and the sum is
A + B: +5x^4 +2x^3 +2x^1
Three nodes of eight are left; five were freed.
Why the lists must be sorted
Like terms meet only because both lists are in the same order. If B’s x² term came after its x term, the merge would pass A’s 3x² before seeing B’s −3x², and the sum would hold two x² terms.
The cost
Each round moves past at least one term, and the loop stops while one list still has a term, unless the last round used up both at once. So lists of m and n terms take at most m + n − 1 rounds, and no new nodes. A version that must keep A and B intact would allocate a new node for each term of the sum instead, and leave the inputs alone.
Running it yourself
gcc -std=c11 -Wall -Wextra -pedantic polyadd.c -o polyadd
./polyadd
Worked example
One node left. (x² + 1) + (−x² + 1): the x² terms cancel, and the constants make 2. The sum is the single node 2x⁰.
A polynomial plus its negative. Every pair cancels, every node is freed, and poly_add returns NULL: the zero polynomial, an empty list.
No common exponents. Adding 3 terms to 5 terms with no exponent in common takes at most 3 + 5 − 1 = 7 rounds, when the exponents interleave. If all of A’s exponents are larger than B’s, it takes 3: once A is used up, the rest of B is attached in one step.
Two checks. Which node of A survives as 2x? (A’s −2x node, its coefficient changed to 2.) What does poly_add return for two empty lists? (NULL.)
Where it’s used
Computer algebra. Adding polynomials with many terms is the basic step of symbolic computation.
Error-correcting codes. Decoders add and combine polynomials whose coefficients are bits or bytes.
Filters. Two filters in parallel have the sum of their transfer functions as theirs, and that sum is worked out with polynomial products and additions.
Reference card
| Code or idea | Meaning |
|---|---|
| larger exponent first | the merge order of two sorted term lists |
a->coeff += b->coeff; free(b); | like terms: A’s node keeps the sum |
if (a->coeff == 0.0) free(a); | a cancelling pair leaves nothing |
na, nb | the next nodes, saved before freeing |
| m + n − 1 rounds | at most, with no new nodes |
Next, multiplying polynomials takes every term times every term.
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.