ex-2/fanc{y,ier}: implement proper computation of N

This commit is contained in:
Michele Guerini Rocco 2020-05-25 21:55:54 +02:00
parent 845548fa93
commit dd7a3055ee
2 changed files with 71 additions and 34 deletions

View File

@ -2,27 +2,42 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <stdarg.h>
#include <math.h> #include <math.h>
#include <gsl/gsl_sf_lambert.h>
#include <gmp.h> #include <gmp.h>
#include <time.h> #include <time.h>
/* The Euler-Mascheroni constant is here computed to /* The Euler-Mascheroni constant is here computed to
* arbitrary precision using GMP rational numbers * arbitrary precision using GMP rational numbers
* with the formula: * and the standard Brent-McMillan algorithm.
* *
* γ = A(N)/B(N) - C(N)/B(N)² - log(N) * γ = A(N)/B(N) - C(N)/B(N)² - log(N)
* *
* with: * where:
* *
* A(N) = Σ_(k = 1)^(k_max) (N^k/k!)² * H(k) * A(N) = Σ_(k = 1)^(k_max) (N^k/k!)² * H(k)
* B(N) = Σ_(k = 0)^(k_max) (N^k/k!)² * B(N) = Σ_(k = 0)^(k_max) (N^k/k!)²
* C(N) = 1/4N Σ_(k = 0)^(2N) ((2k)!)^3/((k!)^4*(16N))^2k * C(N) = 1/4N Σ_(k = 0)^(2N) ((2k)!)^3/((k!)^4*(16N))^2k
* H(k) = Σ_(j = 1)^(k) (1/k), the k-th harmonic number * H(k) = Σ_(j = 1)^(k) (1/k), the k-th harmonic number
* *
* where N is computed from D as written below and k_max is * The error of the estimation is given by
* currently 5*N, chosen in a completely arbitrary way.
* *
* source: http://www.numberworld.org/y-cruncher/internals/formulas.html * Δ(N) = 5/12 (2π) exp(-8N)/x
* *
* so, given a number D of decimal digits to compute we ask
* that the error be smaller than 10^-D. The smallest integer
* to solve the inequality can be proven to be
*
* N = floor(W(5π/9 10^(2D + 1))/16) + 1
*
* where W is the principal value of the Lambert W function.
*
* The number of series terms needed to reduce the error, due to
* to less than 10^-D is α2^p where α is the solution of:
*
* α log(α) = 3 + α α = exp(W(3e) - 1) 4.97
*
* source: Precise error estimate of the Brent-McMillan algorithm for
* the computation of Euler's constant, Jean-Pierre Demailly.
*/ */
/* `mpq_dec_str(z, n)` /* `mpq_dec_str(z, n)`
@ -490,6 +505,11 @@ int main(int argc, char** argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// requested decimal digits
size_t D = atol(argv[1]);
// Brent-McMillan number N
size_t N = gsl_sf_lambert_W0(5*M_PI/9*pow(10, 2*D + 1))/16 + 1;
mpq_t exact, indicator; mpq_t exact, indicator;
mpq_inits(exact, indicator, NULL); mpq_inits(exact, indicator, NULL);
@ -525,11 +545,6 @@ int main(int argc, char** argv) {
"137174210/1111111111", "137174210/1111111111",
10); 10);
// number of decimal places
size_t D = atol(argv[1]);
// number of terms
size_t N = 10 + floor(1.0/8 * log(10) * (double)D);
fprintf(stderr, "[main] N: %ld\n", N); fprintf(stderr, "[main] N: %ld\n", N);
/* calculate series */ /* calculate series */

View File

@ -1,24 +1,40 @@
#include <stdio.h> #include <stdio.h>
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h>
#include <gsl/gsl_sf_lambert.h>
// The Euler-Mascheroni constant is computed through the formula: /* The Euler-Mascheroni constant is computed to D decimal
// * places using the refined Brent-McMillan formula:
// γ = A(N)/B(N) - ln(N) *
// * γ = A(N)/B(N) - C(N)/B(N)² - log(N)
// with: *
// * where:
// A(N) = Σ_(k = 1)^(k_max) (N^k/k!) * H(k) *
// B(N) = Σ_(k = 0)^(k_max) (N^k/k!) * A(N) = Σ_(k = 1)^(k_max) (N^k/k!)² * H(k)
// H(k) = Σ_(j = 1)^(k) (1/k) * B(N) = Σ_(k = 0)^(k_max) (N^k/k!)²
// * C(N) = 1/4N Σ_(k = 0)^(2N) ((2k)!)^3/((k!)^4*(16N))^2k
// where N is computed from D as written below and k_max is the value * H(k) = Σ_(j = 1)^(k) (1/k), the k-th harmonic number
// at which there is no difference between two consecutive terms of *
// the sum because of double precision. * The error of the estimation is given by
// *
// source: http://www.numberworld.org/y-cruncher/internals/formulas.html * Δ(N) = 5/12 (2π) exp(-8N)/x
*
* so, given a number D of decimal digits to compute we ask
* that the error be smaller than 10^-D. The smallest integer
* to solve the inequality can be proven to be
*
* N = floor(W(5π/9 10^(2D + 1))/16) + 1
*
* where W is the principal value of the Lambert W function.
*
* The series are truncated at k_max, which is when the difference
* between two consecutive terms of the sum is zero, at double precision.
*
* source: Precise error estimate of the Brent-McMillan algorithm for
* the computation of Euler's constant, Jean-Pierre Demailly.
*/
// Partial harmonic sum h // Partial harmonic sum H
double harmonic_sum(double n) { double harmonic_sum(double n) {
double sum = 0; double sum = 0;
for (double k = 1; k < n+1; k++) { for (double k = 1; k < n+1; k++) {
@ -49,6 +65,7 @@ double b_series(int N){
return sum; return sum;
} }
// C series
double c_series(int N) { double c_series(int N) {
double sum = 0; double sum = 0;
for (double k = 0; k < N; k++) { for (double k = 0; k < N; k++) {
@ -57,23 +74,28 @@ double c_series(int N) {
return sum/(4.0*N); return sum/(4.0*N);
} }
// Takes in input the number D of desired correct decimals /* The Best result is obtained with D=15, which accurately
// Best result obtained with D = 15, N = 10 * computes 15 digits and gives an error of 3.3e-16.
*
* Increasing to D=21 decreases the error to a minimum of
* 1.1e-16 but the program can't achieve the requested D.
*/
int main(int argc, char** argv) { int main(int argc, char** argv) {
double exact = double exact =
0.57721566490153286060651209008240243104215933593992; 0.57721566490153286060651209008240243104215933593992;
// If no argument is given is input, an error signal is displayed /* if no argument is given, show the usage */
// and the program quits
if (argc != 2) { if (argc != 2) {
fprintf(stderr, "usage: %s D\n", argv[0]); fprintf(stderr, "usage: %s D\n", argv[0]);
fprintf(stderr, "Computes γ up to D decimal places.\n"); fprintf(stderr, "Computes γ up to D decimal places.\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
int N = floor(2.0 + 1.0/4 * log(10) * (double)atoi(argv[1])); // requested decimal digits
int D = atoi(argv[1]);
// Brent-McMillan number N
int N = gsl_sf_lambert_W0(5*M_PI/9*pow(10, 2*D + 1))/16 + 1;
double A = a_series(N); double A = a_series(N);
double B = b_series(N); double B = b_series(N);
double C = c_series(N); double C = c_series(N);