Binary Exponentiation

Typical Exponentiation

Let's say you wanted to calculate the large power of a number modulo another such as

739(mod1000)7^{39} \pmod{1000}

The most naive way to do this would be to multiply 77 by itself 3939 times and then take the remainder when divided by 10001000, aka the last 33 digits. However, this technique quickly becomes unmanageable, especially since 7397^{39} has 3232 digits, which would lead to a lot of work and the opportunity to make a lot of mistakes. Instead, we could work smarter, not harder.

The next way we could augment this process is by realizing that after each successive multiplication, we could take the intermediate product modulo 10001000. For instance, we would first have 7,72=49,73=3437, 7^2 = 49, 7^3 = 343. After this, we would have 74=24017^4 = 2401, but we can ignore everything but the last 33 digits and simply take the 401401 as our product moving forward. By continuing this strategy, we will constantly stay with small numbers and not have to deal with 3232 digit monstrosities. However, this still involves us doing 3838 multiplications to get from 77 to 7397^{39}. As the exponent grows even larger, it would become infeasible for humans to do it by hand.

While computers can solve these problems easily, even computers have their limitations. If the computer has to do thousands of these calculations and take powers that go up to the hundreds of millions, this strategy quickly grows infeasible as well.

However, there is a solution that can drastically speed up this calculation for large numbers known as binary exponentiation. Binary exponentiation takes advantage of the fact that we can square numbers in a single operation by simply multiplying it with itself, which doubles the exponent.

Applying Squaring

Let's say that the power we wanted to calculate was 787^8. While we could multiply 77 with itself 88 times, let's use squaring. We can first calculate 72=497^2 = 49. Then, rather than multiplying 4949 by 77 twice, we can instead square 72=497^2 = 49 in a single operation to get

74=(72)2=492=24017^4 = \left(7^2\right)^2 = 49^2 = 2401

Finally, we can square this number to get

78=(74)2=24012=57648017^8 = \left(7^4\right)^2 = 2401^2 = 5764801

By using squaring, we were able to reduce our multiplication from 88 steps to only 33. While one may say that this may have made the operation harder since we had to square very large numbers, if we had even larger exponents and were also taking the values modulo something, the benefits of this method quickly become apparent as the number of multiplications required would actually be logarithmic when compared to the exponent. Going all the way up to something like the 10241024th exponent would only require 1010 operations, compared to the 10231023 from before.

However, this very simple trick only works at the moment with exponents that are powers of 22. However, this is where the binary part of binary exponentiation comes in.

Binary

If you aren't familiar with the concept of other number bases, please look at this explanation from Better Explained that covers it well.

Now that you are familiar with number bases, binary is another word for base 22, where each digit in a number can be either 00 or 11 and represents a power of 22. You may have heard about binary in other contexts such as describing things that only have 22 options or with computers since binary is used to represent all numbers for a computer.

Let's look at a few examples to make sure we are comfortable with binary, but feel free to skip to the next section if you are already comfortable.

Example

Express the binary number 111011111011 in base 1010.

We can do this by looking at each digit from right to left and using those to see which powers of 22 are present in our number. Since the first two digits on the right are both 11s, we know that 202^0 and 212^1 are present in our number. Since the third digit is a 00, 222^2 is not. However, the next three are, so the total sum would be

20+21+23+24+25=522^0 + 2^1 + 2^3 + 2^4 + 2^5 = \boxed{52}

Example

Express the number 3737 in binary.

We can do this by first looking for the largest power of 22 present in 3737. The powers of 22 are 1,2,4,8,16,32,64,...1, 2, 4, 8, 16, 32, 64, ... Since 32<3732 < 37 but 64>3764 > 37, we know that 3232 is the largest power of 22 present. We can keep note that 32=2532 = 2^5 is present in the number and move on by subtracting 3232 from 3737. We are left with 55 and we have to now find the largest power of 22 present in that. The answer is 4=224 = 2^2, and after subtracting that we are left with 1=201 = 2^0. We know that the powers of 22 present are 0,2,50, 2, 5, which corresponds to the number 100101100101, since all of the correct powers of 22 are represented there.

Full Binary Exponentiation

Now that we have a grasp on binary, we can move on to what exactly binary exponentiation is. The idea behind the technique is that we can break up the exponent into its binary representation and then use those corresponding powers of 22s in our calculation. For instance, let's say we wanted to calculate the following:

3373^{37}

We can first express our exponent, 3737, in binary, or as sums of powers of 22. We can quickly get that it is

37=32+4+1=25+22+2037 = 32 + 4 + 1 = 2^5 + 2^2 + 2^0

We can then plug this representation back into our original equation to obtain:

325+22+203^{2^5 + 2^2 + 2^0}

Rewriting this with laws of exponents, we obtain the following result:

337=3203223253^{37} = 3^{2^0} * 3^{2^2} * 3^{2^5}

However, remember what we said before about exponents that were powers of 22. We can apply squaring to easily calculate all of those powers required in just 55 operations. Then, we can simply multiply together the results to obtain the final answer in much less than 3737 operations.

Calculating the Powers

320=3321=32=9322=92=81323=812=6561324=65612=43046721325=430467212=1853020188851841\begin{aligned} 3^{2^0} &= 3 \\ 3^{2^1} &= 3^2 = 9 \\ 3^{2^2} &= 9^2 = 81 \\ 3^{2^3} &= 81^2 = 6561 \\ 3^{2^4} &= 6561^2 = 43046721 \\ 3^{2^5} &= 43046721^2 = 1853020188851841 \end{aligned}

Final Result

337=320322325=3811853020188851841=4502839058909973633^{37} = 3^{2^0} * 3^{2^2} * 3^{2^5} = 3 * 81 * 1853020188851841 = \boxed{450283905890997363}

Since those numbers were huge, a more reasonable calculation might be to instead calculate

337(mod1000)3^{37} \pmod{1000}

This could be done the mostly the same way, but you have to instead take each intermediate result mod 10001000.

Calculating the Powers

320=3321=32=9322=92=81323=812=6561561324=5612=314721721325=7212=519841841\begin{aligned} 3^{2^0} &= 3 \\ 3^{2^1} &= 3^2 = 9 \\ 3^{2^2} &= 9^2 = 81 \\ 3^{2^3} &= 81^2 = 6561 \rightarrow 561 \\ 3^{2^4} &= 561^2 = 314721 \rightarrow 721 \\ 3^{2^5} &= 721^2 = 519841 \rightarrow 841 \end{aligned}

Final Result

337=320322325=381841=2043633633^{37} = 3^{2^0} * 3^{2^2} * 3^{2^5} = 3 * 81 * 841 = 204363 \rightarrow \boxed{363}

And that's pretty much the entire method! Next up, we'll go over the advantages of this algorithm and then some of the applications for such huge exponents.

Big O Notation

For those not comfortable with big O notation from computer science, feel free to skip this section. But for those interested, it is essentially a measure of how the number of operations required in a calculation scale as the input size NN changes. Check out this article for more details if you're interested. It's a topic that is very important for analyzing the runtime of computer algorithms, but its core ideas can be applied in other subject areas.

For those who know what it is, you may have realized that the naive algorithm for calculating exponents runs in O(N)O(N) linear time. However, the binary exponentiation method runs in O(log2(N))O(\log_2(N)) time, which is a massive improvement as NN increases. This is because the number of digits in the binary representation of a number is approximately equal to log2(N)\log_2(N).

Advantage Over Naive Method

One could appreciate the significant advantages of using this algorithm when calculating something like 710000007^{1000000}.

