The exponent y
is written as a sum of powers of two, i.e. in binary.
Consider a practical example: (x**11) % M
. Mathematically,
(x**11) % M == ((((((x**1) % M) * x**2) % M) * x**8) % M)
This is useful, because a simple loop is sufficient to calculate the powers of x
that are powers of two. For example, if you want to calculate x**(2**i)
:
for (j = 0; j < i; j++)
x = x * x;
We can examine the logic in detail, if we look at a function that calculates basis**exponent
:
unsigned long power(unsigned long basis,
unsigned long exponent)
{
unsigned long result = 1u;
unsigned long factor = basis;
unsigned long i;
for (i = 1u; i < exponent; i = i * 2u) {
/* i is some power of two,
and factor = basis**i.
*/
/* If i is in the sum (of powers of two) for exponent,
we include the corresponding power of basis
in the product. */
if (i & exponent)
result = result * factor;
/* Update factor for the next i. */
factor = factor * factor;
}
return result;
}
Note that the test (i & exponent)
is a binary AND operation, which is true if the result is nonzero, false if zero. Because i
is a power of two, in binary it has a single 1
with all other binary digits zero, so it essentially tests if the exponent, written in binary, has a 1
in that position.
OP's code is simpler, because instead of using separate variables i
and factor
, it shifts the exponent
right (dropping the rightmost binary digit), and uses the basis
itself. That is,
unsigned long power(unsigned long basis,
unsigned long exponent)
{
unsigned long result = 1u;
while (exponent > 0u) {
if (exponent & 1u)
result = result * basis;
basis = basis * basis;
exponent = exponent >> 1;
}
return result;
}
The modulo operation is the final wrinkle, but it too is trivial. If you compute a product modulo some positive integer, you can apply the modulo operator to each term, and to each temporary result, without affecting the result:
(a * b * c * d * ... * z) % m
= ( (a % m) * (b % m) * (c % m) * ... * (z % m) ) % m
= ( ... (((((a % m) * (b % m)) % m) * (c % m)) % m) * ... * (z % m)) % m
This is obviously useful, because you can calculate the product of any number of terms, with all terms smaller than m
, and all intermediate terms smaller than m*m
.
Time complexity of this kind of exponentiation is O(log N), where N is the exponent.