This commit is contained in:
Kevin Brewer 2024-07-17 10:37:00 +09:00 committed by GitHub
commit d1388f8dc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 0 deletions

View File

@ -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)

View File

@ -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);
});
});

View File

@ -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;
}