Fast prime factorization module
Asked Answered
L

9

104

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):
    # https://mcmap.net/q/86560/-fastest-way-to-list-all-primes-below-n/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)
Lignin answered 10/1, 2011 at 4:19 Comment(12)
@wheaties - that would be what the while checker*checker <= num is there for.Transfusion
You might find this thread useful: https://mcmap.net/q/211675/-calculating-phi-k-for-1-lt-k-lt-n/…Winona
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?Siddon
@Siddon Outside of things like Prject Euler there aren't much uses for this. Certainly not enough to put it in the standard libraries.Lignin
@nightcracker: There's no practical use for factoring numbers??Siddon
@Siddon 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.Lignin
@nightcracker: oh I found it in another library at least: docs.sympy.org/dev/modules/…Siddon
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 miller-rabin.appspot.comOrwin
"The Magic Words are Squeamish Ossifrage". If the history interests you, you might enjoy reading en.wikipedia.org/wiki/RSA_Factoring_ChallengeMikael
Here's a 20 digit (63 bit) semiprime to test your factorisation code: 8876044532898802067Mikael
Bonus 40 digit (130 bit) semiprime: 2630492240413883318777134293253671517529. This took a few hours to factor using sympy.ntheory.factorintMikael
Is there any similar one for php?Humoral
M
112

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}
Mikael answered 13/8, 2015 at 11:3 Comment(2)
Thanks for sharing this, @Colonel Panic . This is exactly what I was looking for : an integer factorization module in a well-maintained library, rather than snippets of code.Mirella
Yes, sympy is simple!Burgos
S
19

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.

Simar answered 19/1, 2011 at 19:33 Comment(9)
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.Lignin
One primefactors calls bigfactors then its clear that there is will no power of small prime in the next factors obtained by pollard-brent.Simar
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 explanationsSimar
Shit NVM, ofcourse. If N doesn't contain any factors found by smallprimes then factor F of N won't either >.<Lignin
Also you should use smallprimeset instead of smallprime and remove smallprimeset from miller-rabin.Simar
@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).Lignin
As the numbers are 10**20 you should use smallprimeset in primefactors and remove if statement which returns n in smallprimeset.Simar
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.Overblouse
Well I haven't changed the primefactors implementation much from the given code. Nyways it was a great find.Simar
B
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 //
Bear answered 10/1, 2011 at 4:34 Comment(7)
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).Lignin
@night, you just need to recalcuate s whenever you alter num thenBrooking
True, figured that while messing around :) Seems to be faster to recalculate sqrt then checker*checker.Lignin
@nightcracker: Let N=n*n+1, ceil(sqrt(N)) cost about 2~4 times than n*n, num does not change that frequently.Bear
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).Lignin
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 floatPyralid
4. write it in C/Fortran and link it as a Python library, not in native Python. :-DReduced
I
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.

Intellection answered 10/1, 2011 at 4:33 Comment(0)
C
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.

Crawford answered 9/10, 2013 at 2:24 Comment(0)
N
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))
Nobukonoby answered 20/4, 2015 at 9:30 Comment(0)
M
1

Very interesting question, thanks!

Today I decided to implement for you from scratch Elliptic Curve ECM factorization method in Python.

I tried to make clean and easy understandable code. You can just copy-paste whole function FactorECM() from my code into your code and it will work without any other tweaking or dependencies.

Due to some simplifications in the code algorithm is NOT highly optimized, e.g. it doesn't use multiprocessing module to use processes and all CPU cores. Basically my algorithm is single core for single factored number.

I used following sub-algorithms in my code: Trial Division Factorization, Fermat Probability Test, Sieve of Eratosthenes (prime numbers generator), Euclidean Algorithm (computing Greatest Common Divisor, GCD), Extended Euclidean Algorithm (GCD with Bezu coefficients), Modular Multiplicative Inverse, Right-to-Left Binary Exponentation (for elliptic point multiplication), Elliptic Curve Arithmetics (point addition and multiplication), Lenstra Elliptic Curve Factorization.

NOTE. You can speedup my code 2.5x times if you install gmpy2 module through python -m pip install gmpy2 command line. But don't forget to uncomment line #import gmpy2 in my code, if uncommented then 2.5x boost will be applied. I used gmpy2 for its considerably faster implementation of gmpy2.gcdext() functions, which implements Extended Euclidean Algorithm needed for Modular Multiplicative Inverse.

