diff --git a/src/algorithms/math/karatsuba-multiplication/README.md b/src/algorithms/math/karatsuba-multiplication/README.md new file mode 100644 index 0000000000..94ea93085b --- /dev/null +++ b/src/algorithms/math/karatsuba-multiplication/README.md @@ -0,0 +1,41 @@ +# Karatsuba Multiplication + +Karatsuba is a fast multiplication algorithm discovered by Anatoly Karatsuba in 1960. Given two n-digit numbers, the "grade-school" method of long multiplication has a time complexity of O(n<sup>2</sup>), whereas the karatsuba algorithm has a time complexity of O(n<sup>1.59</sup>). + +## Recursive Formula + +``` +x = 1234 +y = 5678 + +karatsuba(x, y) +``` + +1. Split each number into numbers with half as many digits +``` +a = 12 +b = 34 + +c = 56 +d = 78 +``` + +2. Compute 3 subexpressions from the smaller numbers + - `ac = a * c` + - `bd = b * d` + - `abcd = (a + b) * (c + d)` + +3. Combine subexpressions to calculate the product +``` +A = ac * 10000 +B = (abcd - ac - bd) * 100 +C = bd + +x * y = A + B + C +``` + +_**Note:**_ *The karatsuba algorithm can be applied recursively to calculate each product in the subexpressions.* (`a * c = karatsuba(a, c)`*). When the numbers get smaller than some arbitrary threshold, they are multiplied in the traditional way.* + +## References +[Stanford Algorithms (YouTube)](https://www.youtube.com/watch?v=JCbZayFr9RE) +[Wikipedia](https://en.wikipedia.org/wiki/Karatsuba_algorithm) diff --git a/src/algorithms/math/karatsuba-multiplication/__test__/karatsuba.test.js b/src/algorithms/math/karatsuba-multiplication/__test__/karatsuba.test.js new file mode 100644 index 0000000000..7a4979d698 --- /dev/null +++ b/src/algorithms/math/karatsuba-multiplication/__test__/karatsuba.test.js @@ -0,0 +1,14 @@ +import karatsuba from '../karatsuba'; + +describe('karatsuba multiplication', () => { + it('should multiply simple numbers correctly', () => { + expect(karatsuba(0, 37)).toEqual(0); + expect(karatsuba(1, 8)).toEqual(8); + expect(karatsuba(5, 6)).toEqual(30); + }); + + it('should multiply larger numbers correctly', () => { + expect(karatsuba(1234, 5678)).toEqual(7006652); + expect(karatsuba(9182734, 726354172)).toEqual(6669917151266248); + }); +}); diff --git a/src/algorithms/math/karatsuba-multiplication/karatsuba.js b/src/algorithms/math/karatsuba-multiplication/karatsuba.js new file mode 100644 index 0000000000..f0da5194c7 --- /dev/null +++ b/src/algorithms/math/karatsuba-multiplication/karatsuba.js @@ -0,0 +1,68 @@ +/** + * + * @param {number} x + * @param {number} y + * @return {number} + */ +export default function karatsuba(x, y) { + // BASE CASE: + // if numbers are sufficiently small, + // multiply them together in the traditional way + if (x < 10 || y < 10) { + return x * y; + } + + // SCALE FACTOR: + // scaleFactor is used to split the numbers + // into smaller numbers for recursion. + // when combining the subexpressions back + // together, the scaleFactor is used to + // recreate the magnitude of the original numbers + const minDigits = Math.min( + String(x).length, + String(y).length, + ); + const scaleFactor = 10 ** Math.floor(minDigits / 2); + + // PARAMETER COMPONENTS: + // a b are the two components of x + // c d are the two components of y + // + // e.g. + // x = 1234 -> a = 12, b = 34 + // y = 5678 -> c = 56, d = 78 + + // example of component computations: + // x = 1234, y = 5678 + // scaleFactor = 100 + + // a = floor(1234 / 100) = floor(12.34) = 12 + const a = Math.floor(x / scaleFactor); + + // b = 1234 - (12 * 100) = 1234 - 1200 = 34 + const b = x - (a * scaleFactor); + + // c = floor(5678 / 100) = floor(56.78) = 56 + const c = Math.floor(y / scaleFactor); + + // d = 5678 - (56 * 100) = 5678 - 5600 = 78 + const d = y - (c * scaleFactor); + + // COMPUTE SUBEXPRESSIONS: + // since a + b is less than x, and c + d is less than y + // the recursion is guaranteed to reach the base case + const ac = karatsuba(a, c); + const bd = karatsuba(b, d); + const abcd = karatsuba(a + b, c + d); + + // COMBINE SUBEXPRESSIONS: + // since the scaleFactor was used to + // reduce the size of the components, + // the scaleFactor must be applied in reverse + // to reconstruct the magnitude of the original components + const A = ac * (scaleFactor ** 2); + const B = (abcd - ac - bd) * scaleFactor; + const C = bd; + + return A + B + C; +}