/*
   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 
*/

/* +------------------------------------------------------------------------+
   |                                                                        |
   |                      Entiers de longueur arbitraire                    |
   |                                                                        |
   |                                  Carré                                 |
   |                                                                        |
   +------------------------------------------------------------------------+ */

/* M. Quercia, 31/01/2001 */

#include "long_int.h"
#include "long_int-s.h"


                /* +-------------------------------+
                   |  c <- a^2, algorithme en n^2  |
                   +-------------------------------+ */

#ifndef have_sn_sqr_n2
void xn(sqr_n2)(naturel a, longueur la, naturel c) {
  ndouble ret,m0,m1;
  longueur i;

  /* juxtapose dans c les carrés des chiffres de a */
  for (i=0; i < la; i++) {
    ret = (ndouble)a[i] * (ndouble)a[i];
    c[2*i]   = ret;
    c[2*i+1] = ret >> HW;
  }

  /* ajoute la somme des doubles produits */
  for (m0=0; la > 1; a++, c+=2, la--) {

    /* m0 = chiffre suivant de a, m1 = chiffre suivant de 2a */
    if (m0 >= BASE/2) {
      /* récupère le bit perdu dans le chiffre précédent */
      m0   = a[0];
      m1   = 1 + ((m0 << 1) & (BASE-1));
      ret  = m0 + (ndouble)c[0];
      c[0] = ret;
      ret >>= HW;
    } else {
      m0  = a[0];
      m1  = (m0 << 1) & (BASE-1);
      ret = 0;
    }

    /* multiplie par les chiffres suivants de a */
    for (i=1; i < la; i++) {
      ret += m1*(ndouble)a[i] + (ndouble)c[i];
      c[i] = ret;
      ret >>= HW;
    }
    /* propage la retenue */
    if (ret) {
      ret += (ndouble)c[i];
      c[i++] = ret;
     if (ret >> HW) while (++c[i] == 0) i++;
    }
  }

  /* dernier bit de l'avant dernier chiffre de a */
  if (m0 >= BASE/2) {
    ret  = (ndouble)c[0] + (ndouble)a[0];
    c[0] = ret;
    if (ret >> HW) c[1]++;
  }

}
#endif

                /* +------------------------------+
                   |  Décomposition de Karatsuba  |
                   +------------------------------+ */

#ifndef have_sn_karasqr
void xn(karasqr)(naturel a, longueur la, naturel c) {
  longueur l0,l1,lu;
  naturel buff,c0,c1,c2,c3;
  ndouble x,y;

  /* élimine les petits carrés */
  if (la < klim) {
    xn(sqr_n2)(a,la,c);
    return;
  }

  l1 = la/2; l0 = la-l1;
  c0 = c; c1 = c0+l0; c2 = c1+l0; c3 = c2+l0;

  /*
      +------+------+         +------+------+------+------+
  a = |  a1  |  a0  |     c = |  c3  |  c2  |  c1  |  c0  |
      +------+------+         +------+------+------+------+
       <-l1-> <-l0->           <----2*l1---> <-l0-> <-l0-> 
  */

  lu = xn(cmp)(a,l0, a+l0,l1);                 /* u <- |a0-a1| */
  if      (lu > 0) {xn(sub)(a,lu, a+l0, min(l1,lu), c);}
  else if (lu < 0) {lu = -lu; xn(sub)(a+l0, lu, a, lu, c);        }

  if (lu) {                               /* buff <- (a0-a1)^2 */
    buff = xn(alloc_tmp)(2*lu);
    xn(karasqr)(c,lu, buff);
  }
#ifdef useless_init
  else buff = NULL;
#endif

  xn(karasqr)(a,   l0, c0);                    /* c1:c0 <- a0^2 */
  xn(karasqr)(a+l0,l1, c2);                    /* c3:c2 <- a1^2 */

  x = xn(inc)(c1, l0, c2, l0);               /* c1 <- c1 + c2  */
  y = c3[0];
  xn(add)(c3, 2*l1-l0, c1, l0, c2);          /* c2 <- c1 + c2 + c3 */
  xn(inc)(c1, 2*l1+l0, c0, l0);              /* c1 <- c0 + c1 + c2 */
  xn(inc_1)(c2, 2*l1,    x);               /* propage les retenues */
  xn(inc_1)(c3, 2*l1-l0, x);
  xn(inc_1)(c3, 2*l1-l0, y);

  /* retranche (a0-a1)^2 */
  if (lu) {
    xn(dec)(c1,l0+2*l1, buff,2*lu);
    xn(free)(buff);
  }
}
#else
void xn(karasqr)(naturel a, longueur la, naturel c);
#endif


              /* +--------------------------+
                 |  c <- a^2, carré rapide  |
                 +--------------------------+ */

#ifndef have_sn_sqr_k
void xn(sqr_k)(naturel a, longueur la, naturel c) {

  /* sélectionne le meilleur algorithme */
  if      (la < klim)   xn(sqr_n2)  (a,la,c);
  else if (la < flim)   xn(karasqr) (a,la,c);
  else                  xn(fftsqr)  (a,la,c);
}
#endif

               /* +--------------------------------+
                  |  c <- a^2, capacité(c) >= 2la  |
                  +--------------------------------+ */


#ifndef have_sz_sqr_k
void xz(sqr_k)(entier *a, entier *c) {
  longueur la = Lg(a);
  naturel buff, aa;

  if (la == 0) c->hd = 0;
  else if (la >= flim) xn(fftsqr) (a->val,la,c->val);
  else {

    /* recopie a s'il va être surchargé */
    if (c == a) {
      buff = xn(alloc_tmp)(la);
      xn(cpy)(buff,a->val,la);
      aa = buff;
    } else {
      buff = NULL;
      aa = a->val;
    }

    /* sélectionne le meilleur algorithme */
    if (la < klim) xn(sqr_n2)  (aa,la,c->val);
    else           xn(karasqr) (aa,la,c->val);

    if (buff) xn(free)(buff);
  }
  make_head(c,2*la,0);

}
#endif