ECM algorithm in general, if well optimized (for example written in C++), is capable of factoring quite big numbers, even hardest 100-bit number (30 digits), consisting of two 50-bit primes, can be factored within several seconds.

My algorithm is written according to ECM in Wikipedia and is as following:

  1. Check if number is smaller than 2^16, then factor it through Trial Division method. Return result.

  2. Check if number is probably prime with high condifence, for that I use Fermat Test with 32 trials. If number is primes return it as only factor.

  3. Generate curve parameters A, X, Y randomly and derive B from curve equation Y^2 = X^3 + AX + B (mod N). Check if curve is OK, value 4 * A ** 3 - 27 * B ** 2 should be non-zero.

  4. Generate small primes through Sieve of Eratosthenes, primes below our Bound. Each prime raise to some small power, this raised prime would be called K. I do raising into power while it is smaller than some Bound2, which is Sqrt(Bound) in my case.

  5. Compute elliptic point multiplication P = k * P, where K taken from previous step and P is (X, Y). Compute according to Elliptic Curve Arithmetics Wiki.

  6. Point multiplication uses Modular Multiplicative Inverse, which computes GCD(SomeValue, N) according to Extended Euclidean Algorithm Wiki. If this GCD is not 1, then it gives non-trivial factor of N. Collect this factor, remove it from number and re-run ECM algorithm (steps 1.-6. above) for remaining number.

  7. If all primes till Bound were multiplied and gave no factor then re-run ECM factorization algorithm (steps 1.-6. above) again with another random curve and bigger Bound. In my code I take new bound by adding 512 to old bound.

See example of usage of my factoring function inside Test() function. After code below is located example console output for factoring hardest 72-bit random number (consisting of two 36-bit primes). If you want different size of number than modify bits = 72 in my code to your desired bit size of input random number.

Try it online!

