/*
   This file is part of Numerix.  Numerix is free software; you can
   redistribute it and/or modify it under the terms of the GNU General
   Public License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA 
*/

/* +------------------------------------------------------------------------+
   |                                                                        |
   |                    Calcul de Pi, formule de Ramanujan                  |
   |                                                                        |
   +------------------------------------------------------------------------+ */

/* M. Quercia, le 06/02/2001 */
/* cf. "The Caml Numbers Reference Manual", Inria, RT-0141 */
/* annexe A, pp. 115 et suivantes.                         */

#include <stdlib.h>
#include <stdio.h>
#include <sys/times.h>
#include <time.h>
#include <gmp.h>

                            /* +-----------------+
                               |  Chronométrage  |
                               +-----------------+ */

void chrono(char *msg) {
  static double tlast = 0;
  double t;
  struct tms buf;

  times(&buf);
  t = (double)(buf.tms_utime + buf.tms_stime)/CLK_TCK;
  fprintf(stderr,"%8.2f %8.2f %s\n",t,t-tlast,msg);
  fflush(stderr);
  tlast = t;
}


                       /* +--------------------------+
                          |  Sommation dichotomique  |
                          +--------------------------+ */

#define kprof 31 /* profondeur maxi de récursion */

void somme(long prec, mpz_t num, mpz_t den) {

  long etapes = (prec+197)/94; /* nombre de termes à calculer */
  mpz_t pile[3*kprof];         /* pile de récursion */
  mpz_t *sp = pile;            /* pointeur de pile  */

  mpz_t a,b,c;
  mpz_t p,alpha,beta,gamma,delta,eps,t,u;
  long i,j;

  mpz_init_set_ui( a,    13591409);     /* constantes  */
  mpz_init_set_ui( b,    545140134);
  mpz_init_set_str(c,    "10939058860032000", 10);

  mpz_init_set_ui( p,     0);            /* index série */
  mpz_init_set_ui( alpha, 1);            /* 2p + 1      */
  mpz_init_set_ui( beta,  1);            /* 6p + 1      */
  mpz_init_set_ui( gamma, 5);            /* 6p + 5      */
  mpz_init_set_ui( delta, 53360);        /* c*p^3       */
  mpz_init_set(    eps,   a);            /* a + bp      */
  mpz_init(t);                           /* scratch     */
  mpz_init(u);                           /* scratch     */

  /* initialise la pile */
  for (i=0; i < 3*kprof; i++) mpz_init(pile[i]);

  for (i=1; i <= etapes; i++) {

    /* calcule et retranche les termes de rangs p et p+1 */
    mpz_mul (t,      alpha,  beta);
    mpz_mul (sp[0],  t,      gamma);
    mpz_set (sp[1],  delta); 
    mpz_set (sp[2],  eps);   
                                
    mpz_add_ui(p,    p,      1);
    mpz_add_ui(alpha,alpha,  2);
    mpz_add_ui(beta, beta,   6);
    mpz_add_ui(gamma,gamma,  6);
    mpz_mul (t,      p,      p);
    mpz_mul (u,      c,      p);
    mpz_mul (delta,  t,      u);
    mpz_add (eps,    eps,    b);
                                
    mpz_mul (t,      delta,  sp[2]);
    mpz_mul (u,      sp[0],  eps);
    mpz_sub (sp[2],  t,      u);
    mpz_mul (t,      alpha,  beta);
    mpz_mul (u,      sp[0],  gamma);
    mpz_mul (sp[0],  t,      u);
    mpz_mul (sp[1],  sp[1],  delta);
                                
    mpz_add_ui(p,    p,      1);
    mpz_add_ui(alpha,alpha,  2);
    mpz_add_ui(beta, beta,   6);
    mpz_add_ui(gamma,gamma,  6);
    mpz_mul (t,      p,      p);
    mpz_mul (u,      c,      p);
    mpz_mul (delta,  t,      u);
    mpz_add (eps,    eps,    b);

    sp += 3;
    
    /* combine avec les calculs précédents */
    for (j=1; (j&i) == 0; j <<= 1) {
      sp -= 3;

      mpz_mul(t,      sp[1],  sp[-1]);
      mpz_mul(sp[-1], sp[-3], sp[2]);
      mpz_add(sp[-1], sp[-1], t);
      mpz_mul(sp[-3], sp[-3], sp[0]);
      mpz_mul(sp[-2], sp[-2], sp[1]);

    }
  }

  /* termine les calculs en instance */
  sp -= 3;
  while (sp != pile) {
    sp -= 3;

    mpz_mul(t,     sp[4],  sp[2]);
    mpz_mul(sp[2], sp[0],  sp[5]);
    mpz_add(sp[2], sp[2],  t);
    /* mpz_mul(sp[0], sp[0],  sp[3]); */ /* inutile, a0 ne servira plus */
    mpz_mul(sp[1], sp[1],  sp[4]);
  }

  /* nettoie les variables locales et retourne la fraction */
  mpz_set(num, pile[1]);
  mpz_set(den, pile[2]);
  for (i=0; i<3*kprof; i++) mpz_clear(pile[i]);
  mpz_clear(a);
  mpz_clear(b);
  mpz_clear(c);
  mpz_clear(p);
  mpz_clear(alpha);
  mpz_clear(beta);
  mpz_clear(gamma);
  mpz_clear(delta);
  mpz_clear(eps);
  mpz_clear(t);
  mpz_clear(u);
}


                 /* +--------------------------------------+
                    |  Calcule pi avec digits+2 décimales  |
                    +--------------------------------------+ */

