-4

I need to do factorials for quite large numbers that will take some time. How can I determine how far through working out the factorial the function is?

Lyrenhex
  • 92
  • 2
  • 8
  • 1
    Are you just running `math.factorial`? –  Jun 28 '15 at 14:18
  • if you do not need the exact integer solution, try the [stirling approximation](https://en.wikipedia.org/wiki/Stirling%27s_approximation). – hiro protagonist Jun 28 '15 at 14:18
  • @hiroprotagonist I do need the exact solution. – Lyrenhex Jun 28 '15 at 14:20
  • @tristan Yes, but I'm also using Prime Factorisation for larger numbers. – Lyrenhex Jun 28 '15 at 14:21
  • How large are your "quite large" numbers? – dlask Jun 28 '15 at 14:23
  • Correction: I'm not using `math.factorial`. – Lyrenhex Jun 28 '15 at 14:26
  • @dlask around 5 million and larger... – Lyrenhex Jun 28 '15 at 14:27
  • If you're not using `math.factorial` what _are_ you using? FWIW, in Python 3 `math.factorial` uses the [divide-and-conquer factorial algorithm](http://hg.python.org/cpython/file/d42f264f291e/Modules/mathmodule.c#l1218), so it's much faster than using a simple multiplication loop. There's a bit of a discussion about it at [Why is math.factorial much slower in Python 2.x than 3.x?](http://stackoverflow.com/q/9815252/4014959). – PM 2Ring Jun 28 '15 at 14:44
  • 1
    @Scratso If you are doing anything involving Wilson's Theorem you should probably be computing modular factorials rather than raw factorials – John Coleman Jun 28 '15 at 14:44
  • @PM2Ring The OverflowError hit me when I tried doing some larger numbers. – Lyrenhex Jun 28 '15 at 14:49
  • @JohnColeman Modular factorials? What do you mean? – Lyrenhex Jun 28 '15 at 14:52
  • 1
    @Scratso In the loop have something like p = (p * i) % n rather than simply p *= i. If all you want is the modulus of the answer then it is extremely wasteful to save intermediate results with tens or even hundreds of thousands digits. – John Coleman Jun 28 '15 at 14:58
  • 1
    I second John's suggestion about modular factorials. But if you really need all the digits, you could use a more powerful math library, eg [gmpy](https://pypi.python.org/pypi/gmpy2). I only have gmpy 1.11 installed on this 2GHz machine, but it computes the 31323382 digits of `fac(5000000)` in 32 seconds. I just timed it in Bash using `time python -c "n=5000000;from gmpy import fac;a=fac(n);print a.numdigits()"` – PM 2Ring Jun 28 '15 at 15:10
  • @JohnColeman That ended up breaking the program. It called every number a prime. – Lyrenhex Jun 28 '15 at 15:40
  • @Scratso That shouldn't be the case. See my second answer below. – John Coleman Jun 28 '15 at 17:04
  • @JohnColeman That worked, thanks. :) – Lyrenhex Jun 28 '15 at 21:31

3 Answers3

1

(This answer is a spin-off of the discussion of modular factorials in the comments.)

Computing modular factorials by taking mods at each step is definitely the way to go and can be used in conjunction with Wilsons's Theorem to give an (impractical) way to test for primes:

def modFact(k,n):
    #computes k! mod n
    p = 1
    for i in range(1,k+1):
        p = (p*i) % n
    return p

def isPrime(n):
    return n == 1+ modFact(n-1,n)

Typical output:

>>> for i in range(2,20): print(i,isPrime(i))

2 True
3 True
4 False
5 True
6 False
7 True
8 False
9 False
10 False
11 True
12 False
13 True
14 False
15 False
16 False
17 True
18 False
19 True
>>> isPrime(531455)
False
>>> isPrime(531457)
True
John Coleman
  • 51,337
  • 7
  • 54
  • 119
0

Using the ipython utility timeit:

In [2]: timeit math.factorial(10)
1000000 loops, best of 3: 238 ns per loop

In [3]: timeit math.factorial(100) 
100000 loops, best of 3: 2.43 µs per loop

In [4]: timeit math.factorial(1000)
10000 loops, best of 3: 114 µs per loop

In [5]: timeit math.factorial(10000)
100 loops, best of 3: 9.02 ms per loop

In [6]: timeit math.factorial(100000)
1 loops, best of 3: 517 ms per loop

....can you memoize? At all?

NightShadeQueen
  • 3,284
  • 3
  • 24
  • 37
0

You could do something like this:

def factorial(n, displayProgress = False):
    p = 1
    for i in range(1,n+1):
        p *= i
        if displayProgress and i % 1000 == 0:
            print(round(100*i/n,1),'%', sep = '')
    return p

Typical output:

>>> print(len(str(factorial(10000,True))))
10.0%
20.0%
30.0%
40.0%
50.0%
60.0%
70.0%
80.0%
90.0%
100.0%
35660
John Coleman
  • 51,337
  • 7
  • 54
  • 119