Cairn
algorithms · number theory · O(log exp)

back to Number Theory

Modular Exponentiation

The site's fifth Number Theory entry. The question: given base, exp, and mod, what is baseexp mod mod — without ever forming the full, possibly astronomical, number baseexp along the way? This isn't a new idea on this site: Miller–Rabin already leans on it, compressed into one small modPow helper, to test whether a number with hundreds of digits is prime. This page unpacks exactly what that helper does and why it stays fast and small no matter how large exp gets.

The naive approach — multiply base by itself exp times, then reduce mod mod once at the end — costs exp multiplications and, worse, lets the intermediate number grow without bound the whole way (see Pitfalls). Square-and-multiply does both better: reduce mod mod after every multiplication, and reuse each squared value for two bits' worth of work at once, cutting the multiplication count from exp down to about log₂(exp).

Try it

Enter a base, exponent, and modulus, then step through the exponentiation one bit at a time. 3, 13, 7 is small enough to check by hand. The other two presets replay numbers from Miller–Rabin's own Pitfalls section — this is the exact computation that decides whether witness 2 or witness 3 exposes 2047 as composite.

Press Load, then Step through the exponentiation.

Why it works

Write the exponent in binary: exp = b0 + b1·2 + b2·4 + ... with each bi either 0 or 1. Then

base^exp = base^(b0 + 2b1 + 4b2 + ...) = (base^1)^b0 · (base^2)^b1 · (base^4)^b2 · ...

Each factor base2i is just the previous one squared, so the whole sequence — base1, base2, base4, base8, ... — costs one squaring per step to build, no matter how large exp is. The algorithm walks exp's bits from least significant to most significant: at bit i, it already has base2i mod mod sitting in a running variable; if that bit is 1 it multiplies that value into the running result, then squares it (mod mod) to get ready for bit i + 1, and shifts the exponent right to expose the next bit. Reducing mod mod after every squaring and every multiply keeps every number the algorithm ever holds below mod² — the size of exp only affects how many bits there are to walk, never how big any single number gets.

Reference implementation

function modPow(base, exp, mod) {   // all BigInt; exp assumed >= 0
  base = ((base % mod) + mod) % mod;
  let result = 1n % mod;
  while (exp > 0n) {
    if (exp & 1n) result = (result * base) % mod;
    base = (base * base) % mod;
    exp >>= 1n;
  }
  return result;
}

This is the same routine, reference-for-reference, that Miller–Rabin's own modPow uses internally — the only difference is the 1n % mod starting point instead of a bare 1n, which matters only for the mod = 1 edge case below (Miller–Rabin never calls it with a modulus that small, so its version never needed the extra care).

Pitfalls

Scanning the exponent's bits in the wrong order doesn't crash — it silently computes a different power. The algorithm above is built around one specific pairing: process bits least-significant-first, and square the base after using it. Flip that to scan the bits most-significant-first while keeping the same "multiply-then-square" step, and the running base no longer lines up with the bit it's paired against. Toggle the checkbox above on the default preset (3, 13, 7, where 13 is 1101 in binary — not a palindrome, so the bug is visible without changing any input) and the answer changes from the correct 3 to 5. That's not noise: 5 is the exact answer to 311 mod 7, and 11 is 1011 — the bit-reversal of 13's 1101. This was checked, not just observed once: sweeping every base in [1, 15], exp in [0, 63], and mod in [2, 15] (13,440 combinations), the broken scan's output matched base^reverseBits(exp) mod mod exactly every time, and diverged from the true, correctly-ordered answer in 4,067 of those 13,440 cases — whenever the exponent's binary form isn't its own reversal.

Reducing mod mod only once, at the very end, isn't just slower — the intermediate number it builds can dwarf the final answer by orders of magnitude. Take base = 7, exp = 222, mod = 13: the correct answer is 12. Computing 7222 in full before reducing requires building and holding a 188-digit integer, checked directly by running the multiplication out. Reducing mod 13 after every squaring, as the reference implementation does, never lets any intermediate value exceed mod² − 1 = 168 — a three-digit number, for the exact same computation. The gap only widens as exp grows: real uses of this algorithm (RSA key generation, Diffie–Hellman, and Miller–Rabin itself) routinely run it with exponents hundreds of digits long, where the never-reduce-until-the-end version isn't just slow, it's computationally out of reach.

An unreduced starting result is invisible for almost every input — except mod = 1. Initialize result to a bare 1n instead of 1n % mod, and for any exponent with at least one set bit, the loop's own % mod on the first multiply corrects it before it's ever observed — checked: modPow(5, 7, 1) comes out 0 either way, since the bug is masked the moment a real multiply happens. The one case that exposes it is exp = 0, where the loop body never runs at all: modPow(5, 0, 1) is mathematically 0 (everything reduces to 0 modulo 1, including the base case), but the unreduced version returns the bare starting 1 instead — checked directly, and the only input shape in this whole algorithm where that particular oversight actually surfaces.

Complexity

O(log exp) modular multiplications — one squaring per bit of exp, plus at most one extra multiply per set bit — against the naive approach's O(exp) multiplications. Each individual multiplication is on numbers bounded by mod², regardless of how large exp is, which is exactly what makes this usable with exponents that have hundreds of digits.

Extended Euclidean Algorithm already computes a modular inverse a different way, via Bézout coefficients, for any modulus; when the modulus happens to be prime, Fermat's Little Theorem gives a second route straight through this page's own routine — amod − 2 mod mod is a's inverse.

This site's guide, Choosing a Number Theory Algorithm, compares this entry against the other thirteen Number Theory entries side by side.