MATH-3

Fast exponentiation

Number theory and countingLesson 3 of 713 min

RECR-1 raised x to the power n by halving n, with about log₂ n calls, and RECR-P1 asked for it modulo m. This lesson writes the same idea as a loop over the bits of n, and then uses it for something bigger: powers of matrices, which reach the millionth term of a rule such as Fibonacci’s, or the 10¹⁸th (10¹⁸ is a 1 followed by 18 zeros), in a few dozen steps.

Powers from the bits of n

Write n in binary (BITS-1). 13 is 1101, which is 8 + 4 + 1, so x¹³ = x⁸ · x⁴ · x¹. The powers x¹, x², x⁴, x⁸, … each come from squaring the one before: multiplying two equal powers adds their exponents, and 2ᵏ + 2ᵏ = 2ᵏ⁺¹. So go through the bits of n from the lowest: keep squaring a running power, and multiply it into the result whenever the bit is 1.

FIG MATH-3.1Powers by squaring
Power of
1101
13 = 8 + 4 + 1 = 1101
1/6

Work out 3¹³ % 100 (3 to the power 13, then the remainder after dividing by 100). 13 in binary is 1101, so 3¹³ = 3⁸ · 3⁴ · 3¹. The powers 3¹, 3², 3⁴, 3⁸ each come from squaring the one before.

Step 1 of 6

power_mod.pyPython
def power_mod(x, n, m):
    result = 1 % m
    x %= m
    while n:
        if n & 1:                  # this bit of n is 1
            result = result * x % m
        x = x * x % m              # x to the next power of 2
        n >>= 1
    return result

For n = 10¹⁸ (a 1 followed by 18 zeros) that is about 60 squarings, against 10¹⁸ multiplications for a plain loop. The recursive version of RECR-1 does the same work, from the top bit down.

With a modulus

Lesson 2 showed that a remainder can be taken after every product, so every number in the loop stays below m. Without the modulus, 3¹⁰⁰⁰⁰⁰⁰ (a million 3s multiplied together) has 477,122 digits, and the last few squarings alone take real time. With it, each step is a product of two small numbers.

Python’s built-in pow(x, n, m) does exactly this, in C. It also accepts n = −1 for the inverse modulo m (lesson 5). Always use the three-argument form: pow(x, n) % m works out the full power first.

Powers of a matrix

The loop above only multiplies, and it only needs the multiplication to be associative: (A · B) · C = A · (B · C), so the products can be grouped in any way. Matrices (square tables of numbers) are multiplied that way too: entry (i, j) of A · B is the sum over t of A[i][t] · B[t][j]. The order of the two factors matters for matrices, but grouping does not, and the power loop never changes the order.

The Fibonacci numbers are F(0) = 0, F(1) = 1, and each next one is the sum of the two before. Let M be the 2 × 2 matrix with rows (1, 1) and (1, 0). Then

Why? For k = 1 it is M itself. If it holds for k, multiply by M: the new top row is (F(k+1) + F(k), F(k+1)), which is (F(k+2), F(k+1)), and the bottom row is (F(k) + F(k−1), F(k)), which is (F(k+1), F(k)). So it holds for k + 1 too. The second version of the figure builds M¹³ from M¹, M⁴, M⁸.

fib_mod.pyPython
def mat_mul(A, B, m):
    k = len(A)
    return [[sum(A[i][t] * B[t][j] for t in range(k)) % m
             for j in range(k)] for i in range(k)]

def mat_pow(A, n, m):
    k = len(A)
    R = [[int(i == j) for j in range(k)] for i in range(k)]
    while n:
        if n & 1:
            R = mat_mul(R, A, m)
        A = mat_mul(A, A, m)
        n >>= 1
    return R

def fib_mod(n, m):
    return mat_pow([[1, 1], [1, 0]], n, m)[0][1] % m

R starts as the identity matrix, with 1s on the diagonal and 0s elsewhere: it plays the part of the 1 in result = 1, since multiplying by it changes nothing. A product of two k × k matrices takes k³ multiplications, so a power costs O(k³ log n).

Any linear rule

The same works for any rule where the next term is a fixed sum of multiples of the last k terms. RECR-P2 counted the ways to go up n steps with moves of 1, 2 or 3: w(n) = w(n − 1) + w(n − 2) + w(n − 3). Keep the last three terms as a column (w(n), w(n − 1), w(n − 2)). One step of the rule turns it into (w(n + 1), w(n), w(n − 1)), and that is multiplying by a 3 × 3 matrix:

  • its top row is (1, 1, 1): the new term adds up the last three;
  • its other rows are (1, 0, 0) and (0, 1, 0): they copy w(n) and w(n − 1) one place down.

So the nth term is an entry of this matrix to the power n, times the starting column. MATH-P3 builds such a matrix for any set of allowed moves. The top row has a 1 for each allowed move and a 0 for each move that is not allowed.

Where fast powers are used

  • Cryptography. RSA encrypts a message m as mᵉ modulo a number of about 600 digits, and key exchange computes gᵃ modulo a large prime. Both are fast powers with exponents of hundreds of digits.
  • Testing for primes. Lesson 5’s Fermat rule and the Miller–Rabin test (HARD-5) check a number with a few modular powers, far faster than trying divisors.
  • Counting walks. In a graph (GRPH-1), entry (i, j) of the adjacency matrix to the power n is the number of walks of exactly n edges from i to j.
  • Repeating remainders. Fibonacci numbers modulo m repeat in a cycle (modulo 10, every 60 terms). Matrix powers find any term without knowing the cycle.

Now try MATH-P3, The footbridge, 10¹⁸ steps.

MATH-3 counts as completed once you step through the figure to the last step.