void pi(long digits, int pgcd, int print, int skip, int debug) {

  long prec,i,j;
  mpz_t t,d,num,den;
  char *s;
  char ss[80];
  
  mpz_init(t);
  mpz_init_set_ui(d, 640320);
  mpz_init(num);
  mpz_init(den);

  if (debug) chrono("début");  
  mpz_ui_pow_ui(t, 5, digits);
  if (debug) chrono("puiss-5");
  prec = mpz_sizeinbase(t,2) + digits;
  mpz_mul   (t,  t,  t);
  mpz_mul   (t,  d,  t);
  mpz_mul_2exp(t,t, 2*digits);
  mpz_sqrt  (t,  t);
  if (debug) chrono("sqrt");
  somme(prec,num,den);
  if (debug) {sprintf(ss,"série lb=%d",mpz_sizeinbase(num,2)); chrono(ss);}
  if (pgcd) {
    mpz_gcd(t,num,den);
    mpz_divexact(num,num,t);
    mpz_divexact(den,den,t);
    if (debug) {sprintf(ss,"pgcd  lb=%d",mpz_sizeinbase(num,2)); chrono(ss);}
  }
  mpz_mul_ui(t,  t, 100);
  mpz_mul   (t,  num, t);
  mpz_fdiv_q(t,  t, den);
  if (debug) chrono("quotient");

  mpz_clear(d);
  mpz_clear(num);
  mpz_clear(den);

  if (print) {
    s = mpz_get_str(NULL, 10, t);
    if (debug) chrono("conversion");

    printf("%c.\n",s[0]);
    for (i=1; (s[i]); i++) {
      printf("%c",s[i]);
      if      ((i%250) == 0) printf("\n\n");
      else if ((i%50)  == 0) printf("\n");
      else if ((i%10)  == 0) printf("  ");
      else if ((i%5)   == 0) printf(" ");
      if ((skip) && ((i%50) == 0)) {
	j = strlen(s+i)/50 - 1;
	if (j > 0) {printf("... (%ld lignes omises)\n",j); i += 50*j;}
      }
    }
    if ((i%50) != 1) printf("\n");
    free(s);
  }
  mpz_clear(t);
}


int main(int argc, char **argv) {
  long digits=100;
  int  pgcd=0, print=1, skip=0, debug=0, help=0;
  char *cmd = argv[0];

  argv++;
  while (*argv) {
    if      (strcmp(*argv,"-h") == 0)        help  = 1;
    else if (strcmp(*argv,"-d") == 0)        debug = 1;
    else if (strcmp(*argv,"-noprint") == 0)  print = 0;
    else if (strcmp(*argv,"-skip") == 0)     skip  = 1;
    else if (strcmp(*argv,"-gcd") == 0)      pgcd  = 1;
    else if (strcmp(*argv,"-test") == 0)     {
      digits = 1000;
      print=skip=1; debug=pgcd=0;
    }
    else digits = strtol(argv[0],NULL,0);
    argv++;
  }

  if (help) printf("usage: %s [digits] [-d] [-noprint] [-skip] [-gcd]\n",cmd);
  else      pi(digits-2,pgcd,print,skip,debug);

  fflush(stdout); fflush(stderr);
  return(0);
}
