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.