Cairn
algorithms · number theory · O(n1.465) single-digit multiplications

back to Number Theory

Toom-Cook Multiplication

The site's thirteenth Number Theory entry, and the direct generalization of the twelfth: Karatsuba Multiplication splits each number into 2 half-size parts and gets away with 3 sub-multiplications instead of the schoolbook 4. Toom-Cook multiplication — specifically the Toom-3 variant this page builds — splits each number into 3 third-size parts and gets away with 5 sub-multiplications instead of the schoolbook 9, the same split-and-combine idea taken one step further. The two-part split has an algebraic shortcut simple enough to spot directly (Karatsuba's own page derives it in three lines); the three-part split doesn't have an equally short shortcut, so Toom-3 reaches for a different, more general tool instead: treat each number as a small polynomial, and recover the product polynomial by evaluating at a handful of points, multiplying the point-values directly, and interpolating back.

Try it

Enter two non-negative integers (up to 5 digits each) and press Build. Step through the recursion: each node is one call multiplying two numbers, splitting each into three parts, evaluating both at x = 0, 1, -1, 2, ∞ to get five pairs of smaller numbers, recursing on each pair, then interpolating the five results back into the answer. A node with both operands under 10 is a base case — direct single-digit multiplication, counted in the stats line below. The checkbox reproduces a real, checked bug — see Pitfalls.

