/*
   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 08/02/2001 */
/* cf. "The Caml Numbers Reference Manual", Inria, RT-0141 */
/* annexe A, pp. 115 et suivantes.                         */

#include <stdlib.h>
#include <stdio.h>
#include "c-long_int.h"

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

void somme(long prec, xint *num, xint *den) {

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

  xint a     = xx(of_int)(13591409);     /* constantes  */
  xint b     = xx(of_int)(545140134);
  xint c     = xx(of_string)("10939058860032000");
  xint un    = xx(of_int)(1);
  xint deux  = xx(of_int)(2);
  xint six   = xx(of_int)(6);

  xint p     = xx(of_int)(0);            /* index série */
  xint alpha = xx(of_int)(1);            /* 2p + 1      */
  xint beta  = xx(of_int)(1);            /* 6p + 1      */
  xint gamma = xx(of_int)(5);            /* 6p + 5      */
  xint delta = xx(of_int)(53360);        /* c*p^3       */
  xint eps   = xx(of_xint)(a);           /* a + bp      */
  xint t     = xx(new)();                /* scratch     */
  xint u     = xx(new)();                /* scratch     */
  xint x1    = xx(new)();                /* scratch     */
  xint x2    = xx(new)();                /* scratch     */
  xint x3    = xx(new)();                /* scratch     */
  long i,j;

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

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

    /* calcule et retranche les termes de rangs p et p+1 */
    xx(mul) (&t,     alpha,  beta);
    xx(mul) (sp,     t,      gamma);
    xx(copy)(sp+1,   delta); 
    xx(copy)(sp+2,   eps);   
                                
    xx(add) (&p,     p,       un);
    xx(add) (&alpha, alpha,  deux);
    xx(add) (&beta,  beta,   six);
    xx(add) (&gamma, gamma,  six);
    xx(sqr) (&t,     p);
    xx(mul) (&u,     c,      p);
    xx(mul) (&delta, t,      u);
    xx(add) (&eps,   eps,    b);
                                
    xx(mul) (&t,     delta,  sp[2]);
    xx(mul) (&u,     sp[0],  eps);
    xx(sub) (sp+2,   t,      u);
    xx(mul) (&t,     alpha,  beta);
    xx(mul) (&u,     sp[0],  gamma);
    xx(mul) (sp,     t,      u);
    xx(mul) (sp+1,   sp[1],  delta);
                                
    xx(add) (&p,     p,      un);
    xx(add) (&alpha, alpha,  deux);
    xx(add) (&beta,  beta,   six);
    xx(add) (&gamma, gamma,  six);
    xx(sqr) (&t,     p);
    xx(mul) (&u,     c,      p);
    xx(mul) (&delta, t,      u);
    xx(add) (&eps,   eps,    b);

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

      xx(mul)(&t,    sp[1],  sp[-1]);
      xx(mul)(sp-1,  sp[-3], sp[2]);
      xx(add)(sp-1,  sp[-1], t);
      xx(mul)(sp-3,  sp[-3], sp[0]);
      xx(mul)(sp-2,  sp[-2], sp[1]);

    }
  }

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

    xx(mul)(&t,    sp[4],  sp[2]);
    xx(mul)(sp+2,  sp[0],  sp[5]);
    xx(add)(sp+2,  sp[2],  t);
    xx(mul)(sp+1,  sp[1],  sp[4]);
  }

  /* nettoie les variables locales et retourne la fraction */
  {
    xint x;
    x = *num; *num = pile[1]; pile[1] = x;
    x = *den; *den = pile[2]; pile[2] = x;
  }
  for (i=0; i<3*kprof; i++) xx(free)(pile+i);
  xx(free)(&a);
  xx(free)(&b);
  xx(free)(&c);
  xx(free)(&un);
  xx(free)(&deux);
  xx(free)(&six);
  xx(free)(&p);
  xx(free)(&alpha);
  xx(free)(&beta);
  xx(free)(&gamma);
  xx(free)(&delta);
  xx(free)(&eps);
  xx(free)(&t);
  xx(free)(&u);
  xx(free)(&x1);
  xx(free)(&x2);
  xx(free)(&x3);
}


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

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

  long prec, i,j;
  xint cinq = xx(of_int)(5);
  xint d    = xx(of_int)(640320);
  xint num  = xx(new)();
  xint den  = xx(new)();
  xint t    = xx(new)();
  xint u    = xx(new)();
  xint x1   = xx(new)();
  xint x2   = xx(new)();
  xint x3   = xx(new)();
  char *s;
  char ss[80];
  
  if (debug) chrono("début");  
  xx(pow)(&t, cinq, digits);
  if (debug) chrono("puiss-5");
  prec = xx(nbits)(t) + digits;
  xx(sqr)  (&t, t);
  xx(mul)  (&t, d,   t);
  xx(shl)  (&t, t,   2*digits);
  xx(sqrt) (&t, t);
  if (debug) chrono("sqrt");
  somme(prec,&num,&den);
  if (debug) {sprintf(ss,"série lb=%ld",xx(nbits)(num)); chrono(ss);}
  if (pgcd) {
    xx(cfrac)(&num,&den,&x1,&x2,&x3,num,den);
    if (debug) {sprintf(ss,"pgcd  lb=%ld",xx(nbits)(num)); chrono(ss);}
  }
  xx(mul_1)(&t, t, 100);
  xx(mul)  (&t, num, t);
  xx(quo)  (&t, t, den);
  if (debug) chrono("quotient");

  xx(free)(&cinq);
  xx(free)(&d);
  xx(free)(&num);
  xx(free)(&den);
  xx(free)(&u);
  xx(free)(&x1);
  xx(free)(&x2);
  xx(free)(&x3);

  if (print) {
    s = xx(string_of)(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");
    xx(free_string)(s);
  }
  xx(free)(&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);
}
