Modular Arithmetic
When numbers get astronomically large, we work with remainders — and modular exponentiation keeps them in check.
The problem
A problem asks you to compute 7^1000000 mod 1000000007. You type 7 ** 1000000 and wait.
In Python it eventually returns a number with over 800,000 digits; in Java and C it silently
overflows into garbage long before that. Either way you have burned time and memory building
a monster of a number, only to throw almost all of it away with one final mod.
The answer you actually want fits in a single 64-bit integer. The trouble is getting there without ever holding the giant intermediate value. This is what modular arithmetic is for — staying small the whole way.
A first attempt
Multiply the base by itself exp times, taking the remainder as you go so the running value
never explodes:
def mod_pow_slow(base, exp, mod):
result = 1
for _ in range(exp):
result = result * base % mod
return resultTaking the mod every step keeps each number small — good. But it still loops exp times.
For an exponent of a billion that is a billion multiplications, O(exp) time. We fixed the
size problem but not the count problem.
The insight
You do not have to multiply one factor at a time. Any exponent can be built from repeated
squaring. Reading the exponent in binary, x^13 = x^8 · x^4 · x^1, and each of
x^2, x^4, x^8 is just the previous one squared. So instead of exp multiplications you
need about log₂(exp) of them.
Combine that with two facts that let you take the remainder early and often:
(a · b) mod m = ((a mod m) · (b mod m)) mod m
(a + b) mod m = ((a mod m) + (b mod m)) mod mThe result is modular exponentiation by squaring — a billion-step loop becomes about 30 steps, and no value ever leaves the range of the modulus.
How it works
Reduce the base and start at 1
Set result = 1 and base = base mod m. Everything from here stays below m², which fits
in a 64-bit integer for reasonable moduli.
Look at the lowest bit of the exponent
If exp is odd (its last bit is 1), the current base contributes to the answer, so fold
it in: result = result · base mod m.
Square the base, shift the exponent
Square the base for the next bit: base = base · base mod m. Then drop the bit you just
handled with exp >>= 1.
Stop when the exponent hits zero
Repeat until exp is 0. You have processed every binary digit, and result holds
base^exp mod m.
The code
def mod_pow(base, exp, mod):
result = 1
base %= mod
while exp > 0:
if exp & 1:
result = result * base % mod
base = base * base % mod
exp >>= 1
return result
print(mod_pow(7, 1000000, 1000000007)) # 880007888// BigInt keeps intermediate products exact for large moduli.
function modPow(base: bigint, exp: bigint, mod: bigint): bigint {
let result = 1n;
base %= mod;
while (exp > 0n) {
if (exp & 1n) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exp >>= 1n;
}
return result;
}
console.log(modPow(7n, 1000000n, 1000000007n).toString()); // 880007888// Safe while mod < ~3 billion, so base * base fits in a long.
static long modPow(long base, long exp, long mod) {
long result = 1;
base %= mod;
while (exp > 0) {
if ((exp & 1) == 1) {
result = result * base % mod;
}
base = base * base % mod;
exp >>= 1;
}
return result;
}long long mod_pow(long long base, long long exp, long long mod) {
long long result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) {
result = result * base % mod;
}
base = base * base % mod;
exp >>= 1;
}
return result;
}long long modPow(long long base, long long exp, long long mod) {
long long result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) {
result = result * base % mod;
}
base = base * base % mod;
exp >>= 1;
}
return result;
}Complexity
| Approach | Time | Space |
|---|---|---|
Repeated multiply mod_pow_slow | O(exp) | O(1) |
| Exponentiation by squaring | O(log exp) | O(1) |
Each iteration handles one bit of the exponent, so the loop runs ⌊log₂ exp⌋ + 1 times —
about 30 iterations for a billion, 60 for a quintillion.
When to use it
Mind the overflow and the sign
Modular exponentiation powers hashing, RSA, and every mod 1e9+7 competitive problem. Two
traps: base * base can overflow a 64-bit integer once mod exceeds ~3 billion — use
128-bit or BigInt then. And in C, C++, and Java the % of a negative number is negative, so
normalize with ((x % m) + m) % m when inputs might be negative.
Practice
Recap
- Naive powering either overflows or loops
exptimes — both are unacceptable at scale. (a·b) mod mdistributes, so you can take remainders early and keep numbers small.- Exponentiation by squaring reads the exponent's bits for an O(log exp) modular power.
How is this guide?
Last updated on