recursion tree (bold border = current call, shaded = resolved)
    Press Build.

    Why it works

    Split an n-digit number x into three parts at a shared base B (a power of ten): x = x2·B² + x1·B + x0. That's the same shape as reading off the coefficients of a degree-2 polynomial X(t) = x2t² + x1t + x0 at t = B. Two such numbers a and b correspond to two degree-2 polynomials A(t) and B(t), and their product a·b is A(t)·B(t) evaluated at t = B — but A(t)·B(t) is itself a polynomial, of degree 4, with five unknown coefficients c0...c4. A degree-4 polynomial is uniquely determined by its value at any five distinct points, so the plan is: evaluate A and B at five cheap points, multiply the five pairs of point-values directly (five sub-multiplications, each on numbers not much bigger than a third-size part), then solve for c0...c4 from those five products.

    The standard Toom-3 points are 0, 1, -1, 2, and (shorthand for "the leading coefficient," read off directly rather than evaluated), chosen because they keep the evaluated numbers small: A(0) = a0, A(1) = a0+a1+a2, A(-1) = a0-a1+a2, A(2) = a0+2a1+4a2, A(∞) = a2 — and likewise for B. Multiplying each pair gives v0, v1, v-1, v2, v. Two of the five coefficients fall out immediately: c0 = v0 and c4 = v. Subtracting those known contributions from v1, v-1, and v2 leaves three linear equations in the three remaining unknowns c1, c2, c3, which solve cleanly to c2 = (v1'+v-1')/2, then c3 = ((v2'-4c2)/2 - (v1'-v-1')/2) / 3, and c1 from what's left over (the reference implementation below spells out every step). The final answer is c4·B⁴ + c3·B³ + c2·B² + c1·B + c0. Each of the five sub-multiplications is itself recursively split the same way until an operand is small enough to multiply directly, giving the recurrence T(n) = 5T(n/3) + O(n) — five subproblems of a third the size, plus linear-time splitting and interpolating — which solves to O(nlog&sub2;5) = O(nlog5⁄log3) ≈ O(n1.465) by the same Master Theorem argument Karatsuba's own page uses for its O(n1.585).

    Reference implementation

    This is the exact algorithm the demo above steps through (numbers kept as plain JavaScript integers, small enough to stay well under Number.MAX_SAFE_INTEGER; a production implementation would use digit arrays or a bignum type instead):

    function digits(n) { return n === 0 ? 1 : String(n).length; }
    
    function toomCook(a, b) {
      if (a < 10 || b < 10) return a * b;              // base case: direct single-digit multiply
      const chunk = Math.ceil(Math.max(digits(a), digits(b)) / 3);
      const base = 10 ** chunk;
      const a0 = a % base, a1 = Math.floor(a / base) % base, a2 = Math.floor(a / (base * base));
      const b0 = b % base, b1 = Math.floor(b / base) % base, b2 = Math.floor(b / (base * base));
    
      const Av0 = a0,               Bv0 = b0;
      const Av1 = a0 + a1 + a2,     Bv1 = b0 + b1 + b2;
      const Avm1 = a0 - a1 + a2,    Bvm1 = b0 - b1 + b2;   // can go negative -- see Pitfalls
      const Av2 = a0 + 2*a1 + 4*a2, Bv2 = b0 + 2*b1 + 4*b2;
      const Avinf = a2,             Bvinf = b2;
    
      function rmul(x, y) {                                 // signed multiply via a non-negative recursion
        const sign = (x < 0) !== (y < 0) ? -1 : 1;
        return sign * toomCook(Math.abs(x), Math.abs(y));   // both sign extraction and reapplication matter
      }
    
      const v0 = rmul(Av0, Bv0);
      const v1 = rmul(Av1, Bv1);
      const vm1 = rmul(Avm1, Bvm1);
      const v2 = rmul(Av2, Bv2);
      const vinf = rmul(Avinf, Bvinf);
    
      const c0 = v0, c4 = vinf;
      const v1p = v1 - c0 - c4, vm1p = vm1 - c0 - c4, v2p = v2 - c0 - 16 * c4;
      const c2 = (v1p + vm1p) / 2;
      const S = (v1p - vm1p) / 2;
      const T = (v2p - 4 * c2) / 2;
      const c3 = (T - S) / 3;
      const c1 = S - c3;
    
      return c4 * base**4 + c3 * base**3 + c2 * base**2 + c1 * base + c0;
    }

    Verified against native multiplication exhaustively (every integer pair with a under 3,000 and b under 3,000 stepping by 7, 184,041 pairs, 0 mismatches) and against 300,000 randomized pairs with both operands up to 6 digits, 0 mismatches in both sweeps. Correctness at scale was checked separately with a BigInt version (no floating-point precision ceiling): 2,000 randomized pairs with both operands up to 40 digits, 0 mismatches.

    Pitfalls

    Dropping the sign at the x = -1 evaluation point is a checked, frequent failure — and it's a genuinely new failure mode this algorithm introduces, not a repeat of Karatsuba's own bugs. Karatsuba's three evaluation points (low×low, high×high, and a sum of two non-negative halves) are all non-negative by construction for non-negative inputs, so Karatsuba's reference implementation never has to think about sign at all. Toom-3's x = -1 point, a0 - a1 + a2, has no such guarantee — it goes negative whenever the middle part outweighs the two outer parts, which happens for perfectly ordinary inputs. The checkbox above reproduces the mistake of recursing on Math.abs(x) and Math.abs(y) but never reapplying the sign the two originally implied (the rmul helper above without its sign * factor). The smallest clean divergent example: 100 × 120. The correct algorithm gives 12,000; with the sign dropped, 11,760 — because a splits to a0=0, a1=0, a2=1 (so Av-1 = 1) while b splits to b0=0, b1=2, b2=1 (so Bv-1 = -1): one positive, one negative, a sign the buggy version silently discards. Swept across 300,000 randomized pairs (both operands up to 6 digits): 97.8% disagree with the correct product — not every pair, since the bug is invisible whenever both evaluation results at x = -1 happen to carry the same sign (both positive, or both negative, cancel out in the product either way), but the overwhelming majority of pairs trigger it somewhere across the recursion.

    Complexity

    O(nlog5⁄log3) ≈ O(n1.465) single-digit multiplications for two n-digit numbers, against schoolbook's O(n²) and Karatsuba's O(n1.585) — asymptotically the best of the three. But the reference implementation above recurses all the way down to single digits, and five sub-multiplications plus interpolation overhead per level is not free: measured over 2,000 randomized trials at each size, 2-digit pairs need 7.09 single-digit multiplications on average (schoolbook needs only 4), 4-digit pairs need 31.46 (schoolbook 16), 7-digit pairs need 79.52 (schoolbook 49) — Toom-3 is worse than schoolbook at every one of these sizes. A wider sweep (3,000 trials per size, using exact BigInt arithmetic to check sizes too large for ordinary floating-point numbers) finds where that flips: at 12 digits schoolbook still wins narrowly (144 against a measured 157.8), but by 13 digits Toom-3 has pulled ahead (169 against a measured 146.1), and the gap only widens — at 27 digits, 729 against 576.0; at 81 digits, 6,561 against 2,961.8. Each tripling of digit count multiplies schoolbook's exact cost by precisely 9 (its cost is exactly , no randomness involved) while Toom-3's measured cost grows only ×5.1 to ×5.6 per tripling — close to the ×5 the T(n) = 5T(n/3) recurrence predicts, and visibly slower-growing than schoolbook's ×9, exactly the asymptotic win the exponent promises, just one that only shows up once the recursion has enough levels to amortize its own overhead.

    Space: O(n) for the digit representation of intermediate values, with O(log n) recursion depth. Real bignum libraries don't recurse to single digits the way the reference implementation above does for clarity — they measure their own crossover points empirically and switch strategies as numbers grow: schoolbook multiplication below a few dozen digits, Karatsuba above that, Toom-3 (and sometimes higher-order Toom-k variants, splitting into more than three parts at the cost of proportionally more evaluation points and messier interpolation formulas) above that, and Fast Fourier Transform-based convolution once the numbers are large enough that O(n log n) is worth its own larger constant factor — the same four-tier ladder GMP and Python's arbitrary-precision int both climb in practice, each rung covered by one of this site's last three Number Theory entries.

    This site's guide, Choosing a Number Theory Algorithm, sets this page and its two siblings aside from the rest of the category for that reason — every other Number Theory entry treats a multiplication as a single cheap step, an assumption that stops holding at this scale. Toom-Cook is the middle rung: reach for it once Karatsuba's split-in-two has itself been outgrown, and before the numbers are large enough that FFT-based convolution's larger constant factor pays for itself.