Karatsuba Algorithm

Karatsuba Algorithm

·

4 min read

The Karatsuba algorithm is a fast multiplication algorithm. It use the divide and conquer techinque to speed up the multiplication process. In this article, let's dig into its details to see how it works.

Say we want to multiply 2 number: x and y.

The first step is to split each number into 2 half. For example, 12345 can be split into 123 and 45 and expressed as 123 * 100 + 45. So, in this way, we can express the x and y in below. n is the max length between x and y.

x = a * 10^(n/2) + b
y = c * 10^(n/2) + d

Now let's try to multiply these 2 parts.

x * y
  = (a * 10^(n/2) + b) * (c * 10 ^(n/2) + d)
  = (a * 10^(n/2)) * (c * 10 ^(n/2)) + ad*(10^(n/2)) + bc*(10^(n/2)) + bd
  = ac * 10^(2*(n/2)) + (ad + bc)*10^(n/2) + bd

So to get the result of x * y, we need to get ac, bd and ad+bc first.

And ad+bc can be expressed as:

ad + bc = (a + b)*(c + d) - ac - bd

So, in the end, what we only need to know is ac and bd.

Now, how can we get ac and bd? Well, since it is still multiplication, we can calculate these using the same process recursively, until we find x or y is 1 digit number, then we can stop the recursion and do the multiplication in the normal way.

As you can see, this whole process is what the divide and conquer means. Big x and y are divided into small a,b,c,d, which can be divided recursively further.

With all this in mind, let see them in code.

function multiply(x: string, y: string): string {
    if (x.length === 0) return y;
    if (y.length === 0) return x;

    // 1 digit number, use normal multiply
    if (x.length === 1) return singleMultiply(y, x);
    if (y.length === 1) return singleMultiply(x, y);

    // divide each into 2 parts
    const half = Math.floor(Math.max(x.toString().length, y.toString().length) / 2);

    const xMid = x.length - half;
    const a = xMid <= 0 ? "0" : x.slice(0, xMid);
    const b = x.slice(xMid < 0 ? 0 : xMid);

    const yMid = y.length - half;
    const c = yMid <= 0 ? "0" : y.slice(0, yMid);
    const d = y.slice(yMid < 0 ? 0 : yMid);

    // calculate key elements
    const ac = multiply(a, c);
    const bd = multiply(b, d);
    const ad_plus_bc = subtract(subtract(multiply(add(a, b), add(c, d)), ac), bd);

    // calculate final result
    return add(add(pow10(ac, 2 * half), pow10(ad_plus_bc, half)), bd);
};

Since I want to avoid converting input number string into number directly, so I prepare the string version of add, subtract and pow10 functions, which use the standard operations we learn from elementary school to do each operations.

// x * 10^y
function pow10(x: string, y: number): string {
    while (y-- > 0) {
        x += "0";
    }
    return x;
}

// y: 1 digit
function singleMultiply(x: string, y: string): string {
    const res: number[] = [];
    let carry = 0;
    const base = "0".charCodeAt(0);
    const yv = y.charCodeAt(0) - base;
    for (let i = x.length - 1; i >= 0; i--) {
        const v = (x.charCodeAt(i) - base) * yv + carry;
        res.push(v % 10);
        carry = Math.trunc(v / 10);
    }
    if (carry > 0) {
        res.push(carry);
    }
    res.reverse();
    return trimStart0(res.join(""));
}

function add(x: string, y: string): string {
    const res: number[] = [];
    let carry = 0;
    const base = "0".charCodeAt(0);
    for (let i = x.length - 1, j = y.length - 1; i >= 0 || j >= 0; i--, j--) {
        const v1 = i >= 0 ? x.charCodeAt(i) - base : 0;
        const v2 = j >= 0 ? y.charCodeAt(j) - base : 0;
        const v = v1 + v2 + carry;
        res.push(v % 10);
        carry = Math.trunc(v / 10);
    }
    if (carry > 0) {
        res.push(carry);
    }
    res.reverse();
    return res.join("");
}

// value of x >= value of y
function subtract(x: string, y: string): string {
    const res: number[] = [];
    let carry = 0;
    const base = "0".charCodeAt(0);
    for (let i = x.length - 1, j = y.length - 1; i >= 0 || j >= 0; i--, j--) {
        const v1 = i >= 0 ? x.charCodeAt(i) - base : 0;
        const v2 = j >= 0 ? y.charCodeAt(j) - base : 0;
        const v = v1 - v2 - carry;
        res.push(v < 0 ? v + 10 : v);
        carry = v < 0 ? 1 : 0;
    }
    if (carry > 0) {
        res.push(carry);
    }
    res.reverse();
    return trimStart0(res.join(""));
}

function trimStart0(x: string): string {
    return x.replace(/^0+(?=\d)/, "");
}

In the end, don't forget to use the leetcode problem Multiply Strings to test the code.