95

I am looking for an implementation or clear algorithm for getting the prime factors of N in either python, pseudocode or anything else well-readable. There are a few requirements/constraints:

  • N is between 1 and ~20 digits
  • No pre-calculated lookup table, memoization is fine though
  • Need not to be mathematically proven (e.g. could rely on the Goldbach conjecture if needed)
  • Need not to be precise, is allowed to be probabilistic/deterministic if needed

I need a fast prime factorization algorithm, not only for itself, but for usage in many other algorithms like calculating the Euler phi(n).

I have tried other algorithms from Wikipedia and such but either I couldn't understand them (ECM) or I couldn't create a working implementation from the algorithm (Pollard-Brent).

I am really interested in the Pollard-Brent algorithm, so any more information/implementations on it would be really nice.

Thanks!

EDIT

After messing around a little I have created a pretty fast prime/factorization module. It combines an optimized trial division algorithm, the Pollard-Brent algorithm, a miller-rabin primality test and the fastest primesieve I found on the internet. gcd is a regular Euclid's GCD implementation (binary Euclid's GCD is much slower then the regular one).

Bounty

Oh joy, a bounty can be acquired! But how can I win it?

  • Find an optimization or bug in my module.
  • Provide alternative/better algorithms/implementations.

The answer which is the most complete/constructive gets the bounty.

And finally the module itself:

import random

