Welcome everyone with another problem to solve! Today we have some nice math to work with: factorials! We are going to build an algorithm to calculate huge factorials: we’ll see how fast they grow and how big they get, and finally, we will calculate the sum of their digits to solve our problem.
Today’s problems are given by
Enough talking: let’s see what today’s problem has to offer.
n! means n * (n-1) * … * 3 * 2 * 1.
For example, 10! = 10 * 9 * … * 3 * 2 * 1 = 3628800, and the sum of the digits of 10! is 3+6+2+8+8+0+0 = 27.
Find the sum of the digits in the number 100!
As we anticipated before, what we are asked to do is to calculate the digits of a number, in particular, the number 100! = 100 * 99 * … * 3 * 2 * 1 : pretty easy...right? Well, not quite!
Let’s put down some basics then and see how to manage such a big product.
The particular notation stated by the problem is a famous concept in math known as factorial. The factorial of a number is simply the product of all the natural numbers up until that number. It’s usually expressed with the expression mark after the number (n!) or, in symbols, as
So, for instance, the factorial of the number 10, written as 10!, is 10! = 10 * 9 * … * 3 * 2 * 1 = 3628800.
What’s really cool about factorials is how fast they grow: since we are used to dealing with Big-O complexity notation, here’s a chart comparing some function’s growth rates. It’s pretty clear that the factorial growth is the worst: for an instance size of less than 10, the number of operations executed by a factorial growing function exceeds (by a lot) a thousand operations!
One simple way to see this behavior is to try with a calculator: if you still have your scientific calculator from high school, like the famous Casio or Sharpe, you can easily see that you can’t calculate over 69!: it gives you back a math error. This is because the number cannot be stored by the memory of the calculator, for it gets too big to address.
Some more recent calculators try to go over that bond: for example, the calculator of my phone (a Redmi 7) can calculate up to 170!, giving out the result with the exponential notation (7.25741562e³⁰⁶) if you try with 171! though, it just spits a disappointing ∞ … which is really not the case.
It’s also really cool to look at how many digits the result of a factorial has:
the number 100! has 158 digits
the number 500! has 1135 digits
the number 1000! has 2568 digits
the number 5000! has 16326 digits
The number 10000! has 35660 digits: it is so big that I could not fit it in a single image. Here’s a GIF with all the digits:
But wait a minute: didn’t we just say that we could not calculate over the factorial of 70? There’s a trick to do so that we’ll see in a moment. But to understand how we can calculate arbitrarily big factorials, we first must go back to the basics of multiplication.
When you were kids, chances are that your teacher at school taught you to multiply in columns with a process similar to this:
On the left side, a simple multiplication between a three-digit number and a one-digit number, in which each digit from the right to the left gets multiplied by 9. At each multiplication, if the result is greater than 9, the remainder is written on top of the next digit (right to left) and added on the next step of the process; on the right side, a multiplication of a three-digit number and a two-digit number, where there are two “temporary” results in between of the process. Here, we previously wrote a 0 as the rightmost digit in the second temporary result and summed those to get the final result.
It’s really basic math, but this process works because we can decompose the starting number in units, tens, hundreds, and so on, and multiply each of those by the second number. After that, we just sum each individual partial result to obtain the final result. Something like this:
That is precisely what we are going to do on a much bigger scale. We are going to deal with very big numbers, so this process needs to be modified a bit to work with the integer’s memory limits built into the language we are using (Python, in this case). To be true, Python 2 used to have a size limit in it’s int
type, but since Python 3 replaced it, the limit was somehow removed. Other languages do have it, though, so we build this approach to keep the algorithm portable to those languages too. Also, it will come in handy to multiply from the right to the left.
Basically, we first convert our number to a string and reverse it. After that, we multiply each digit for the second number, add the previous remainder, and store the result and the new remainder for the next calculation. Let’s see the process step by step first:
With this process, ideally, the only two limits we have on our products are:
The RAM limit: when the number we are trying to multiply gets too big, we’ll be thrown a MemoryError
, for the interpreter has not enough memory to allocate the calculated number. Every modern computer has 8, 16, or 32Gb of RAM tough, so you should have plenty of memory to allocate before reaching the limit.
The time limit: as we’ll see better later, the time needed to complete the process grows pretty quickly. Even if it’s not a real limit (one can always wait, right?), the more the number grows, the more time you’ll need to wait for a result.
Our code will replicate this process step by step, returning the result. Here’s the code:
The function multiplication
accepts two arguments: n
, the number to multiply as string, and m
, the other one as integer. First off, we reverse n
and create two other variables:
newNumber
, which will hold our new number digit by digit
remainder
, which will keep track of the remainder of each operation. The remainder is initially equal to 0.
We loop over the reversed number and, at each step:
result
of the digit, we are on times the second number m
and add the remainder.result
to the newNumber
;result > 9
), we update the variable remainder
with the new one. The remainder is composed by the digits of result
except the last one. If there is no remainder, we set remainder
back to 0. This is really important to avoid bringing on remainders of previous multiplications!
We keep looping for each digit of reversed
and finally, if there is a remainder after the last multiplication, we add it to newNumber
. After that, we simply return our newNumber
.
Now that we have a method to multiply numbers of the size we want, we can apply it to calculate our factorials! Here’s the code:
It’s just a wrapper, really: all it does is set a variable out
, initially equal to the string “1” and then range from 1 to n
(n+1 really, for Python’s range stops at n-1) to apply our multiplication
function, each time updating out
with the new result.
For example, here’s the result of 500!:
1220136825991110068701238785423046926253574342803192842192413588385845373153881997605496447502203281863013616477148203584163378722078177200480785205159329285477907571939330603772960859086270429174547882424912726344305670173270769461062802310452644218878789465754777149863494367781037644274033827365397471386477878495438489595537537990423241061271326984327745715546309977202781014561081188373709531016356324432987029563896628911658974769572087926928871281780070265174507768410719624390394322536422605234945850129918571501248706961568141625359056693423813008856249246891564126775654481886506593847951775360894005745238940335798476363944905313062323749066445048824665075946735862074637925184200459369692981022263971952597190945217823331756934581508552332820762820023402626907898342451712006207714640979456116127629145951237229913340169552363850942885592018727433795173014586357570828355780158735432768888680120399882384702151467605445407663535984174430480128938313896881639487469658817504506926365338175055478128640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Since our problem requires us to calculate the sum of the digits of 100!, we also need a function to sum all the digits, but it’s quite simple to build: you can do it directly in your main
function. Here’s the main:
As in
Have a nice coding, guys! 😎
It’s not that hard to evaluate the time complexity of this algorithm: the biggest amount of time is spent inside the multiplication
function. Here, besides allocating some variables and modifying their values, the slowest part is the for loop, which will run len(n)
times, depending on the length of the number we are multiplying. It is linear, since there are no nested loops to deal with.
When we wrap this function in hugeFactorial
though, we must run that function n
times: so the time complexity becomes quadratic, leaving us with O(n²) t.c.
Finally, summing up the digits it’s another linear time algorithm, but it’s only invoked after the factorial is calculated: for this reason, it won’t add any complexity, leaving us with a total complexity of O(n²).
Talking about execution time, here’s the time taken by my machine (AMD Ryzen 5, avg. ck. 2700 MHz) to calculate some results:
With a simple regression, even if the data is clearly not enough, we can see that our prediction of a O(n²) time complexity holds true: the time taken actually grows like a parabola.
I did not have much time to go over 10000!, so if you try it out, please let me know your results!
Here it is; this was my tackle! In all likelihood, there are better approaches to this kind of problem, but, as always, I wanted to share my try with you guys.
What do you think about this? Do you have a better solution or idea? Let me know in the comments!
I hope you liked the article and found something interesting in it! If so, please leave a like or share it with someone who could appreciate this kind of problem: that would really make my day!
Thanks for reading; as always