FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

bpo-40028: Add is_prime, factorise, previous_prime and next_prime by remilapeyre · Pull Request #19918 · python/cpython · GitHub

Repository navigation

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (2) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
197 changes: 197 additions & 0 deletions Lib/imath.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
from math import gcd
from random import randrange


def _miller_rabin_witness(a, u, t, n):
"""Return True if a witness is found for n, we are sure that n is composite."""
a %= n
if a <= 1:
return False

x = pow(a, u, n)
if x == 1 or x == n-1:
return False
for _ in range(t-1):
x = pow(x, 2, n)
if x == n-1:
return False
return True

def _miller_rabin(bases, n):
"""Perform a Miller-Rabin primality test of an odd integer n."""
q, t = n-1, 0
while q & 1 == 0:
t += 1
q >>= 1
u = (n-1)//(2**t)

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Is there a reason to recompute u here when its value should be the same as q?


return not any(
_miller_rabin_witness(a, u, t, n)
for a in bases
)


def is_prime(n: int, s: int=25) -> bool:
"""Returns whether n is prime.

A deterministic version of the Miller-Rabin primality test is used for smalls
n. For large n, a probabilistic Miller-Rabin test is used and n is either
known to be composite when is_prime(n) returns True, or probably prime with
with a probability of failure of at most 4^(−s).

>>> is_prime(17)
True
>>> is_prime(121)
False
"""

if n <= 1:
return False
if n%2 == 0:
return (n == 2)

# If n is small with test we use a deterministic Miller-Rabin test,
# see https://miller-rabin.appspot.com/ and
# https://arxiv.org/pdf/1509.00864.pdf
if n < 341531:
bases = [9345883071009581737]
elif n < 1050535501:
bases = [336781006125, 9639812373923155]
elif n < 350269456337:
bases = [4230279247111683200, 14694767155120705706, 16641139526367750375]
elif n < 55245642489451:
bases = [2, 141889084524735, 1199124725622454117, 11096072698276303650]
elif n < 7999252175582851:
bases = [2, 4130806001517, 149795463772692060, 186635894390467037, 3967304179347715805]
elif n < 585226005592931977:
bases = [2, 123635709730000, 9233062284813009, 43835965440333360, 761179012939631437, 1263739024124850375]
elif n < 18446744073709551616:
bases = [2, 325, 9375, 28178, 450775, 9780504, 1795265022]
elif n < 318665857834031151167461:
bases = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
elif n < 3317044064679887385961981:
bases = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]
else:
bases = (randrange(2, n) for _ in range(s))

return _miller_rabin(bases, n)


def next_prime(n: int) -> int:
"""Returns the smallest prime number greater than n.

>>> next_prime(6)
7
"""
if n < 0:
n = 0
if n < 10:
return [2, 2, 3, 5, 5, 7, 7, 11, 11, 11][n]

if 1 <= n % 6 <= 4:
n += 5 - (n%6)
if is_prime(n):
return n

if n % 6 == 0:
n += 1
else:
n += 2

while True:
if is_prime(n):
return n
n += 4
if is_prime(n):
return n
n += 2


def previous_prime(n: int) -> int:
"""Returns the largest prime number less than n.

>>> previous_prime(23)
19
"""
if n <= 2:
raise ValueError(f'There is no prime number less than {n}.')

if n < 10:
return [0, 0, 0, 2, 3, 3, 5, 5, 7, 7, 7][n]

if 1 < n % 6 <= 5:
n -= (n%6) - 1
if is_prime(n):
return n

if n % 6 == 0:
n -= 1
else:
n -= 2

while True:
if is_prime(n):
return n
n -= 4
if is_prime(n):
return n
n -= 2


def _rho_pollard(n):

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The following should be exactly the same algorithm, just refactored a little bit, and it runs a little faster for a few tests by my measurements.

def _rho_pollard(n):
    # From https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm
    # and https://en.wikipedia.org/wiki/Cycle_detection#Brent.27s_algorithm
    tortoise = hare = 2
    power = 1
    _gcd = gcd
    _repeat = itertools.repeat
    while True:
        # Double the length of the run each time
        for _ in _repeat(None, power):
            hare = (hare**2 + 1) % n
            d = _gcd(hare-tortoise, n)
            if d == 1:
                continue
            elif d == n:
                # failure; start another
                tortoise = hare = randrange(n)
                power = 1
                break
            else:
                return d
        else: # no break
            tortoise = hare
            power *= 2

