Skip to content

gh-153222: Add math.integer.isprime() function - #160678

Open
StanFromIreland wants to merge 4 commits into
python:mainfrom
StanFromIreland:math-integer-isprime-u64
Open

StanFromIreland wants to merge 4 commits into
python:mainfrom
StanFromIreland:math-integer-isprime-u64

Conversation

@StanFromIreland

@StanFromIreland StanFromIreland commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

This builds (and trims) on #153224.

As I suggest in #153224 (comment), it drops math.integer.primes().

And, the strong probable prime test now runs in Montgomery form. A context (inverse of n mod 2**64, 2**64 mod n, and the odd part of n - 1) is computed once per n, and each modular multiplication costs two 64-bit high products instead of a 128-bit division. The reduction is then written subtractively so no intermediate exceeds 64 bits, which keeps n close to 2**64. FLINT made the same change to n_is_prime in flintlib/flint#2519 and reported 1.2x to 1.5x speedups for 33 to 64 bit inputs. Benchmarking our isprime on an optimised build (compared to the original PR), I get a nice speedup:

isprime[small]: Mean +- std dev: [before] 305 us +- 11 us -> [after] 303 us +- 8 us: 1.01x faster
isprime[below_2**32]: Mean +- std dev: [before] 699 us +- 13 us -> [after] 606 us +- 19 us: 1.15x faster
isprime[below_2**63]: Mean +- std dev: [before] 1.63 ms +- 0.04 ms -> [after] 1.33 ms +- 0.03 ms: 1.23x faster
isprime[below_2**64]: Mean +- std dev: [before] 1.68 ms +- 0.03 ms -> [after] 1.51 ms +- 0.03 ms: 1.11x faster

Geometric mean: 1.12x faster
Benchmark script
import pyperf
from math.integer import isprime

N = 10_000

# name: (start, number of primes in [start, start + N))
RANGES = {
    'small': (2, 1229),
    'below_2**32': (2**32 - N, 447),
    'below_2**63': (2**63 - N, 216),
    'below_2**64': (2**64 - N, 218),
}


def count_primes(start, expected):
    count = sum(map(isprime, range(start, start + N)))
    assert count == expected, (start, count, expected)
    return count


runner = pyperf.Runner()
for name, (start, expected) in RANGES.items():
    runner.bench_func(f'isprime[{name}]', count_primes, start, expected)

CC @mdickinson @tim-one

serhiy-storchaka and others added 3 commits July 6, 2026 21:31
Draft implementation of prime-number functionality for the math.integer
module (deferred out of the initial PEP 791 scope), supporting integers
less than 2**64.

isprime() uses the deterministic Miller-Rabin test: bases {2, 7, 61}
below 4759123141, Jim Sinclair's seven bases up to 2**64 (verified
against the Feitsma-Galway list of base-2 strong pseudoprimes).  The
result is exact for the whole supported range; larger arguments raise
OverflowError.

primes(start=2, stop=None) returns an iterator of the primes in
[start, stop), unbounded if stop is None.

Neither function is mirrored into the math namespace (PEP 791).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #35396164 | 📁 Comparing 0a794af against main (bb7e16a)

  🔍 Preview build  

3 files changed
± library/math.integer.html
± whatsnew/3.16.html
± whatsnew/changelog.html

Comment on lines +16 to +18
The following functions are provided by this module.
Unless stated otherwise below,
all return values are computed exactly and are integers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change is not required, no? isprime() returns an integer (boolean) and it's exact.

Comment on lines +52 to +59
Return ``True`` if *n* is a prime number, ``False`` otherwise.

A prime number is a natural number greater than 1
that is not a product of two smaller natural numbers.
Negative numbers, ``0`` and ``1`` are not prime.

The argument must be less than 2\ :sup:`64`;
raises :exc:`OverflowError` otherwise.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's an acceptable API.

  1. it isn't compatible with gmpy2 (is_prime() returns True for probable primes)
  2. can't be used as an alternative to gmpy2 in applications like SymPy or Mpmath, currently using own isprime() helpers.

I suggest using Miller-Rabin test without constraints on the number range. You can document that test is certain for integers < 2^64, perhaps as an implementation detail. Or introduce instead API like gmpy2.is_probab_prime (ternary logic: definitely prime, probably prime, or definitely composite).

@picnixz

picnixz commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

For primality testing we should definitely use MR testing and make sure that we just do not limit ourselves to 64 bits. I think we should rather keep the other PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting core review type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants