Note that you should never compute factorials if you can help it, and if you're implementing maths, don't compute a recurrence relation if there is a direct formula that you can use. (That's not always possible, but the majority of the time it is)
Catalan numbers have the direct formula Cn = 1/(n+1) * 2n choose n
, and while "on paper" you can write 2n choose n using factorials divided by factorials, in computer land you don't want to make that substitution because factorials are famously huge numbers, and if we want a binomial coefficient we don't actually need them: we can near-trivially compute any binomial coefficient with Pascal's triangle, or even more trivially by using a prebuilt binomial function such as the scipy.special.binom
function. However, at very large numbers scipy (ironically) becomes imprecise, so let's just write our own:
binomials = [[1]]
def binom(n,k):
while n >= len(binomials):
s = len(binomials)
next = [0] * (s+1)
next[0] = 1
for i in range(1,s):
next[i] = binomials[s-1][i-1] + binomials[s-1][i]
next[s] = 1;
binomials.append(next)
return binomials[n][k]
There, this is all pure integer math, and python has infinite precision ints, so as long as we have enough memory this binom
function will always give us the correct answer.
with that, let's compute things:
def Cn(n):
return binom(2*n, n) / (n+1)
def calcSum(n):
sum = 0
for k in range(0, n+1):
sum += Cn(k) * Cn(n-k)
return "{:f}".format(sum)
And let's test calcSum(59)
:
>>> calcSum(59)
'1583850964596120221000260537810944.000000'
>>>
Now, that's not the right answer of course (that would be 1583850964596120042686772779038896, so we're off by a factor 10^18), but it is "the right answer for fixed precision IEEE floating point numbers to yield" without any sort of factorial problems.
Of course, we could add in Decimal
:
from decimal import Decimal
def Cn(n):
return Decimal(binom(2*n, n)) / Decimal((n+1))
def calcSum(n):
sum = Decimal(0)
for k in range(0, n+1):
sum += Cn(k) * Cn(n-k)
return sum
This gives us Decimal('1.583850964596120042686772779E+33')
which is clearly a bad answer (if we print it without scientific notation it turns into 1583850964596120042686772779000000
) and that E+33 tells us that we forgot to set the decimal precision to something that has at least as many digits as the answer needs. So, let's just use 100 digits of precision:
from decimal import Decimal, getcontext
getcontext().prec = 100
And now the answer is Decimal('1583850964596120042686772779038896')
.
Done: that is the correct answer.
So your code has two problems. First: don't use factorials when you're programming, use the thing you actually need. In this case, binomial coefficients. Second: use the things Python has for getting the precision you need. You imported Decimal
but didn't use it, but even if you had, your code was missing the instruction to set the decimal precision to a number high enough to get the right answer.
The most important lesson here is don't blindly implement math formulae because the symbolic maths that we work out by hand have been optimized over the centuries to be easy for "I know a whole bunch of susbtitution rules from years of study" human brains. So unless you're working in a programming language that can do that same symbolic substitution work (e.g. Mathematica), you're going to have to think like a computer. If you have a recurrence relation, find the non-recurrence equivalent function and implement that. And if you ever see a factorial, ask yourself what it's used for, then see if there's a clean and efficient way to compute that instead.