Skip to content

Commit 92c57fe

Browse files
gh-153222: Add math.integer.isprime() and math.integer.primes()
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>
1 parent 3a1b547 commit 92c57fe

6 files changed

Lines changed: 814 additions & 8 deletions

File tree

Doc/library/math.integer.rst

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ These functions accept integers and objects that implement the
1313
:meth:`~object.__index__` method which is used to convert the object to an integer
1414
number.
1515

16-
The following functions are provided by this module. All return values are
17-
computed exactly and are integers.
16+
The following functions are provided by this module.
17+
Unless stated otherwise below,
18+
all return values are computed exactly and are integers.
1819

1920

2021
.. function:: comb(n, k, /)
@@ -46,6 +47,20 @@ computed exactly and are integers.
4647
returns ``0``.
4748

4849

50+
.. function:: isprime(n, /)
51+
52+
Return ``True`` if *n* is a prime number, ``False`` otherwise.
53+
54+
A prime number is a natural number greater than 1
55+
that is not a product of two smaller natural numbers.
56+
Negative numbers, ``0`` and ``1`` are not prime.
57+
58+
The argument must be less than 2\ :sup:`64`;
59+
raises :exc:`OverflowError` otherwise.
60+
61+
.. versionadded:: next
62+
63+
4964
.. function:: isqrt(n, /)
5065

5166
Return the integer square root of the nonnegative integer *n*. This is the
@@ -83,3 +98,21 @@ computed exactly and are integers.
8398
and the function returns ``n!``.
8499

85100
Raises :exc:`ValueError` if either of the arguments are negative.
101+
102+
103+
.. function:: primes(start=2, stop=None)
104+
105+
Return an iterator of the prime numbers *p* with ``start <= p < stop``,
106+
in increasing order.
107+
If *stop* is ``None`` (the default), the iteration does not stop.
108+
109+
Roughly equivalent to
110+
``(p for p in itertools.count(start) if isprime(p))``
111+
for the unbounded form.
112+
113+
The bounds must be less than 2\ :sup:`64`;
114+
raises :exc:`OverflowError` otherwise.
115+
Unbounded iteration raises :exc:`OverflowError`
116+
if the candidates reach that limit.
117+
118+
.. versionadded:: next

Doc/whatsnew/3.16.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,15 @@ math
279279
(Contributed by Jeff Epler in :gh:`150534`.)
280280

281281

282+
math.integer
283+
------------
284+
285+
* Added :func:`math.integer.isprime` for primality testing of integers
286+
less than 2**64 and
287+
:func:`math.integer.primes` for iterating over prime numbers.
288+
(Contributed by Serhiy Storchaka in :gh:`153222`.)
289+
290+
282291
os
283292
--
284293

Lib/test/test_math_integer.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from decimal import Decimal
22
from fractions import Fraction
3+
import itertools
4+
import random
35
import unittest
46
from test import support
57

@@ -78,6 +80,47 @@ def py_factorial(n):
7880
outer *= inner
7981
return outer << (n - count_set_bits(n))
8082

83+
# Reference implementations for primality testing.
84+
85+
def primes_below(n):
86+
"""List of the primes below n, by the sieve of Eratosthenes."""
87+
sieve = bytearray([1]) * n
88+
sieve[:2] = bytes(2)
89+
i = 2
90+
while i * i < n:
91+
if sieve[i]:
92+
sieve[i*i::i] = bytes(len(range(i*i, n, i)))
93+
i += 1
94+
return [i for i in range(n) if sieve[i]]
95+
96+
# The Miller-Rabin test with the first 13 primes as bases is known to be
97+
# exact for n < 3.3 * 10**24. More bases are used for larger inputs, for
98+
# which the test is probabilistic (but independent of the Baillie-PSW test
99+
# used in the implementation).
100+
MILLER_RABIN_BASES = primes_below(100)
101+
102+
def py_isprime(n):
103+
"""Miller-Rabin primality test, for cross-checking."""
104+
if n < 2:
105+
return False
106+
for p in MILLER_RABIN_BASES:
107+
if n % p == 0:
108+
return n == p
109+
d = n - 1
110+
s = (d & -d).bit_length() - 1
111+
d >>= s
112+
for a in MILLER_RABIN_BASES:
113+
x = pow(a, d, n)
114+
if x == 1 or x == n - 1:
115+
continue
116+
for _ in range(s - 1):
117+
x = x * x % n
118+
if x == n - 1:
119+
break
120+
else:
121+
return False
122+
return True
123+
81124

