CS 3343/3341
Analysis of Algorithms |
Exponentiation to an Integer Power |
Let's look at calculating 323 = 94 143 178 827. If we want to jack ourselves up to higher powers of 3, we could try repeated squaring, at least until we get bigger than 323:
Successive Squares of Powers of 3 |
---|
30 = 1 |
31 = 3 |
(31)2 = 32 = 9 |
(32)2 = 34 = 81 |
(34)2 = 38 = 6561 |
(38)2 = 316 = 43 046 721 |
(316)2 = 332 = don't need this one |
If you look above, the table shows 3 raised to powers which themselves are successive powers of 2. We were able to get to 323 by representing 23 as a sum of powers of two:
23 = 16 + 4 + 2 + 1. But this is just the binary representation of 23 = 1 * 24 + 0 * 23 + 1 * 22 + 1 * 21 + 1 * 20, so that 23 = 101112. The 1s in the binary representation of the exponent tell you which table entries to multiply together.Suppose we have a base a and an exponent b. We want ab. At each stage we need to raise a to successive powers: 2, 4, 8, 16, 32, 64, .... We will use the parameter a below to hold these powers. At each stage we also need to generate successive bits of the exponent, from the least significant to the most significant. For each 1 bit, we will multiply in the power created at that stage, using, say, a variable d to hold these successive results (d starts out at 1). d is the value returned.
A clever way to produce successive binary bits from right to left is to take b%2 for the low order bit, and then replace b with b / 2 to drop the low order bit; then repeat. Here's the C code I ended up with. For comparison, I stuck in the slow exponentiation, with a function exps.Exponential using successive squares | Runs using Ruby (arbitrary precision ints) |
---|---|
// exponen.c: fast exponentiation #include <stdio.h> int exponentiation(int, int); int main() { int d; int a, b; scanf("%i %i", &a, &b); d = expon(a, b); printf("a: %i, b: %i, d: %i\n", a, b, d); } // expon: fast exponentiation int expon(int a, int b) { int d = 1; while(b > 0) { if (b%2 == 1) d = d*a; b = b/2; if (b > 0) a = a*a; } return d; } // exps: slow exponentiation int exps(int a, int b) { int d = 1; while(b > 0) { d = d*a; b = b--; } return d; } | % ruby power.rb 2 127 170141183460469231731687303715884105728 (2^127, 8 loop iters, log2(127) = 6.98868) % ruby power.rb 3 236 398672347901056050310521584754736038852 144821237913809261660811330548546381908 73316632624557307582586552837323121 (3^236, 9 loop iters, log2(236) = 7.8826) % ruby power.rb 2 431 55453393882416297191568283682861674068728741 50751633150340959161229242615611251246079948 812208279156194782421922807143657948315648 (2^431, 10 loop iters, log2(431) = 8.7515) % ruby power.rb 2 814 10924874846830353242652429173917708774841972 03528238392999322879879419861044040838598071 45179079545499532163655162810211465442126301 09167747667322556138831947522306178275761452 58590746363791609811593628243703660868680212 15846014341794428966928384 (2^814, 11 loop iters, log2(814) = 9.668885) % ruby power.rb 2 2011 23513716639216732656288423960118927032777066 53877647770578212325019169303331574402778438 62797620484079297733558064632376001969963184 75373187603074174293057236813821776959954944 14106196955956118966864424756500198293127484 69547803369364105269330875092932413432951116 59276071860143231654225825006432659524133139 28579256001710194736731848866079926291144222 72878597563815169700909072654819904085527076 62503363911474257186432414060515001147834337 93192991557415606314842532965535407916483361 99847848379659433483019111595470879802058314 58699684763425848480539161635929196298408518 1994635193249304954575153212162048 (2^2011, 12 loop iters, log2(2011) = 10.9737) |
This algorithm has a loop iteration for each bit in the binary form of the exponent. If n is the exponent, then log2 n gives the number of bits in its binary representation (see logarithms). We do one or two multiplications during each loop iteration except the last, which has zero or one. So the number of multiplications is between log2 n and 2 * log2 n. Thus this is an O(log n) algorithm.
As n gets large, the difference between it and log2 n becomes phenomenal. For example, if the exponent is 1 000 000, the simple algorithm would require that many mulitiplications, while the better algorithm here would need only log2 106 = 6 * log2 10 = 6 * 3.321928095 = 19.93156857 loop interations (that is, 20), and up to twice that many multiplications. Here the comparison is between forty and a million multiplications. But who would use such large exponents? The RSA cryptosystem routinely uses exponents that are 200 decimal digits (about 666 bits) long. So exponentiation needs only 666 loop iterations, rather than 10200 iterations, a number that will forever be impossible for one of our computers to carry out.From the way this program has been developed, it should be plausible that it is correct. However, a formal correctness proof is important, when we can give it. Such a proof for a simple program sometimes has a loop invariant that should remain true at the beginning and end of each loop. For this proof, it also helps to introduce separate variables for the formal parameters. The execution is the same as before.
Exponential using squaring with new formal parameters |
---|
// expon: fast exponentiation int expon(int A, int B) { int a = A, b = B, d = 1; while(b > 0) { if (b%2 == 1) d = d*a; b = b/2; if (b > 0) a = a*a; } return d; } |
Exponential algorithm in Java (Exp.java) | ... in Ruby (exp.rb) |
---|---|
// Exp: Java version of exponentiation // Use long type to get 64-bit integers public class Exp { public static long exp2(long X, long Y) { long x = X, y = Y, z = 1; while (y > 0) { if (y%2 == 1) z = z*x; y = y/2; if (y > 0) x = x*x; } return z; } public static void main(String[] args) { long x = Long.parseLong(args[0]); long y = Long.parseLong(args[1]); long z = Exp.exp2(x, y); System.out.println("exp(" + x + ", " + y + ") = " + z); } } | def expn(a, b) d = 1 iter = 1 while b > 0 iter = iter + 1 if b%2 == 1 # right 1 bit d = d*a end b = b/2 # drop right bit if b > 0 a = a*a end end print "Iterations: ", iter, "\n" d end x = ARGV[0].to_i y = ARGV[1].to_i print "exp(", x, ",", y, "):" print expn(x, y), "\n" |