Algorithm Search⌘K

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
meissel_lehmer_algorithm.pyRawGitHub
# 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

Read the full page about Meissel-Lehmer algorithm