Changes (feel free to pick and choose as you desire):

  • starting at 2 rather than random seems to do good things
  • Limit the Python int comparisons to one per inner loop
    • Factor out the inner loop as a repeat (a little faster than range()), making c code do the comparisons
  • making _gcd and _repeat local variables helps a little.

x = y = randrange(n)
i = k = 2

while True:
x = (x**2 - 1) % n
d = gcd(y-x, n)
if d == n:
# break the loop
x = y = randrange(n)
i = k = 2
elif d != 1:
return d

if i == k:
y = x
k *= 2
i += 1


def _factorise(n):

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Something like this would help to not exceed maximum recursion depth:

def _factorise(n):
    stack = [n]
    while stack:
        n = stack.pop()
        if is_prime(n):
            yield n
        else:
            d = _rho_pollard(n)
            stack += [n//d, d]

_factorise could perhaps then be in-lined, since it seems like factorise doesn't do too much on its own.
This then passes the test set(factorise(2**10000)) == {2}.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thanks!

stack = [n]
while stack:
n = stack.pop()
if is_prime(n):
yield n
else:
d = _rho_pollard(n)
stack += [n//d, d]

def factorise(n: int):
"""Factorise an integer n using Pollard's rho algorithm.

Returns an iterator of all prime divisors of n repeated so that
prod(factorise(n)) == n.

If n is less than 0, -1 is the first element of the result.

Example:
>>> sorted(factorise(1729))
[7, 13, 19]
>>> sorted(factorise(-21))
[-1, 3, 7]
"""
if n == 0:
raise ValueError("Cannot factorise 0.")
elif n < 0:
yield -1
n *= -1

if n == 1:
return

yield from _factorise(n)


__all__ = ['is_prime', 'next_prime', 'previous_prime', 'factorise']
61 changes: 61 additions & 0 deletions Lib/test/test_imath.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import unittest
import imath
import math


class TestPrimes(unittest.TestCase):
small_primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97)

def test_is_prime(self):
primes = list(filter(imath.is_prime, range(100)))
self.assertSequenceEqual(primes, self.small_primes)
self.assertFalse(imath.is_prime(2**800+1))
self.assertFalse(imath.is_prime(2**900+1))
self.assertFalse(imath.is_prime(2**1000+1))
self.assertFalse(imath.is_prime(2**(2**8)+1))

def test_next_prime(self):
self.assertEqual(imath.next_prime(-1), 2)
for start, end in zip(self.small_primes, self.small_primes[1:]):
for n in range(start, end):
self.assertEqual(imath.next_prime(n), end)


def test_previous_prime(self):
for start, end in zip(self.small_primes, self.small_primes[1:]):
for n in range(start+1, end+1):
self.assertEqual(imath.previous_prime(n), start)

for n in (-1, 2):
with self.assertRaisesRegex(ValueError, 'no prime number'):
imath.previous_prime(n)

def test_factorize(self):
with self.assertRaisesRegex(ValueError, 'Cannot factorise 0.'):
next(imath.factorise(0))

# Getting a first factor is fast
n = 2**800+1
d = next(imath.factorise(n))
self.assertEqual(n%d, 0)

for n in range(1, 100):
with self.subTest(n=n):
factors = list(imath.factorise(n))
self.assertEqual(math.prod(factors), n)
self.assertTrue(all(map(imath.is_prime, factors)))

factors = list(imath.factorise(-n))
self.assertEqual(math.prod(factors), -n)
self.assertEqual(factors[0], -1)
self.assertTrue(all(map(imath.is_prime, factors[1:])))

# Numbers with many factors can be factorised
self.assertEqual(set(imath.factorise(2**10000)), {2})


def load_tests(loader, tests, pattern):
from doctest import DocTestSuite
tests.addTest(DocTestSuite(imath))
return tests

Back | FazBrowse Home | New Git URL