The naive method would require around 10000001000000 operations since you would have to multiply by 77 that many times. However, the binary representation of 10000001000000 is 1111010000100100000011110100001001000000, which is only 2020 digits long. This means that you would have to calculate only 2020 powers of 77 (with two of them being 707^0 and 717^1). Then, you would only need to multiply 77 of those numbers, which would correspond to the 11s in the binary representation. This totals only 2727 operations compared to the almost 10000001000000 from before.

Applications

Modular Inverses

The first application is for calculating modular inverses. In the short future, I'll add a post that goes over this topic in depth, but essentially there's a formula that allows you to calculate the modular inverse of a number aa mod nn.

aϕ(n)1(modn)a^{\phi(n) - 1} \pmod{n}

For extremely large nn, especially when nn is prime, binary exponentiation is almost a necessity to do the operations with enough speed.

Fibonacci Relation

However, this idea of binary exponentiation does not just apply to numbers; it can also be applied to ideas like matrices. For instance, it can even be used to calculate the Fibonacci numbers! If you're not familiar with matrices, and how they can be multiplied or used to represent linear systems, I would highly recommend checking it out since they can be really cool.

For those unfamiliar with the Fibonacci numbers, they are a sequence of integers such that FnF_n represents the nnth Fibonacci numbers. We define F0=0F_0 = 0 and F1=1F_1 = 1, and then we use the recurrence relation

Fn=Fn1+Fn2F_n = F_{n - 1} + F_{n - 2}

Essentialy, each next term is the sum of the previous 22 terms. The sequence goes like this:

0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,...0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, ...

We can actually express this with the following equation:

[0111][FnFn+1]=[Fn+1Fn+2]\begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix} * \begin{bmatrix} F_n \\ F_{n+1} \end{bmatrix} = \begin{bmatrix} F_{n+1} \\ F_{n+2} \end{bmatrix}

To dissect how this matrix equation works, we can break it down into its component linear equations:

Fn+1=0+Fn+1F_{n + 1} = 0 + F_{n + 1}
Fn+2=Fn+Fn+1F_{n + 2} = F_{n} + F_{n + 1}

Both of these equations perfectly describe how the Fibonacci sequence is generated, showing how the matrix equation encodes the linear recurrence present in the system. The reason why this is so powerful is that we can continuously multiply these matrices to obtain higher values in the Fibonacci sequence.

In general, we can do the following:

[0111]n[F0F1]=[FnFn+1]\begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}^n * \begin{bmatrix} F_0 \\ F_1 \end{bmatrix} = \begin{bmatrix} F_{n} \\ F_{n+1} \end{bmatrix}

This gives us a method using matrix exponentiation to calculate the n+1n + 1th Fibonacci number. However, with the power of binary exponentiation, we can calculate this exponent quickly, making calculating something like the 10001000th Fibonacci number relatively trivial. The same process of binary exponentiation can be used making this a relatively flexible technique.

Conclusion

Overall, binary exponentiation is a pretty powerful technique to both do hand computations quickly and to speed up computer algorithms. It can be applied to pretty much any mathematical structure that has exponentiation as well, including real numbers, complex numbers, and matrices, two of which we saw above.

Side Note: Implementation in Programming

As a quick side note, we could do the following to implement this algorithm in code (the code is in Java, but it is easily expandable to other languages). I'm not going to go over exactly how the code works as of now, but I might add it in the future.

long binaryExpo(int base, int exponent) { long result = 1; long currPower = base; while(exponent > 0) { if (exponent % 2 > 0) { result = result * currPower; } currPower = currPower * base; exponent /= 2; } return result; }

We could optimize this program with bitwise operations as following (although a good compiler would probably automatically substitute these in). This method might appear more intuitive for those familar with the binary operations utilized, since it more clearly shows how you're analyzing each binary digit of the exponent.

long binaryExpo(int base, int exponent) { long result = 1; long currPower = base; while(exponent > 0) { if (exponent & 1 > 0) { result = result * currPower; } currPower = currPower * base; exponent >>= 1; } return result; }