← Propositiones
Principia P.000009 ·ARITHMETIC ·SEED

On raising to a power by repeated squaring

De Potentia per Quadrationem Iteratam Eruenda

Discharged pending classO(log n) paradigmdivide_and_conquer falsifiable ifFor any base x and any non-negative integer n, the repeated-squaring procedure returns x^n using at most 2*(floor(log2 n) + 1) multiplications, i.e. Theta(log n) multiplications, never the n-1 multiplications of the naive product.

Enuntiatio

Propositio IX. Theorema. Let x be any element of a monoid under multiplication (in particular any number), and let n be a non-negative integer. Then the power x^n may be raised, not by n−1 successive multiplications, but by a number of multiplications not exceeding 2·(⌊log₂ n⌋ + 1).

The construction rests upon a single arithmetical identity, which I take as the law beneath the appearance: a power with an even exponent is the square of the power with half the exponent, and a power with an odd exponent is that same square multiplied once by the base. Formally, for n > 0,

x^n = (x^(n/2))² when n is even, and x^n = x · (x^((n−1)/2))² when n is odd,

with the boundary x^0 = 1. Because each appeal to the identity replaces the exponent by its halving, the exponent is exhausted after ⌊log₂ n⌋ + 1 halvings, and the count of multiplications is therefore logarithmic in n, not linear. Q.E.D. follows in the Demonstratio.

Expressio

def power(x, n):
    """Raise x to the non-negative integer power n by repeated squaring.

    Computes x**n using O(log n) multiplications via the identity
        x**n = (x**(n//2))**2            if n is even,
        x**n = x * (x**((n-1)//2))**2    if n is odd,
    with x**0 = 1. Works for any value x that supports multiplication
    (int, float, Fraction, complex, square matrix, ...).

    >>> power(3, 13)
    1594323
    >>> power(2, 10)
    1024
    >>> power(5, 0)
    1
    >>> [power(2, k) for k in range(6)]
    [1, 2, 4, 8, 16, 32]
    """
    if not isinstance(n, int) or n < 0:
        raise ValueError("exponent n must be a non-negative integer")

    result = 1        # invariant accumulator: holds the product of "odd" contributions
    base = x          # holds x**(2**k) for the current bit position k
    e = n
    while e > 0:
        if e & 1:               # current low bit of e is set
            result *= base      # fold in x**(2**k) for this bit
        base *= base            # advance base to x**(2**(k+1)): one squaring
        e >>= 1                 # drop the consumed bit
    return result


# --- concrete demonstrations of correctness (inline assertions) ---
assert power(3, 13) == 1594323          # 3**13, the stated witness
assert power(3, 13) == 3 ** 13
assert power(2, 0) == 1                 # boundary n = 0
assert power(7, 1) == 7                 # boundary n = 1
assert power(2, 10) == 1024
assert power(0, 0) == 1                 # 0**0 taken as the empty product = 1
# Exhaustive cross-check against Python's own exponentiation:
for _x in range(-4, 5):
    for _n in range(0, 30):
        assert power(_x, _n) == _x ** _n, (_x, _n)

if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=False)
    print("power: all assertions and doctests passed")

Demonstratio

I prove two things: that the procedure returns x^n (correctness), and that it does so within 2·(⌊log₂ n⌋ + 1) multiplications (the bound).

1. The governing identity. For n > 0 write n = 2q + r with r ∈ {0, 1}. By associativity and commutativity of multiplication, x^n = x^(2q + r) = (x^q)² · x^r. When r = 0 (n even, q = n/2) this is (x^(n/2))²; when r = 1 (n odd, q = (n−1)/2) this is x·(x^((n−1)/2))². This is exactly the identity stated in the Enuntiatio. The base case x^0 = 1 is the empty product. This justifies the recursive form; I now show the iterative form computes the same value.

2. Correctness by loop invariant. Let n have binary expansion n = Σ_{k=0}^{m} b_k·2^k with b_k ∈ {0,1} and m = ⌊log₂ n⌋ (for n > 0). The procedure consumes the bits b_0, b_1, … from least significant upward. Index the loop iterations by k = 0, 1, 2, …, and let e_k, base_k, result_k denote the values of e, base, result at the start of iteration k. I claim the invariant:

(I) at the start of iteration k, base_k = x^(2^k), e_k = ⌊n / 2^k⌋, and result_k = x^(Σ_{j<k} b_j·2^j).

Base (k = 0). Before the loop, base_0 = x = x^(2^0), e_0 = n = ⌊n/2^0⌋, and result_0 = 1 = x^(empty sum). The invariant holds.

Step. Suppose (I) holds at the start of iteration k and the loop body executes (so e_k > 0). The low bit of e_k = ⌊n/2^k⌋ is precisely b_k. If b_k = 1 the body multiplies result by base_k = x^(2^k), giving x^(Σ_{j<k} b_j 2^j) · x^(2^k) = x^(Σ_{j≤k} b_j 2^j); if b_k = 0 result is unchanged and equals x^(Σ_{j≤k} b_j 2^j) trivially (the new term contributes 2^k·0 = 0). Either way result_{k+1} = x^(Σ_{j<k+1} b_j 2^j). Next, base *= base gives base_{k+1} = (x^(2^k))² = x^(2^(k+1)). Finally e >>= 1 gives e_{k+1} = ⌊e_k/2⌋ = ⌊⌊n/2^k⌋/2⌋ = ⌊n/2^(k+1)⌋ (the standard identity for iterated integer division). Thus (I) holds at the start of iteration k+1.

Termination and conclusion. e is a non-negative integer strictly decreasing under e ↦ ⌊e/2⌋ while positive, so the loop halts; it halts at the first index K with e_K = ⌊n/2^K⌋ = 0, i.e. K = m+1 = ⌊log₂ n⌋ + 1 for n > 0. By (I) the returned value is result_K = x^(Σ_{j<K} b_j·2^j) = x^(Σ_{k=0}^{m} b_k·2^k) = x^n. For n = 0 the loop body never runs and the procedure returns the initial result = 1 = x^0. Correctness is established in all cases.

3. The multiplication bound. The loop executes exactly K = ⌊log₂ n⌋ + 1 iterations for n > 0 (and none for n = 0). Each iteration performs exactly one squaring (base *= base) and at most one accumulating multiplication (result *= base, only when b_k = 1). Hence the total number of multiplications is at most 2K = 2·(⌊log₂ n⌋ + 1), and at least K (one squaring per iteration). This is Θ(log n), which for all n ≥ 3 is strictly fewer than the n−1 multiplications of the naive successive-product method, and the gap widens without bound. The witness power(3, 13): n = 13 = 1101₂, so K = ⌊log₂ 13⌋ + 1 = 3 + 1 = 4 iterations, three set bits among b_3 b_2 b_1 b_0 = 1101 contributing three accumulations, for 4 + 3 = 7 multiplications against the naive 12 — and the result is 3^13 = 1594323, as asserted in the Expressio.

Q.E.D.