The Python installation comes with its own library of functions. The factorial function math.factorial()
is included with its math module. Math modules are part of the Python install code package. Math modules or functions such as factorial, square root, sine, and exponential may be used in Python programs. To use them, the import command must be used prior to the math function call, as shown in the code that follows.
import math print(math.factorial(4)) # the output is: 24 because 4 factorial is 24
The import math
command incorporates math functions from the Python library during the code execution.
Factorial in Mathematics
In mathematics, the term factorial is given to a specific expression that results in the product of natural numbers in sequential order either ascending or descending. The sequence starts or ends with 1. For example, 1*2*3*4 = 24, is said to be “4 factorial” and denoted by 4! (with the exclamation mark.
Therefore, 1*2*3*4*5 is 5! which could also be written as 5*4*3*2*1. In general, for arbitrary (greater) integers n, you can write n! = n * (n-1) * (n-2) * … * 3 * 2 * 1.
Factorial numbers have many applications, especially in the field of probability and combinatorial math. Combinatorics is about counting, or ways to arrange things, or permutations. For a very simple example, how many possible ways are there for one to place 4 books on a shelf?
_4_ _3_ _2_ _1_
In the illustration on the left, there are 4 bookshelf positions.
You can place any of the four books in the first position from left to right. In the second position, you have any of the 3 books left to place there, and so on. Therefore, the total number of possible ways to arrange the books is 4! = 4*3*2*1 = 24.
It is a permutation with 4 different elements (books). In general, the number of permutations of n elements is given by n! = n*(n-1)*…*2*1.
A more practical example is a typical state lottery, where the lottery tickets have basically 6 sets of number pairs or 2-digit numbers. Each digit is composed of 10 possible numbers, namely, 0 – 9. There is a formula on which python math factorial functions could be used to calculate the odds of winning. The formula is:
where the number of permutations of n elements are taken r at a time. The numerator, n! here refers to the range of numbers used in a pair. For example, if the 2-digit numbers are picked from 1 to 40, then the result is:
So, the probability of winning this lottery is 1/3818380. This illustrates that when factorial numbers are used, one is dealing with large numbers. In addition, this case contains a denominator that brings up the importance of why 0! needs to be 1, since divisibility by zero is not allowed. Is buying lottery tickets a waste of money?
Interestingly, the combinatorial probability is covered in Discrete Math along with other topics such as graph theory, trees, cryptography, and several other areas. Discrete Math is an important set of courses that appear in the computer science curriculum. Therefore, factorial numbers are an important topic for programming. One could argue that in many software applications Discrete Math is more important for programmers to master than calculus.
Math Factorial Function Alternatives
In Python, the method math.factorial
can be used to calculate factorial numbers as mentioned previously. But as usual, you can define your own factorial function to generate the same result. Examples 1 and 2 below define a factorial function. Example 1 includes a very important fact about factorial numbers, i.e., what 0! equal to. Mathematically, 0! is equal to 1 and 1! Is also equal to 1. When defining your own function, it is good practice to include this. The following code covers 0! and 1! as a conditional and returns 1 for either; whereas the iterative method in example 2 below does not cover 0!.
Factorial Alternative 1: A Recursive Approach
import math def fcto(n): '''Factorial called fcto is defined''' if n==0 or n==1 : return 1 else : return n * fcto(n-1) # the rest of factorial expression, n(n-1)(n-2)...3(2)(1) print(fcto(5)) # 120
In this example, line 6 is the trickiest since this is the recursive part of function fcto
. The print()
command in line 13 first calls the function with 5 as a parameter, so the function, fcto(5)
returns 5 * fcto(4)
, and then fcto
is called on 4, which returns 4 * fcto(3)
, and then fcto
is called on 3, which returns 3 * fcto(2)
, and so on until finally, fcto(2-1)
is 1, hence line 4 is executed returning 1.
Factorial Alternative 2: Iterative Approach
import math def factorial(n): fact = 1 for num in range(2, n+1): fact *= num return fact print(factorial(4)) # 24
The factorial function is called with 4. Within the scope of the function, the variable fact = 1
. And then, the for loop iteration begins with the value of 2 for num. Since in the first iteration num
is 2, then 2×1 is returned. On the next iteration num = 3
, so the fact variable gets assigned 2×3, so 6 gets returned. So far, 2 and 6 have been returned. This constitutes the first two factorial numbers, or 1×2 = 2, or 2! and 1x2x3 = 6, or 3!. On the next iteration num
is 4, so fact gets assigned 6×4 = 24, which is 4!. This is the last iteration since the range upper limit is n + 1, or 4 + 1 = 5.
5 is not used, since the upper bound of the range command is not executed.
Math Factorial Runtime Complexities
Factorial numbers, n!, increase in value very fast as n gets larger, the product grows much faster than the fast-growing exponential functions, 10^x as an example.
A growth comparison of common functions in mathematics are generally expressed as n! >> 10^x >> x^3 >> x^2 >> lnx, where >> means much-larger-than. In computer science they teach the Big-O notation:
The point is that when you design algorithms using calculations with n!, it is good practice to do careful runtime analysis.
For this reason, how you write programs that make use of factorial functions could make a significant difference in runtime. When calculating large numbers, it is preferrable to use linear O(n) or O(log(n)) if possible. It might be even acceptable to use estimates. For example, since
equals
it is a case where O(n log(n)) could be applicable, but it is an estimate. This is called Stirling’s approximation that gives an approximate value for the factorial function. However, if the numbers involved in the algorithm are large enough, this might be an acceptable solution for reasonable runtimes.
Python is an interpreter type language – one in which compiling is not necessary. This makes python portable across operating systems and easy to use. But the price you pay for these conveniences, for example, is that these languages are computationally slower. For example, they are slower than Java or C++. This is another reason why you must be careful with the implementation of math.factorial
function. Here are 3 examples that illustrate runtime differences. All 3 were discussed above. The iterative, recursive and the math.factorial
runtimes for these snippets are compared here.
Example 1: Timing the Recursive approach for 30!
import math import time start = time.time() def fcto(n): if n==0 or n==1: return 1 else: return n*fcto(n-1) print(fcto(30)) stop = time.time() print("\ntime elapsed for Recursive: " + str(stop-start), "\n")
The output:
265252859812191058636308480000000 time elapsed for Recursive: 0.011820793151855469
Example 2: Timing the Iterative approach for 30!
import math import time start = time.time() def factorial(n): fact = 1 for num in range(2, n + 1): fact *= num return fact print(factorial(30)) stop = time.time() print("\ntime elapsed for iterative: " + str(stop-start), "\n")
The output:
265252859812191058636308480000000 time elapsed for iterative: 0.0
Example 3: Timing the math.factorial function for 30!
import math import time start = time.time() print(math.factorial(30)) stop = time.time() print("\ntime elapsed for math.factorial: " + str(stop-start), "\n") Output: 265252859812191058636308480000000 time elapsed for math.factorial: 3.1948089599609375e-05
The output:
265252859812191058636308480000000 time elapsed for Recursive: 0.011820793151855469
These are small programs with a simple algorithm, but they provide a sample of the differences in runtime. There is no claim here as to which program is the best, because there may have been other operating system processes on my computer running in the background at the time of the program execution. The main point is that there are time differences that can be significant with complex algorithms, hence careful analysis is called for. Although, after running these 3 programs multiple times, I found that the execution time differences between the 3 programs were consistent.