82125
class IntMathTests(unittest.TestCase):
83126
import math.integer as module
@@ -406,6 +449,246 @@ class MathTests(IntMathTests):
406449
import math as module
407450

408451

452+
# isprime() and primes() exist only in math.integer, not in math, so their
453+
# tests are not in IntMathTests (which is re-run against math above).
454+
455+
class IsPrimeTests(unittest.TestCase):
456+
import math.integer as module
457+
458+
def test_isprime_small(self):
459+
isprime = self.module.isprime
460+
sieve = set(primes_below(10**4))
461+
for n in range(-10, 10**4):
462+
with self.subTest(n=n):
463+
self.assertIs(isprime(n), n in sieve)
464+
465+
def test_isprime_negative(self):
466+
isprime = self.module.isprime
467+
self.assertIs(isprime(-1), False)
468+
self.assertIs(isprime(-2), False)
469+
self.assertIs(isprime(-3), False)
470+
self.assertIs(isprime(-10**100), False)
471+
472+
def test_isprime_carmichael(self):
473+
# Carmichael numbers are composite (A002997).
474+
isprime = self.module.isprime
475+
for n in [561, 1105, 1729, 2465, 2821, 6601, 8911, 10585, 15841,
476+
29341, 41041, 46657, 52633, 62745, 63973, 75361]:
477+
with self.subTest(n=n):
478+
self.assertIs(isprime(n), False)
479+
480+
def test_isprime_strong_pseudoprimes_base_2(self):
481+
# Composites that pass the strong probable prime test to base 2
482+
# (A001262); they must be caught by the strong Lucas test.
483+
isprime = self.module.isprime
484+
for n in [2047, 3277, 4033, 4681, 8321, 15841, 29341, 42799, 49141,
485+
52633, 65281, 74665, 80581, 85489, 88357, 90751,
486+
3825123056546413051]:
487+
with self.subTest(n=n):
488+
self.assertIs(isprime(n), False)
489+
490+
def test_isprime_strong_lucas_pseudoprimes(self):
491+
# Composites that pass the strong Lucas test with Selfridge's
492+
# parameters (A217255).
493+
isprime = self.module.isprime
494+
for n in [5459, 5777, 10877, 16109, 18971, 22499, 24569, 25199,
495+
40309, 58519, 75077, 97439]:
496+
with self.subTest(n=n):
497+
self.assertIs(isprime(n), False)
498+
499+
def test_isprime_base_divisors(self):
500+
# Divisors of the Miller-Rabin bases exercise the case where a
501+
# base is divisible by the tested number.
502+
isprime = self.module.isprime
503+
for base in [2, 7, 61, 325, 9375, 28178, 450775, 9780504,
504+
1795265022]:
505+
d = 1
506+
while d * d <= base:
507+
if base % d == 0:
508+
for n in [d, base // d]:
509+
with self.subTest(base=base, n=n):
510+
self.assertIs(isprime(n), py_isprime(n))
511+
d += 1
512+
513+
def test_isprime_perfect_squares(self):
514+
isprime = self.module.isprime
515+
for k in range(1000):
516+
with self.subTest(k=k):
517+
self.assertIs(isprime(k*k), False)
518+
for k in [2**31 - 1, 2**32 - 5]:
519+
with self.subTest(k=k):
520+
self.assertIs(isprime(k*k), False)
521+
522+
def test_isprime_word_boundaries(self):
523+
# Exercise the boundaries between the base sets.
524+
isprime = self.module.isprime
525+
for boundary in [2**32, 4759123141]:
526+
for n in range(boundary - 200, boundary + 200):
527+
with self.subTest(n=n):
528+
self.assertIs(isprime(n), py_isprime(n))
529+
for n in range(2**64 - 200, 2**64):
530+
with self.subTest(n=n):
531+
self.assertIs(isprime(n), py_isprime(n))
532+
self.assertIs(isprime(2**64 - 59), True) # largest prime < 2**64
533+
534+
def test_isprime_large_values(self):
535+
isprime = self.module.isprime
536+
self.assertIs(isprime(2**61 - 1), True) # Mersenne prime
537+
self.assertIs(isprime(2**62 - 1), False)
538+
self.assertIs(isprime(10**19), False)
539+
# Arguments not less than 2**64 are not supported.
540+
for n in [2**64, 2**64 + 13, 2**89 - 1, 10**100]:
541+
with self.subTest(n=n):
542+
self.assertRaises(OverflowError, isprime, n)
543+
544+
def test_isprime_random(self):
545+
isprime = self.module.isprime
546+
rng = random.Random(1729)
547+
for bits in [32, 34, 63, 64]:
548+
for _ in range(300):
549+
n = rng.getrandbits(bits)
550+
with self.subTest(n=n):
551+
self.assertIs(isprime(n), py_isprime(n))
552+
for bits in [65, 80, 128]:
553+
n = rng.getrandbits(bits) | (1 << (bits - 1))
554+
with self.subTest(n=n):
555+
self.assertRaises(OverflowError, isprime, n)
556+
557+
def test_isprime_integer_like(self):
558+
isprime = self.module.isprime
559+
self.assertIs(isprime(False), False)
560+
self.assertIs(isprime(True), False)
561+
self.assertIs(isprime(IntSubclass(7)), True)
562+
self.assertIs(isprime(IntSubclass(8)), False)
563+
self.assertIs(isprime(MyIndexable(97)), True)
564+
self.assertIs(isprime(MyIndexable(-97)), False)
565+
566+
def test_isprime_int_subclass_operators(self):
567+
# Overridden operators of an int subclass must not affect the
568+
# result.
569+
isprime = self.module.isprime
570+
self.assertIs(isprime(BadIntSubclass(97)), True)
571+
self.assertIs(isprime(BadIntSubclass(2**61 - 1)), True)
572+
self.assertIs(isprime(BadIntSubclass(2**62 - 1)), False)
573+
574+
def test_isprime_non_integers(self):
575+
isprime = self.module.isprime
576+
for value in [7.0, 7.5, Decimal('7'), Fraction(7, 1), '7', 7.5j]:
577+
with self.subTest(value=value):
578+
self.assertRaises(TypeError, isprime, value)
579+
self.assertRaises(TypeError, isprime)
580+
self.assertRaises(TypeError, isprime, 7, 11)
581+
582+
583+
class PrimesIterTests(unittest.TestCase):
584+
import math.integer as module
585+
586+
def test_primes(self):
587+
primes = self.module.primes
588+
expected = primes_below(10**4)
589+
self.assertEqual(list(primes(stop=10**4)), expected)
590+
self.assertEqual(len(expected), 1229)
591+
self.assertEqual(list(itertools.islice(primes(), 25)), expected[:25])
592+
593+
def test_primes_start(self):
594+
primes = self.module.primes
595+
for start in [-10**100, -100, -1, 0, 1, 2]:
596+
with self.subTest(start=start):
597+
self.assertEqual(list(primes(start, 10)), [2, 3, 5, 7])
598+
self.assertEqual(list(primes(3, 10)), [3, 5, 7])
599+
self.assertEqual(list(primes(4, 10)), [5, 7])
600+
self.assertEqual(list(primes(8, 12)), [11])
601+
self.assertEqual(list(primes(9, 12)), [11])
602+
self.assertEqual(list(primes(7, 8)), [7])
603+
604+
def test_primes_stop(self):
605+
primes = self.module.primes
606+
# The range is half-open.
607+
self.assertEqual(list(primes(2, 2)), [])
608+
self.assertEqual(list(primes(2, 3)), [2])
609+
self.assertEqual(list(primes(3, 3)), [])
610+
self.assertEqual(list(primes(7, 7)), [])
611+
self.assertEqual(list(primes(2, -10)), [])
612+
self.assertEqual(list(primes(10, 5)), [])
613+
self.assertEqual(list(primes(stop=0)), [])
614+
615+
def test_primes_unbounded(self):
616+
primes = self.module.primes
617+
it = primes()
618+
self.assertEqual([next(it) for _ in range(5)], [2, 3, 5, 7, 11])
619+
it = primes(10**6)
620+
self.assertEqual(next(it), 1000003)
621+
622+
def test_primes_huge(self):
623+
primes = self.module.primes
624+
boundary = 10**18
625+
expected = [n for n in range(boundary - 200, boundary + 200)
626+
if py_isprime(n)]
627+
self.assertEqual(list(primes(boundary - 200, boundary + 200)),
628+
expected)
629+
# The top of the supported range.
630+
expected = [n for n in range(2**64 - 200, 2**64) if py_isprime(n)]
631+
self.assertEqual(list(primes(2**64 - 200, 2**64 - 1)), expected)
632+
633+
def test_primes_overflow(self):
634+
primes = self.module.primes
635+
# The bounds must be less than 2**64.
636+
self.assertRaises(OverflowError, primes, 2**64)
637+
self.assertRaises(OverflowError, primes, 2**64 + 100, 2**64 + 200)
638+
self.assertRaises(OverflowError, primes, 0, 2**64)
639+
self.assertRaises(OverflowError, primes, 10**100)
640+
# An unbounded iterator raises when it runs out of the
641+
# supported range.
642+
it = primes(2**64 - 60)
643+
self.assertEqual(next(it), 2**64 - 59)
644+
self.assertRaises(OverflowError, next, it)
645+
self.assertRaises(StopIteration, next, it)
646+
647+
def test_primes_iterator_protocol(self):
648+
primes = self.module.primes
649+
it = primes(2, 10)
650+
self.assertIs(iter(it), it)
651+
self.assertEqual(list(it), [2, 3, 5, 7])
652+
# An exhausted iterator stays exhausted.
653+
self.assertEqual(list(it), [])
654+
self.assertRaises(StopIteration, next, it)
655+
# The iterator type cannot be instantiated directly.
656+
self.assertRaises(TypeError, type(primes()))
657+
658+
def test_primes_keywords(self):
659+
primes = self.module.primes
660+
self.assertEqual(list(primes(start=10, stop=30)), [11, 13, 17, 19, 23, 29])
661+
self.assertEqual(list(primes(10, stop=30)), [11, 13, 17, 19, 23, 29])
662+
663+
def test_primes_integer_like(self):
664+
primes = self.module.primes
665+
self.assertEqual(list(primes(True, 10)), [2, 3, 5, 7])
666+
self.assertEqual(list(primes(IntSubclass(3), IntSubclass(10))), [3, 5, 7])
667+
self.assertEqual(list(primes(MyIndexable(3), MyIndexable(10))), [3, 5, 7])
668+
# The yielded values are exact ints.
669+
for p in primes(IntSubclass(3), IntSubclass(10)):
670+
self.assertIs(type(p), int)
671+
672+
def test_primes_int_subclass_operators(self):
673+
# Overridden operators of an int subclass must not affect the
674+
# iteration.
675+
primes = self.module.primes
676+
self.assertEqual(list(primes(BadIntSubclass(3), BadIntSubclass(10))),
677+
[3, 5, 7])
678+
big = 10**18
679+
self.assertEqual(list(primes(BadIntSubclass(big), big + 100)),
680+
[n for n in range(big, big + 100) if py_isprime(n)])
681+
682+
def test_primes_non_integers(self):
683+
primes = self.module.primes
684+
self.assertRaises(TypeError, primes, 2.5)
685+
self.assertRaises(TypeError, primes, 2.5, 10)
686+
self.assertRaises(TypeError, primes, 2, 10.5)
687+
self.assertRaises(TypeError, primes, '2')
688+
self.assertRaises(TypeError, primes, 2, '10')
689+
self.assertRaises(TypeError, primes, 2, 10, 3)
690+
691+
409692
class MiscTests(unittest.TestCase):
410693

411694
def test_module_name(self):
@@ -416,6 +699,13 @@ def test_module_name(self):
416699
obj = getattr(math.integer, name)
417700
self.assertEqual(obj.__module__, 'math.integer')
418701

702+
def test_math_namespace(self):
703+
# New functions are added only to math.integer, not to math
704+
# (PEP 791).
705+
import math
706+
self.assertFalse(hasattr(math, 'isprime'))
707+
self.assertFalse(hasattr(math, 'primes'))
708+
419709

420710
if __name__ == '__main__':
421711
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :func:`math.integer.isprime` for primality testing of integers
2+
less than 2**64 and
3+
:func:`math.integer.primes` for iterating over prime numbers.

0 commit comments

Comments
 (0)