2022-04-27
Impress your friends, rapidly compute exponents
Written by: Ned
Recall that is just multiplied by itself times . This means
that to find we’d need to do multiplications. Hurray! We have linear time. But can we do
better? We can.
What if we wanted to find ? It’s fairly easy— we just evaluate . But remember
that by exponent laws and also that . So . So if we want , we can
calculate and then multply to get in only two multiplications instead of
three. Similarly, if wanted we could calculate and then multiply it by —that
isn’t any faster than doing it the long way, but it isn’t any slower either.
Binary powering is the idea of splitting the exponent into powers to two and then multiplying the
results together. For example So we can so it in many fewer multiplications
than the naive linear approach. We already have a, so we need to calculate then noting that
, , , and we have a total of 5 multiplications
so far. Then we need to multiply the terms that we need together to get
which is another 4 multiplications. So instead of 60 multiplications 60 multiplications we have 5+4.
We can follow a similar process by breaking down any exponent in terms of its powers of two and then
calculating where . Since numbers already stored as binary, we can just check where the
one bits of are for a very convenient algorithm.
This runs in since the sum of will contain at most of each power of 2 we have at most
terms. Calculating up to n then requires at most multiplications of a, and once we have those terms,
we’ll have at most more multiplications to actually calculate . .
Calculate .
Calculate and store in an array, while for .
Break into a sum of powers of . This is just the binary representation of n.
For every power of in the sum of , multiply the associated array positions that correspond to the powers of that sum to .
Return the result.
function exponent(a, n) {
arr := [a ** 0, a]
for (i := 1; i < n; i := i * 2) {
tmp := arr[arr.length-1] * arr[arr.length-1]
arr.push(tmp)
}
result := 1
count = 0
while (n > 0) {
if (n % 2 != 0) {
result := result * arr[count]
}
++count
n := n / 2
}
return result
}
function exponent(a, n) {
const arr = [a**0, a]
for (let i = 1; i <= n; i = i * 2) {
let tmp = arr[arr.length-1] * arr[arr.length-1]
arr.push(tmp)
}
let result = arr[0]
let count = 1
while (n > 0) {
if (n % 2 != 0) {
result = result * arr[count]
}
++count
n = Math.floor(n / 2)
}
return result
}
I hope that was useful. Binary powering is a pretty neat trick and gives a nice introduction to
creating algorithms that run in logarithmic time. It’s worth noting that in special cases (
or for instance) checking to see what is could optimize the algorithm to avoid even the
logarithmic time complexity, but even in the worst case it’s still which is pretty good.
Generally, it would be wise to implement this with arbitrary precision arithmetic since it shines
when is large, but that’s a problem for another time.