Skip to content
The Lumenary

Math laboratory

How fast factorials grow: exact values and Stirling's formula

Prints exact factorials beside 2^n and 10^n, finds where n! overtakes them (n = 4 and n = 25), counts the 68 digits of 52!, estimates the time to list every shuffle of a deck, and compares n! with Stirling's approximation, whose relative error shrinks roughly like 1/(12n). Exact integers plus floating-point estimates; Stirling's formula is quoted, not proved.

Back to the labExact computationNumerical approximation
How fast factorials grow: exact values and Stirling's formulaPythonExact computationNumerical approximationComputationally tested
"""How fast does n! grow? Exact factorials and a famous approximation.

The number of ways to arrange n different objects is n!. This experiment
prints exact factorials next to 2^n and 10^n, finds where n! overtakes
each, counts the digits of 52! (the number of ways to shuffle a deck of
cards), and estimates how long listing all those orders would take. It
ends with Stirling's approximation n! ~ sqrt(2 pi n) (n/e)^n, a result
from analysis that is quoted here, not proved, with its exact relative
error for several n.
"""

import math

SECONDS_PER_YEAR = 365.25 * 24 * 3600
AGE_OF_UNIVERSE_YEARS = 1.38e10

def first_n_where_factorial_exceeds(base):
    """Smallest n >= 1 with n! > base**n."""
    n = 1
    while math.factorial(n) <= base ** n:
        n += 1
    return n

def digits(n):
    """Number of decimal digits of a positive integer."""
    return len(str(n))

def stirling(n):
    """Stirling's approximation sqrt(2 pi n) (n / e)^n, as a float."""
    return math.sqrt(2 * math.pi * n) * (n / math.e) ** n

def relative_error(n):
    """(n! - Stirling) / n!, computed in floating point."""
    return (math.factorial(n) - stirling(n)) / math.factorial(n)

def years_to_list(count, per_second):
    """Years needed to list `count` items at `per_second` items a second."""
    return count / per_second / SECONDS_PER_YEAR

def main():
    print("  n            n!          2^n")
    for n in (1, 2, 3, 4, 5, 8, 10, 13):
        print(f"{n:3d} {math.factorial(n):>13,} {2 ** n:>12,}")
    print(f"n! first exceeds 2^n at n = {first_n_where_factorial_exceeds(2)},"
          f" and 10^n at n = {first_n_where_factorial_exceeds(10)}.")
    print()
    deck = math.factorial(52)
    print(f"Shuffles of a 52-card deck: 52! has {digits(deck)} digits,")
    print(f"  about {float(deck):.3e}.")
    years = years_to_list(deck, 1e9)
    print(f"  Listing a billion shuffles a second would take about "
          f"{years:.1e} years,")
    print(f"  roughly {years / AGE_OF_UNIVERSE_YEARS:.0e} times the age of "
          "the universe.")
    print()
    print("Stirling's approximation sqrt(2 pi n) (n/e)^n:")
    print("    n    relative error    1/(12n)")
    for n in (1, 2, 5, 10, 20, 52):
        print(f"  {n:3d}    {relative_error(n):.6f}        {1 / (12 * n):.6f}")
    print()
    print("Epistemic status: the factorials, powers and digit count are exact")
    print("integers; times and Stirling values are floating-point estimates.")
    print("Stirling's formula is a theorem of analysis quoted here, not")
    print("proved; the table only shows its error for six values of n.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in