[Tutor] factorial

Greg Chapman greg@gregmchapman.info
Mon May 12 12:15:08 2003


I'm leaving this pretty much intact since it wasn't originally posted to the
list.

>You may want to try the following. fact(n) and fact2(n) will compute
>1000000!. Fact3(n) uses
>De Moivre's approximation also called Stirling's approximation and is good
>only for about 15 places.
>
>The output for 15! is shown.
>>>>fact(15)
>1307674368000L
>>>>fact2(15)
>1307674368000
>n! for  n    =   15
>Actual  digits   13
>Formula digits   13
>Seconds to calc  0.0
>>>>fact3(15)
>1.307674368e+012
>>>>
>
>
>Python has great power. The integer precision is fantastic. Any way to get
>float to be arbitrary?
>I wish the ^ operator could replace ** for exponentiation AND to  be able
to
>edit script and go right
>back in and run without having to close the dos >>> box and re-enter and
>then from factorial import
>* . should be able to  from factorial import * without closing the >>>
>session.

you should be able to do: >>>reload(factorial)

>Have fun
>
>
>#                           Program factorial.py
>#                  Cino Hilliard hillcino368@hotmail.com
># Compute n! using python and the ConText editor which supports Python
># Fact2 tests the accuracy of my formula for the number of digits in n!
># Fact3 tests the accuracy of my modification of De moivre's Formula
>#                                                   1                3
>#          N                  ( -N + 1/(12*N) - 1/(360*N) +. . .)
># N! ~ N * sqrt(2*PI*N) * e
>#
># Perm calculates the permutations of n things taken r at a time
># Comb calculates the combinations of n things taken r at a time
># digits = int((n+.5)*log(n)/log(10) - n*log(exp(1.0))/log(10)+.3991)+1
># usage: >>> from factorial import *  >>> fact2(number)
>#
>from math import *
>def fact(n):
>    f = j = 1
>    while j <= n:
>            f*=j
>            j+=1
>    return f
># digits = int((n+.5)*log(n)/log(10) - n*log(exp(1.0))/log(10)+.3991)+1
># Test this fantastic formula for digits in n!
>def fact2(n):
>    import time
>    t1=time.time()
>    x = fact(n)
>    t2=time.time()
>    print x
>    d = int((n+.5)*log(n)/log(10) - n*log(exp(1.0))/log(10)+.3991)+1
>    print "n! for  n    =  ",n
>    print "Actual  digits  ",len(str(x))
>    print "Formula digits  ",d
>    print "Seconds to calc ",t2 - t1
>
># Test a modification of De moivre's formula for n!
>def fact3(n):
>    series=n
>    nsqr=n*n
>    n2pi =n*2*3.1415926535897932
>    sqrt2pi = sqrt(n2pi)
>    ptn = .5/n
>    tmp = 6
>    ptn /= tmp
>    series -= ptn
>    tmp=30
>    ptn /= nsqr
>    ptn /= tmp
>    series += ptn
>    tmp= 3.49999997364305
>    ptn /= nsqr
>    ptn /= tmp
>    series -= ptn
>    tmp = 1.3333346851
>    ptn /= nsqr
>    ptn /= tmp
>    series += ptn
>    tmp = exp(series)
>    stirtmp = (n**n)/tmp
>    stirling = sqrt2pi*stirtmp
>    print stirling
>
>def comb(n,r):
>        return fact(n)/fact(n-r)/fact(r)
>
>def perm(n,r):
>        return fact(n)/fact(n-r)
>
>
>2^3 + 13^3 + 33^3 + 43^3 = 49^3

I assumed that there would be some advantage to using the approximation, so
I modified the fact2() function to run and time both the fact() function and
the fact3() function:

def stopwatch(func, p):           #I just thought this would be a neat
utility function
    from time import clock        #clock() gives better resolution than
time()
    t1 = clock()
    x  = func(p)
    t2 = clock()
    return (x, t2 - t1)

def fact2(n):
    a = stopwatch(fact, n)
    b = stopwatch(fact3, n)
    print '%e' % a[0]            #I changed this so I could play with bigger
numbers
    print '%e' % b[0]
    d = int((n+.5)*log(n)/log(10) - n*log(exp(1.0))/log(10)+.3991)+1
    print "n! for  n    =         ",n
    print "Actual  digits         ",len(str(a[0]))
    print "Formula digits         ",d
    print "Using fact() time was  ",a[1],"sec"
    print "Using fact3(), time was",b[1],"sec"

>>> fact2(100)
9.332622e+157
9.332622e+157
n! for  n    =          100
Actual  digits          158
Formula digits          158
Using fact() time was   0.000508724584506 sec
Using fact3(), time was 0.00019527648783 sec

but if I switch from scientific notation and force b[0] to long(b[0]), then
we can see the lack of precision:

>>> fact2(30)
265252859812191058636308480000000
265252859812190167497676244910080
n! for  n    =          30
Actual  digits          33
Formula digits          33
Using fact() time was   0.000237181313423 sec
Using fact3(), time was 0.00014834308331 sec

so there is definitely a performance benefit, if you're doing a lot of large
factorial calculations and only need the 14 significant digits. so I
wondered if there would be any application for that.  I added a function
comb3() which is the same as comb() except that it calls fact3() instead of
fact().

>>> x = factorial.comb(100, 5)
>>> print x
75287520
>>> y = factorial.comb3(100, 5)
>>> print int(y)
75287520

and it looks like for some problems, approximation does come close enough.

Interesting stuff.  Thanks Cino.

greg