Calculating Khinchin's constant
Today I and two friends decided that Xavier Gourdon’s 1997 result of 110 000 digits of Khinchin’s constant, K_0 could be improved. Khinchin’s constant is the result of an interesting theorem on the properties of continued fraction expansion, you can read more about K_0 on the usual sources, wikipedia and mathworld.
The defining series used to calculate K_0 is:
\ln K_0 = \frac{1}{\ln{2}} \sum_{[1,\infty]}^{n} \frac{\zeta(2n) -1}{n} \left( \sum_{[1, 2n-1]}^{k} \frac{(-1)^{k+1}}{k} \right)
Let’s call the first summation S_1 and the second summation S_2(n) such that:
S_2(n) = \sum_{[1, 2n-1]}^{k} \frac{(-1)^{k+1}}{k}
S_1(N) = \sum_{[1, N]}^{n} \frac{\zeta(2n) -1}{n} S_2(n)
Trivial implementation:
Now, one could calculate K_0 using something like:
double K0, S1, S2;
S1 = 0.0;
for
K0 = ;
Optimisation
Right now the implementation recalculates S_2(n) entirely for every n. It would be an improvement to re-use the previous value, S_2(n-1) to calculate the new one, S_2(n).
To implement this we calculate the difference between two successive series S_2(n):
\begin{aligned} S_2(n) - S_2(n-1) &= \left( \sum_{[1, 2n-1]}^{k} - \sum_{[1, 2(n-1)-1]}^{k}\right) \frac{(-1)^{k+1}}{k} \\ &= \sum_{[2n-2, 2n-1]}^{k} \frac{(-1)^{k+1}}{k} \\ &= \frac{1}{2(1-n)(2n-1)} \end{aligned}
Which results in the following algorithm:
double K0, S1, S2;
S1 = - 1;
S2 = 0.0;
for
K0 = ;
We eliminated a loop!
But how big should N be to reach convergence? One solution is to loop until S_1 no longer changes:
double K0, S1, S2, error, S1_old;
S1 = - 1;
S2 = 0.0;
unsigned int n = 2;
do
while;
K0 = ;
Implementation and analysis
My first thought was to use C++ and GMP, but GMP lacked support for calculating zeta functions. The documentation points to MPFR for floating point extensions. The MPFR library uses GMP for calculations and contains a ton of useful functions besides zeta.
After compiling (with the latest GCC of course) the program produced thousand digits in less than a second and two thousand digits in 7 seconds, we will never get to a hundred thousand at that rate.
So we analysed this program using callgrind and KCachegrind which revealed that 99% of the time is spent calculating the \zeta(2n)'s. This will be the target of optimisation in following posts.
Complete source
// g++ -O3 -lmpfr
int
int