Here\'s my approach to factorials:
def factorial(n):
\'\'\'Returns factorial of n\'\'\'
r = 1
for i in range(1, n + 1):
r *= i
return
It's actually unusual to really need the true factorial value n! in many areas of application. Often it's way more realistic to use the natural log of the factorial. I can't think of any applications where the log can't be used as a better alternative, because factorials are most often used to compute values related to probabilities of choosing combinations of things.
A common thing to compute is probabilities that are based on factorials such as choosing the binomial coefficient (n k) = n! / (k!(n-k)!). Given this is a ratio of factorials then log(n k) = log(n!)-log(k!)-log((n-k)!) which is reliably computed using one of the various log factorial approximations. And if you do a lot of probability math it's generally best to do it in the log domain anyway (measuring probability in decibels) because it often involves extremely wide ranges of numbers less than 1, and so math precision will fall apart very quickly using common floating point representations if the log version is not used.
E.T.Jaynes was a famous mathematician and an expert in probability theory and I'd recommend his book "Probability Theory: The Logic of Science" as a very readable source on this topic and Bayesian reasoning and information theory using log probabilities.
You can return the gamma function (math.gamma(x)
), but it would probably be faster to generate the factorial with a for loop
Multiplying the numbers in sequence,
r = 1
for i in range(1, n + 1):
r *= i
return r
creates a large number (as in tens of thousands of bits) very quickly, and then you have a lot of multiplications of one huge number and one small number. Multiplications where at least one of the factors is huge are slow.
You can speed it up considerably by reducing the number of multiplications involving huge numbers, for example
def range_prod(lo,hi):
if lo+1 < hi:
mid = (hi+lo)//2
return range_prod(lo,mid) * range_prod(mid+1,hi)
if lo == hi:
return lo
return lo*hi
def treefactorial(n):
if n < 2:
return 1
return range_prod(1,n)
produces, timing the computation of 100000! % 100019
(I first tried len(str(fun(100000))
, but the conversion to string is abominably slow, so that made the difference seem smaller than it is):
$ python factorial.py
81430
math.factorial took 4.06193709373 seconds
81430
factorial took 3.84716391563 seconds
81430
treefactorial took 0.344486951828 seconds
so a more than 10× speedup for 100000!
.