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!.
math.factorial? It's a little bit faster than your code above.