Meissel-Lehmer algorithm in Python
Computes pi(x) as phi(x, a) + a - 1 - P2(x, a) with a = pi(x^(1/3)), splitting phi into ordinary and special leaves. This form takes O(x^(2/3) log x) time and O(x^(2/3)) memory.
- File
meissel_lehmer_algorithm.py, 81 lines- Level
- I0 not checked yet
- Toolchain
- Python 3.14, pytest 9
- Test framework
- pytest
- Run the tests
python3 -m pytest
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/meissel-lehmer-algorithm/. The authors are listed in prov/2SPPAE.jsonl.
from math import isqrt
def icbrt(x: int) -> int:
"""Return the integer cube root, the largest y >= 0 with y^3 <= x. Precondition: x >= 0."""
y = round(x ** (1.0 / 3.0)) if x > 0 else 0
while y * y * y > x:
y -= 1
while (y + 1) ** 3 <= x:
y += 1
return y
def meissel_lehmer(x: int) -> int:
"""Return pi(x), the number of primes p <= x.
Precondition: x is an integer (x < 2 gives 0).
Cost: O(x^(2/3) log x) word operations and O(x^(2/3)) words of memory,
for y = x^(1/3), z = x / y, the Fenwick tree over [1, z] and the sieve up to z.
"""
if x < 2: # @line L1
return 0
y = icbrt(x) # @line L2
z = x // y
# @line L3: least prime factors and primes up to z, pi table, mu up to y
lpf = [0] * (z + 1)
primes: list[int] = []
for i in range(2, z + 1):
if lpf[i] == 0:
lpf[i] = i
primes.append(i)
li = lpf[i]
for p in primes:
if p > li or i * p > z:
break
lpf[i * p] = p
pi = [0] * (z + 1)
count = 0
for i in range(2, z + 1):
if lpf[i] == i:
count += 1
pi[i] = count
mu = [0] * (y + 1)
mu[1] = 1
for m in range(2, y + 1):
p = lpf[m]
k = m // p
mu[m] = 0 if k % p == 0 else -mu[k]
a = pi[y]
phi = 0
for m in range(1, y + 1): # @line L4
phi += mu[m] * (x // m)
tree = [0] + [i & -i for i in range(1, z + 1)] # @line L5
alive = bytearray([1]) * (z + 1)
for b in range(a): # @line L6
p = primes[b] # p is p_(b+1); the tree now counts n <= z free of p_1, ..., p_b
for m in range(y // p + 1, y + 1): # @line L7
if mu[m] != 0 and lpf[m] > p:
n = x // (m * p) # @line L8
s = 0
while n > 0:
s += tree[n]
n -= n & -n
phi -= mu[m] * s
for k in range(p, z + 1, p): # @line L9
if alive[k]:
alive[k] = 0
i = k
while i <= z:
tree[i] -= 1
i += i & -i
r = isqrt(x) # @line L10
p2 = 0
for p in primes[a:]:
if p > r:
break
p2 += pi[x // p] - pi[p] + 1
return phi + a - 1 - p2 # @line L11
test_meissel_lehmer_algorithm.py 85 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/meissel-lehmer-algorithm/. The authors are listed in prov/2SPPAE.jsonl.
import random
from meissel_lehmer_algorithm import icbrt, meissel_lehmer
def is_prime_by_trial_division(m: int) -> bool:
if m < 2:
return False
d = 2
while d * d <= m:
if m % d == 0:
return False
d += 1
return True
LIMIT = 300000
PI = [0] * (LIMIT + 1)
def _fill() -> None:
flags = bytearray([1]) * (LIMIT + 1)
flags[0] = flags[1] = 0
p = 2
while p * p <= LIMIT:
if flags[p]:
flags[p * p :: p] = bytes(len(range(p * p, LIMIT + 1, p)))
p += 1
c = 0
for i in range(LIMIT + 1):
c += flags[i]
PI[i] = c
_fill()
def test_sieve_table_against_trial_division() -> None:
c = 0
for m in range(5001):
c += is_prime_by_trial_division(m)
assert PI[m] == c
def test_every_x_up_to_2000() -> None:
for x in range(-3, 2001):
assert meissel_lehmer(x) == PI[max(x, 0)]
def test_cube_root() -> None:
for x in range(0, 30000):
y = icbrt(x)
assert y**3 <= x < (y + 1) ** 3
for y in (10**5, 10**6 - 1, 2**21):
for x in (y**3 - 1, y**3, y**3 + 1):
c = icbrt(x)
assert c**3 <= x < (c + 1) ** 3
def test_cubes_and_squares_of_primes() -> None:
for q in (2, 3, 5, 7, 11, 13, 17, 31, 47, 61, 67):
for base in (q**3, q * q, (q + 1) ** 3):
for x in (base - 1, base, base + 1):
if x <= LIMIT:
assert meissel_lehmer(x) == PI[x]
for q in (101, 211, 307, 401, 541):
assert meissel_lehmer(q * q) == PI[q * q]
def test_random() -> None:
rng = random.Random(1959)
for _ in range(60):
x = rng.randint(2001, LIMIT)
assert meissel_lehmer(x) == PI[x]
def test_known_values() -> None:
assert meissel_lehmer(100) == 25
assert meissel_lehmer(1000) == 168
assert meissel_lehmer(10**6) == 78498
assert meissel_lehmer(10**7) == 664579
assert meissel_lehmer(10**8) == 5761455
What the next level needs
- the tests did not run in this build