Math Utilities
Quick reference
| GCD (Euclidean, mod version) | O(log(min(a,b))) |
| Primality check (trial division) | O(βn) |
| Modular exponentiation (fast power) | O(log n) |
| Bit operations (AND/OR/XOR/shift) | O(1) |
GCD (greatest common divisor) has a geometric picture worth keeping in your head: imagine a rectangle, say 18Γ12, and you want to tile it exactly with the biggest possible square, no gaps, no overhang. That biggest square's side length is GCD(18, 12) = 6. You can find it by repeatedly cutting the largest square you can off the rectangle and repeating on what's left β that's the same process as the Euclidean algorithm, just drawn instead of computed.
Modulo (%) is clock arithmetic. A 12-hour clock doesn't count 13, 14, 15 β it wraps back to 1, 2, 3. `13 % 12` is 1 for exactly that reason: modulo gives you the remainder after wrapping around every multiple of the divisor. It's the tool for anything cyclical β indexing into a fixed-size buffer, checking even/odd, spreading values evenly across buckets.
A prime number is one that can't be tiled evenly by any square smaller than itself (other than a 1Γ1 square) β no whole-number factors besides 1 and itself. Primes are the "atoms" of the integers: every whole number greater than 1 is either prime or built by multiplying primes together.
Underneath all of it, a computer only has two symbols: 0 and 1 β bits. Every number, character, and instruction is some pattern of bits, and operations like AND/OR/XOR/shift work directly on those patterns. They look low-level, but they're often the fastest tool available, because they map straight onto what the hardware already does.
Core routines
// Euclidean GCD β subtraction intuition first, then the efficient form:
// GCD(a, b) == GCD(a - b, b) when a > b (repeatedly subtracting the smaller from the larger
// eventually lands on the same answer as the tiling picture above)
// The efficient version replaces repeated subtraction with a single mod:
function gcd(a, b):
while b != 0:
a, b = b, a % b // a % b does many subtractions in one step
return aisPrime β trial division, O(βn)
function isPrime(n):
if n < 2:
return false
for i in range(2, int(sqrt(n)) + 1): // factors always come in pairs that
if n % i == 0: // straddle sqrt(n), so checking past
return false // sqrt(n) can never find a new one
return truemodPow β fast modular exponentiation via repeated squaring, O(log n)
function modPow(base, exp, mod):
result = 1
base = base % mod
while exp > 0:
if exp % 2 == 1: // odd exponent: fold in the current base
result = (result * base) % mod
exp = exp // 2 // halve the exponent...
base = (base * base) % mod // ...and square the base to match
return resultbit operations β AND / OR / XOR / shift, each O(1)
a & b // AND: 1 only where BOTH bits are 1 β used to mask/check specific bits
a | b // OR: 1 where EITHER bit is 1 β used to set specific bits
a ^ b // XOR: 1 where the bits DIFFER β a value XORed with itself is 0
a << k // left shift: multiply by 2^k
a >> k // right shift: divide by 2^k (integer division)
n & (n - 1) // clears the lowest set bit β n-1 flips every trailing 0 to 1 and the
// lowest 1 to 0, so ANDing with n turns that lowest 1 off
x ^ x == 0 // any value XORed with itself cancels to 0 β the trick behind "find the
// one unpaired value" problemsFixed-width integer operations (bit ops, basic arithmetic) are O(1) because a CPU register holds a bounded number of bits β the hardware does the work in one step regardless of the number's value. Everything below that assumption gets more expensive as the numbers involved grow.
| Operation | Time | Space | Why |
|---|---|---|---|
| GCD (Euclidean, mod version) | O(log(min(a,b))) | O(1) | Each step replaces (a, b) with (b, a % b); the smaller value shrinks by at least half every two steps, so it takes a logarithmic number of steps to reach 0. |
| Primality check (trial division) | O(βn) | O(1) | Only need to test candidate factors up to βn β anything past that would have already shown up paired with a smaller factor. |
| Modular exponentiation (fast power) | O(log n) | O(1) | Squaring the base and halving the exponent each step reaches the answer in logβ(n) multiplications, versus O(n) for multiplying the base by itself n times naively. |
| Bit operations (AND/OR/XOR/shift) | O(1) | O(1) | A fixed-width integer (32 or 64 bits) fits in a single CPU register β the operation touches all bits at once in hardware, independent of the number's value. |
GCD of 0 and negatives
GCD(0, 0) is conventionally defined as 0 (no positive common divisor exists otherwise), and GCD(a, 0) = |a|. For negative inputs, GCD is defined via absolute value β GCD(-12, 18) = GCD(12, 18) = 6 β because divisibility doesn't care about sign, but a naive mod-based implementation can return a negative result if you don't normalize signs first.
0, 1, and negatives are never prime
Primality requires exactly two distinct positive divisors: 1 and itself. 0 has infinitely many divisors, 1 has only one divisor (itself), and negative numbers aren't part of the standard definition at all β all three are "not prime" by definition, not by any calculation. A common bug is a trial-division loop that never runs for n < 2 and silently returns true.
Modulo of negative numbers isn't universal
Python's `%` always returns a result with the same sign as the divisor (true mathematical mod) β `-7 % 3` is `2`. C, Java, and JavaScript's `%` follows the sign of the dividend instead (a truncating remainder) β `-7 % 3` is `-1` in those languages. Porting modulo-based logic (like circular buffer indexing) between languages without accounting for this is a classic silent bug.
Integer overflow and sign bits in shifting
Left-shifting a signed integer far enough can push a 1 into the sign bit, flipping a positive number negative in languages with fixed-width signed integers β and shifting a fixed-width type by more bits than it has is undefined or implementation-specific behavior in several languages. Bit tricks that work perfectly in a language with arbitrary-precision integers (like Python) can silently break when ported to a fixed-width language.
Sign in to mark problems done β progress syncs across devices.