def FactorECM(N0, *, verbose = False):
    # https://en.wikipedia.org/wiki/Lenstra_elliptic-curve_factorization
    import math, random, time; gmpy2 = None
    #import gmpy2
    def Factor_TrialDiv(x):
        # https://en.wikipedia.org/wiki/Trial_division
        fs = []
        while (x & 1) == 0:
            fs.append(2)
            x >>= 1
        for d in range(3, x + 1, 2):
            if d * d > x:
                break
            while x % d == 0:
                fs.append(d)
                x //= d
        if x > 1:
            fs.append(x)
        return sorted(fs)
    def IsProbablyPrime_Fermat(n, trials = 32):
        # https://en.wikipedia.org/wiki/Fermat_primality_test
        for i in range(trials):
            if pow(random.randint(2, n - 2), n - 1, n) != 1:
                return False
        return True
    def GenPrimes_SieveOfEratosthenes(end):
        # https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
        composite = [False] * end
        for p in range(2, int(math.sqrt(end) + 3)):
            if composite[p]:
                continue
            for i in range(p * p, end, p):
                composite[i] = True
        for p in range(2, end):
            if not composite[p]:
                yield p
    def GCD(a, b):
        # https://en.wikipedia.org/wiki/Euclidean_algorithm
        while b != 0:
            a, b = b, a % b
        return a
    def EGCD(a, b):
        # https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
        if gmpy2 is None:
            ro, r, so, s = a, b, 1, 0
            while r != 0:
                ro, (q, r) = r, divmod(ro, r)
                so, s = s, so - q * s
            return ro, so, (ro - so * a) // b
        else:
            return tuple(map(int, gmpy2.gcdext(a, b)))
    def ModularInverse(a, n):
        # https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
        g, s, t = EGCD(a, n)
        if g != 1:
            raise ValueError(a)
        return s % n
    def EllipticCurveAdd(N, A, B, X0, Y0, X1, Y1):
        # https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication
        if X0 == X1 and Y0 == Y1:
            # Double
            l = ((3 * X0 ** 2 + A) * ModularInverse(2 * Y0, N)) % N
            x = (l ** 2 - 2 * X0) % N
            y = (l * (X0 - x) - Y0) % N
        else:
            # Add
            l = ((Y1 - Y0) * ModularInverse(X1 - X0, N)) % N
            x = (l ** 2 - X0 - X1) % N
            y = (l * (X0 - x) - Y0) % N
        return x, y
    def EllipticCurveMul(N, A, B, X, Y, k):
        # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method
        assert k >= 2, k
        k -= 1
        BX, BY = X, Y
        while k != 0:
            if k & 1:
                X, Y = EllipticCurveAdd(N, A, B, X, Y, BX, BY)
            BX, BY = EllipticCurveAdd(N, A, B, BX, BY, BX, BY)
            k >>= 1
        return X, Y
    
    bound_start = 1 << 9
    
    def Main(N, *, bound = bound_start, icurve = 0):
        def NextFactorECM(x):
            return Main(x, bound = bound + bound_start, icurve = icurve + 1)
        def PrimePow(p, *, bound2 = int(math.sqrt(bound) + 1.01)):
            mp = p
            while True:
                mp *= p
                if mp >= bound2:
                    return mp // p
        
        if N < (1 << 16):
            fs = Factor_TrialDiv(N)
            if verbose and len(fs) >= 2:
                print('Factors from TrialDiv:', fs, flush = True)
            return fs
        
        if IsProbablyPrime_Fermat(N):
            return [N]
        
        if verbose:
            print(f'Curve {icurve:>4},  bound 2^{math.log2(bound):>7.3f}', flush = True)
        
        while True:
            X, Y, A = [random.randrange(N) for i in range(3)]
            B = (Y ** 2 - X ** 3 - A * X) % N
            if 4 * A ** 3 - 27 * B ** 2 == 0:
                continue
            break
        
        for p in GenPrimes_SieveOfEratosthenes(bound):
            k = PrimePow(p)
            try:
                X, Y = EllipticCurveMul(N, A, B, X, Y, k)
            except ValueError as ex:
                g = GCD(ex.args[0], N)
                assert g > 1, g
                if g != N:
                    if verbose:
                        print('Factor from ECM:', g, flush = True)
                    return sorted(NextFactorECM(g) + NextFactorECM(N // g))
                else:
                    return NextFactorECM(N)
        
        return NextFactorECM(N)
    
    if verbose:
        print(f'ECM factoring N: {N0} (2^{math.log2(max(1, N0)):.2f})', flush = True)
        tb = time.time()
        fs = Main(N0)
        tb = time.time() - tb
        print(f'Factored N {N0}: {fs}')
        print(f'Time {tb:.3f} sec.', flush = True)
        return fs
    else:
        return Main(N0)

def Test():
    import random
    def IsProbablyPrime_Fermat(n, trials = 32):
        for i in range(trials):
            if pow(random.randint(2, n - 2), n - 1, n) != 1:
                return False
        return True
    def RandPrime(bits):
        while True:
            p = random.randrange(1 << (bits - 1), 1 << bits) | 1
            if IsProbablyPrime_Fermat(p):
                return p
    bits = 72
    N = RandPrime((bits + 1) // 2) * RandPrime(bits // 2) * random.randrange(1 << 16)
    FactorECM(N, verbose = True)

if __name__ == '__main__':
    Test()

Example console output:

ECM factoring N: 25005272280974861996134424 (2^84.37)
Curve    0,  bound 2^  9.000
Factor from ECM: 2
Curve    1,  bound 2^ 10.000
Factor from ECM: 4
Factors from TrialDiv: [2, 2]
Curve    2,  bound 2^ 10.585
Factor from ECM: 1117
Curve    3,  bound 2^ 11.000
Curve    4,  bound 2^ 11.322
Curve    5,  bound 2^ 11.585
Curve    6,  bound 2^ 11.807
Factor from ECM: 54629318837
Factored N 25005272280974861996134424: [2, 2, 2, 1117, 51222720707, 54629318837]
Time 0.281 sec.
Materialism answered 23/3, 2023 at 6:33 Comment(0)
P
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
...
Pyralid answered 27/10, 2014 at 8:29 Comment(0)
Y
0

At most 1 of the factors is bigger than sqrt(n), so all methods that test numbers up to n are too slow. Test up to sqrt(n), and check the last factor separately.

def prime_factors(n: int) -> dict:
    factors = {}
    i = 2
    while i * i <= n:
        if not n % i:
            factors[i] = factors.get(i, 0) + 1
            n //= i
        else:
            i += 1
    factors[n] = factors.get(n, 0) + 1
    return factors
Yangyangtze answered 27/1 at 18:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.