def primesbelow(N):
    # http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
    #""" Input N>=6, Returns a list of primes, 2 <= p < N """
    correction = N % 6 > 1
    N = {0:N, 1:N-1, 2:N+4, 3:N+3, 4:N+2, 5:N+1}[N%6]
    sieve = [True] * (N // 3)
    sieve[0] = False
    for i in range(int(N ** .5) // 3 + 1):
        if sieve[i]:
            k = (3 * i + 1) | 1
            sieve[k*k // 3::2*k] = [False] * ((N//6 - (k*k)//6 - 1)//k + 1)
            sieve[(k*k + 4*k - 2*k*(i%2)) // 3::2*k] = [False] * ((N // 6 - (k*k + 4*k - 2*k*(i%2))//6 - 1) // k + 1)
    return [2, 3] + [(3 * i + 1) | 1 for i in range(1, N//3 - correction) if sieve[i]]

smallprimeset = set(primesbelow(100000))
_smallprimeset = 100000
def isprime(n, precision=7):
    # http://en.wikipedia.org/wiki/Miller-Rabin_primality_test#Algorithm_and_running_time
    if n < 1:
        raise ValueError("Out of bounds, first argument must be > 0")
    elif n <= 3:
        return n >= 2
    elif n % 2 == 0:
        return False
    elif n < _smallprimeset:
        return n in smallprimeset


    d = n - 1
    s = 0
    while d % 2 == 0:
        d //= 2
        s += 1

    for repeat in range(precision):
        a = random.randrange(2, n - 2)
        x = pow(a, d, n)
    
        if x == 1 or x == n - 1: continue
    
        for r in range(s - 1):
            x = pow(x, 2, n)
            if x == 1: return False
            if x == n - 1: break
        else: return False

    return True

# https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/
def pollard_brent(n):
    if n % 2 == 0: return 2
    if n % 3 == 0: return 3

    y, c, m = random.randint(1, n-1), random.randint(1, n-1), random.randint(1, n-1)
    g, r, q = 1, 1, 1
    while g == 1:
        x = y
        for i in range(r):
            y = (pow(y, 2, n) + c) % n

        k = 0
        while k < r and g==1:
            ys = y
            for i in range(min(m, r-k)):
                y = (pow(y, 2, n) + c) % n
                q = q * abs(x-y) % n
            g = gcd(q, n)
            k += m
        r *= 2
    if g == n:
        while True:
            ys = (pow(ys, 2, n) + c) % n
            g = gcd(abs(x - ys), n)
            if g > 1:
                break

    return g

smallprimes = primesbelow(1000) # might seem low, but 1000*1000 = 1000000, so this will fully factor every composite < 1000000
def primefactors(n, sort=False):
    factors = []

    for checker in smallprimes:
        while n % checker == 0:
            factors.append(checker)
            n //= checker
        if checker > n: break

    if n < 2: return factors

    while n > 1:
        if isprime(n):
            factors.append(n)
            break
        factor = pollard_brent(n) # trial division did not fully factor, switch to pollard-brent
        factors.extend(primefactors(factor)) # recurse to factor the not necessarily prime factor returned by pollard-brent
        n //= factor

    if sort: factors.sort()

    return factors

def factorization(n):
    factors = {}
    for p1 in primefactors(n):
        try:
            factors[p1] += 1
        except KeyError:
            factors[p1] = 1
    return factors

totients = {}
def totient(n):
    if n == 0: return 1

    try: return totients[n]
    except KeyError: pass

    tot = 1
    for p, exp in factorization(n).items():
        tot *= (p - 1)  *  p ** (exp - 1)

    totients[n] = tot
    return tot

def gcd(a, b):
    if a == b: return a
    while b > 0: a, b = b, a % b
    return a

def lcm(a, b):
    return abs((a // gcd(a, b)) * b)
Adam Smooch
  • 717
  • 8
  • 25
orlp
  • 106,415
  • 33
  • 201
  • 300
  • 1
    @wheaties - that would be what the `while checker*checker <= num` is there for. – Amber Jan 10 '11 at 04:30
  • You might find this thread useful: http://stackoverflow.com/questions/1024640/calculating-phik-for-1kn/1134851#1134851 – RBarryYoung Jan 18 '11 at 05:51
  • 1
    Why aren't things like this available in the standard library? When I search, all I find is a million Project Euler solution proposals, and other people pointing out flaws in them. Isn't this what libraries and bug reports are for? – endolith Sep 30 '13 at 23:15
  • @endolith Outside of things like Prject Euler there aren't much uses for this. Certainly not enough to put it in the standard libraries. – orlp Oct 01 '13 at 01:19
  • 1
    @nightcracker: There's no practical use for factoring numbers?? – endolith Oct 01 '13 at 01:20
  • @endolith There's not practical use for factoring numbers of the size we actually can factor. If you manage to find an algorithm for factoring huge numbers, that's a different story (and the only reason why that would be "useful" is to break assymetric encryption, and that use would soon be diminished because people will not use the encryption anymore). So no, there's no real practical use. – orlp Oct 01 '13 at 01:43
  • @nightcracker: oh I found it in another library at least: http://docs.sympy.org/dev/modules/ntheory.html#sympy.ntheory.factor_.factorint – endolith Oct 01 '13 at 03:00
  • Since you've already factored out small primes it is unnecessary to test for 2 and 3 in pollard_brent. Also you can change the implementation of Rabin-Miller to get deterministic(!) test all numbers below 2^64. It will still need seven iterations but it will be deterministic. See http://miller-rabin.appspot.com/ – Vít Tuček Oct 03 '13 at 12:58
  • "*The Magic Words are Squeamish Ossifrage*". If the history interests you, you might enjoy reading https://en.wikipedia.org/wiki/RSA_Factoring_Challenge – Colonel Panic Aug 16 '16 at 08:59
  • Here's a 20 digit (63 bit) semiprime to test your factorisation code: 8876044532898802067 – Colonel Panic Aug 16 '16 at 13:59
  • Bonus 40 digit (130 bit) semiprime: 2630492240413883318777134293253671517529. This took a few hours to factor using [`sympy.ntheory.factorint`](http://stackoverflow.com/a/31986424/284795) – Colonel Panic Aug 16 '16 at 15:32
  • Is there any similar one for `php`? – Vahid Najafi Feb 05 '17 at 21:21

7 Answers7

98

If you don't want to reinvent the wheel, use the library sympy

pip install sympy

Use the function sympy.ntheory.factorint

Given a positive integer n, factorint(n) returns a dict containing the prime factors of n as keys and their respective multiplicities as values. For example:

Example:

>>> from sympy.ntheory import factorint
>>> factorint(10**20+1)
{73: 1, 5964848081: 1, 1676321: 1, 137: 1}

You can factor some very large numbers:

>>> factorint(10**100+1)
{401: 1, 5964848081: 1, 1676321: 1, 1601: 1, 1201: 1, 137: 1, 73: 1, 129694419029057750551385771184564274499075700947656757821537291527196801: 1}
Colonel Panic
  • 126,394
  • 80
  • 383
  • 450
18

There is no need to calculate smallprimes using primesbelow, use smallprimeset for that.

smallprimes = (2,) + tuple(n for n in xrange(3,1000,2) if n in smallprimeset)

Divide your primefactors into two functions for handling smallprimes and other for pollard_brent, this can save a couple of iterations as all the powers of smallprimes will be divided from n.

def primefactors(n, sort=False):
    factors = []

    limit = int(n ** .5) + 1
    for checker in smallprimes:
        print smallprimes[-1]
        if checker > limit: break
        while n % checker == 0:
            factors.append(checker)
            n //= checker


    if n < 2: return factors
    else : 
        factors.extend(bigfactors(n,sort))
        return factors

def bigfactors(n, sort = False):
    factors = []
    while n > 1:
        if isprime(n):
            factors.append(n)
            break
        factor = pollard_brent(n) 
        factors.extend(bigfactors(factor,sort)) # recurse to factor the not necessarily prime factor returned by pollard-brent
        n //= factor

    if sort: factors.sort()    
    return factors

By considering verified results of Pomerance, Selfridge and Wagstaff and Jaeschke, you can reduce the repetitions in isprime which uses Miller-Rabin primality test. From Wiki.

  • if n < 1,373,653, it is enough to test a = 2 and 3;
  • if n < 9,080,191, it is enough to test a = 31 and 73;
  • if n < 4,759,123,141, it is enough to test a = 2, 7, and 61;
  • if n < 2,152,302,898,747, it is enough to test a = 2, 3, 5, 7, and 11;
  • if n < 3,474,749,660,383, it is enough to test a = 2, 3, 5, 7, 11, and 13;
  • if n < 341,550,071,728,321, it is enough to test a = 2, 3, 5, 7, 11, 13, and 17.

Edit 1: Corrected return call of if-else to append bigfactors to factors in primefactors.

Rozuur
  • 4,025
  • 24
  • 30
  • Enjoy your +100 (you're the only one who answered since the bounty). Your `bigfactors` is horribly inefficient though, because `factors.extend(bigfactors(factor))` recurses back to bigfactors which is just plain wrong (what if pollard-brent finds the factor 234892, you don't want to factorize that with pollard-brent again). If you change `factors.extend(bigfactors(factor))` to `factors.extend(primefactors(factor, sort))` then it's fine. – orlp Jan 19 '11 at 21:22
  • One primefactors calls bigfactors then its clear that there is will no power of small prime in the next factors obtained by pollard-brent. – Rozuur Jan 20 '11 at 04:28
  • If its inefficient I would not have answered this. Once call goes from primefactors to bigfactors there will be no factor in n which is lessthan 1000 hence pollard-brent cannot return a number whose factors will be lessthan 1000. If its not clear, reply such that i will edit my answer with more explanations – Rozuur Jan 20 '11 at 05:05
  • Shit NVM, ofcourse. If N doesn't contain any factors found by smallprimes then factor F of N won't either >. – orlp Jan 20 '11 at 16:10
  • Also you should use smallprimeset instead of smallprime and remove smallprimeset from miller-rabin. – Rozuur Jan 20 '11 at 16:40
  • @Rozuur, I didn't get your last comment. Do you mean I should use `smallprimeset` in `primefactors`? I did get the part of miller-rabin though (replace it with the Pomerance, Selfridge and Wagstaff and Jaeschke data). – orlp Jan 22 '11 at 12:57
  • As the numbers are 10**20 you should use `smallprimeset` in primefactors and remove if statement which returns `n in smallprimeset`. – Rozuur Jan 23 '11 at 07:56
  • 1
    Your `primefactors` is really inefficient because `bigfactors` is called for all numbers with small factors where the biggest factor greather than 2. This is because the last factor is skipped due to `n > int(n ** .5) + 1`. It can be easy fixed before calling bigfactorc by `if n in smallprimes:` ` factors.append(n)` `else:` ` ...bigfactors...`. The second bug is that `return any_list.extend(...)` returns None. It can be also easy fixed. – hynekcer Dec 22 '12 at 19:58
  • Well I haven't changed the `primefactors` implementation much from the given code. Nyways it was a great find. – Rozuur Dec 24 '12 at 09:41
7

Even on the current one, there are several spots to be noticed.

  1. Don't do checker*checker every loop, use s=ceil(sqrt(num)) and checher < s
  2. checher should plus 2 each time, ignore all even numbers except 2
  3. Use divmod instead of % and //
bragboy
  • 33,764
  • 30
  • 109
  • 168
Kabie
  • 10,108
  • 1
  • 35
  • 44
  • 1
    I need to do checker*checker because num decreases constantly. I'll implement the even numbers skip though. The divmod decreases the function a lot (it will calculate the // on every loop, instead of only when checker divides n). – orlp Jan 10 '11 at 04:40
  • @night, you just need to recalcuate `s` whenever you alter `num` then – John La Rooy Jan 10 '11 at 04:55
  • True, figured that while messing around :) Seems to be faster to recalculate sqrt then checker*checker. – orlp Jan 10 '11 at 05:05
  • @nightcracker: Let `N=n*n+1`, `ceil(sqrt(N))` cost about 2~4 times than `n*n`, `num` does not change that frequently. – Kabie Jan 10 '11 at 05:11
  • Do you know of an ceil/floor sqrt algorithm, because int(num ** .5) + 1 seems to be overkill (first calculate in float precision and then chop off). – orlp Jan 10 '11 at 05:15
  • 1
    Multiplication is faster than square-root, and Python has difficulty taking the square-root of bignums anyway. I get this error with the current version of the code: `limit = int(n ** .5) + 1` ... `OverflowError: long int too large to convert to float` – Quuxplusone Oct 27 '14 at 08:22
  • 4. write it in C/Fortran and link it as a Python library, not in native Python. :-D – user26742873 Jul 27 '21 at 15:12
5

There's a python library with a collection of primality tests (including incorrect ones for what not to do). It's called pyprimes. Figured it's worth mentioning for posterity's purpose. I don't think it includes the algorithms you mentioned.

Ehtesh Choudhury
  • 7,182
  • 5
  • 42
  • 47
5

You should probably do some prime detection which you could look here, Fast algorithm for finding prime numbers?

You should read that entire blog though, there is a few algorithms that he lists for testing primality.

Community
  • 1
  • 1
milkypostman
  • 2,865
  • 24
  • 22
2

You could factorize up to a limit then use brent to get higher factors

from fractions import gcd
from random import randint

def brent(N):
   if N%2==0: return 2
   y,c,m = randint(1, N-1),randint(1, N-1),randint(1, N-1)
   g,r,q = 1,1,1
   while g==1:             
       x = y
       for i in range(r):
          y = ((y*y)%N+c)%N
       k = 0
       while (k<r and g==1):
          ys = y
          for i in range(min(m,r-k)):
             y = ((y*y)%N+c)%N
             q = q*(abs(x-y))%N
          g = gcd(q,N)
          k = k + m
       r = r*2
   if g==N:
       while True:
          ys = ((ys*ys)%N+c)%N
          g = gcd(abs(x-ys),N)
          if g>1:  break
   return g

def factorize(n1):
    if n1==0: return []
    if n1==1: return [1]
    n=n1
    b=[]
    p=0
    mx=1000000
    while n % 2 ==0 : b.append(2);n//=2
    while n % 3 ==0 : b.append(3);n//=3
    i=5
    inc=2
    while i <=mx:
       while n % i ==0 : b.append(i); n//=i
       i+=inc
       inc=6-inc
    while n>mx:
      p1=n
      while p1!=p:
          p=p1
          p1=brent(p)
      b.append(p1);n//=p1 
    if n!=1:b.append(n)   
    return sorted(b)

from functools import reduce
#n= 2**1427 * 31 #
n= 67898771  * 492574361 * 10000223 *305175781* 722222227*880949 *908909
li=factorize(n)
print (li)
print (n - reduce(lambda x,y :x*y ,li))
Antoni Gual Via
  • 692
  • 1
  • 6
  • 12
0

I just ran into a bug in this code when factoring the number 2**1427 * 31.

  File "buckets.py", line 48, in prettyprime
    factors = primefactors.primefactors(n, sort=True)
  File "/private/tmp/primefactors.py", line 83, in primefactors
    limit = int(n ** .5) + 1
OverflowError: long int too large to convert to float

This code snippet:

limit = int(n ** .5) + 1
for checker in smallprimes:
    if checker > limit: break
    while n % checker == 0:
        factors.append(checker)
        n //= checker
        limit = int(n ** .5) + 1
        if checker > limit: break

should be rewritten into

for checker in smallprimes:
    while n % checker == 0:
        factors.append(checker)
        n //= checker
    if checker > n: break

which will likely perform faster on realistic inputs anyway. Square root is slow — basically the equivalent of many multiplications —, smallprimes only has a few dozen members, and this way we remove the computation of n ** .5 from the tight inner loop, which is certainly helpful when factoring numbers like 2**1427. There's simply no reason to compute sqrt(2**1427), sqrt(2**1426), sqrt(2**1425), etc. etc., when all we care about is "does the [square of the] checker exceed n".

The rewritten code doesn't throw exceptions when presented with big numbers, and is roughly twice as fast according to timeit (on sample inputs 2 and 2**718 * 31).

Also notice that isprime(2) returns the wrong result, but this is okay as long as we don't rely on it. IMHO you should rewrite the intro of that function as

if n <= 3:
    return n >= 2
...
Quuxplusone
  • 21,683
  • 6
  • 86
  • 145