/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * 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 version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

/*
 * __kernel_rem_pio2(x,y,e0,nx,prec,ipio2)
 * double x[],y[]; int e0,nx,prec; int ipio2[];
 *
 * __kernel_rem_pio2 return the last three digits of N with
 *      y = x - N*pi/2
 * so that |y| < pi/2.
 *
 * The method is to compute the integer (mod 8) and fraction parts of
 * (2/pi)*x without doing the full multiplication. In general we
 * skip the part of the product that are known to be a huge integer (
 * more accurately, = 0 mod 8 ). Thus the number of operations are
 * independent of the expone_krnt of the input.
 *
 * (2/pi) is represented by an array of 24-bit integers in ipio2[].
 *
 * Input parameters:
 *  x[] The input value (must be positive) is broken into nx
 *      pieces of 24-bit integers in double precision format.
 *      x[i] will be the i-th 24 bit of x. The scaled expone_krnt
 *      of x[0] is given in input parameter e0 (i.e., x[0]*2^e0
 *      match x's up to 24 bits.
 *
 *      Example of breaking a double positive z into x[0]+x[1]+x[2]:
 *          e0 = ilogb(z)-23
 *          z  = scalbn(z,-e0)
 *      for i = 0,1,2
 *          x[i] = floor(z)
 *          z    = (z-x[i])*2**24
 *
 *
 *  y[] output result in an array of double precision numbers.
 *      The dimension of y[] is:
 *          24-bit  precision   1
 *          53-bit  precision   2
 *          64-bit  precision   2
 *          113-bit precision   3
 *      The actual value is the sum of them. Thus for 113-bit
 *      precision, one_kr may have to do something like:
 *
 *      long double t,w,r_head, r_tail;
 *      t = (long double)y[2] + (long double)y[1];
 *      w = (long double)y[0];
 *      r_head = t+w;
 *      r_tail = w - (r_head - t);
 *
 *  e0  The expone_krnt of x[0]
 *
 *  nx  dimension of x[]
 *
 *      prec    an integer indicating the precision:
 *          0   24  bits (single)
 *          1   53  bits (double)
 *          2   64  bits (extended)
 *          3   113 bits (quad)
 *
 *  ipio2[]
 *      integer array, contains the (24*i)-th to (24*i+23)-th
 *      bit of 2/pi after binary point. The corresponding
 *      floating value is
 *
 *          ipio2[i] * 2^(-24(i+1)).
 *
 * External function:
 *  double scalbn(), floor();
 *
 *
 * Here is the description of some local variables:
 *
 *  jk  jk+1 is the initial number of terms of ipio2[] needed
 *      in the computation. The recommended value is 2,3,4,
 *      6 for single, double, extended,and quad.
 *
 *  jz  local integer variable indicating the number of
 *      terms of ipio2[] used.
 *
 *  jx  nx - 1
 *
 *  jv  index for pointing to the suitable ipio2[] for the
 *      computation. In general, we want
 *          ( 2^e0*x[0] * ipio2[jv-1]*2^(-24jv) )/8
 *      is an integer. Thus
 *          e0-3-24*jv >= 0 or (e0-3)/24 >= jv
 *      Hence jv = max(0,(e0-3)/24).
 *
 *  jp  jp+1 is the number of terms in PIo2[] needed, jp = jk.
 *
 *  q[] double array with integral value, representing the
 *      24-bits chunk of the product of x and 2/pi.
 *
 *  q0  the corresponding expone_krnt of q[0]. Note that the
 *      expone_krnt for q[i] would be q0-24*i.
 *
 *  PIo2[]  double precision array, obtained by cutting pi/2
 *      into 24 bits chunks.
 *
 *  f[] ipio2[] in floating point
 *
 *  iq[]    integer array by breaking up q[] in 24-bits chunk.
 *
 *  fq[]    final product of x*(2/pi) in fq[0],..,fq[jk]
 *
 *  ih  integer. If >0 it indicates q[] is >= 0.5, hence
 *      it also indicates the *sign* of the result.
 *
 */

/*
 * Constants:
 * The hexadecimal values are the intended one_krs for the following
 * constants. The decimal values may be used, provided that the
 * compiler will convert from decimal to binary accurately enough
 * to produce the hexadecimal values shown.
 */

#include "incls/_precompiled.incl"
#include "incls/_Remainder_pio2_kernel.cpp.incl"

#if ENABLE_FLOAT

#ifdef __cplusplus
extern "C" {
#endif

static const int init_jk[] = {2,3,4,6}; /* initial value for jk */

static const double PIo2[] = {
  1.57079625129699707031e+00, /* 0x3FF921FB, 0x40000000 */
  7.54978941586159635335e-08, /* 0x3E74442D, 0x00000000 */
  5.39030252995776476554e-15, /* 0x3CF84698, 0x80000000 */
  3.28200341580791294123e-22, /* 0x3B78CC51, 0x60000000 */
  1.27065575308067607349e-29, /* 0x39F01B83, 0x80000000 */
  1.22933308981111328932e-36, /* 0x387A2520, 0x40000000 */
  2.73370053816464559624e-44, /* 0x36E38222, 0x80000000 */
  2.16741683877804819444e-51, /* 0x3569F31D, 0x00000000 */
};

static const double
zero_kr   = 0.0,
one_kr    = 1.0,
two24_kr   =  1.67772160000000000000e+07, /* 0x41700000, 0x00000000 */
twon24  =  5.96046447753906250000e-08; /* 0x3E700000, 0x00000000 */

int remainder_pio2_kernel(double *x, double *y, int e0, int nx, int prec, const int *ipio2) {
  int jz, jx, jv, jp, jk, carry, n, iq[20], i, j, k, m, q0, ih;
  double z, fw, f[20], fq[20], q[20];

  /* initialize jk*/
  jk = init_jk[prec];
  jp = jk;

  /* determine jx,jv,q0, note that 3>q0 */
  jx =  nx - 1;
  jv = (e0 - 3) / 24;
  if (jv < 0) {
    jv = 0;
  }
  q0 =  e0 - 24 * (jv + 1);

  /* set up f[0] to f[jx+jk] where f[jx+jk] = ipio2[jv+jk] */
  j = jv - jx;
  m = jx + jk;
  for (i = 0; i <= m; i++, j++) {
    f[i] = (j < 0) ? zero_kr : jvm_i2d(ipio2[j]);
  }

  /* compute q[0],q[1],...q[jk] */
  for (i=0; i <= jk; i++) {
    for(j = 0, fw = 0.0; j <= jx; j++) {
      fw = jvm_dadd(fw, jvm_dmul(x[j], f[jx + i - j]));
    }
    q[i] = fw;
  }

  jz = jk;
recompute:
  /* distill q[] into iq[] reversingly */
  for(i = 0, j = jz, z = q[jz]; j > 0; i++,j--) {
    fw    =  jvm_i2d(jvm_d2i(jvm_dmul(twon24, z)));
    iq[i] =  jvm_d2i(jvm_dsub(z, jvm_dmul(two24_kr, fw)));
    z     =  jvm_dadd(q[j-1], fw);
  }

  /* compute n */
  z  = jvm_fplib_scalbn(z,q0);       /* actual value of z */
  z  = jvm_dsub(z, jvm_dmul(8.0, jvm_fplib_floor(jvm_dmul(z,0.125))));     /* trim off integer >= 8 */
  n  = jvm_d2i(z);
  z  = jvm_dsub(z, jvm_i2d(n));
  ih = 0;
  if(q0 > 0) {  /* need iq[jz-1] to determine n */
    i  = (iq[jz-1] >> (24 - q0));
    n += i;
    iq[jz - 1] -= i << (24 - q0);
    ih = iq[jz - 1] >> (23 - q0);
  } else if (q0 == 0) {
    ih = iq[jz-1] >> 23;
  } else if (jvm_dcmpl(z, 0.5) >= 0) {
    ih=2;
  }

  if (ih > 0) {  /* q > 0.5 */
    n += 1;
    carry = 0;
    for (i = 0;i < jz ;i++) {    /* compute 1-q */
      j = iq[i];
      if (carry == 0) {
          if(j != 0) {
            carry = 1;
            iq[i] = 0x1000000- j;
          }
      } else  {
        iq[i] = 0xffffff - j;
      }
    }
    if (q0 > 0) {      /* rare case: chance is 1 in 12 */
      switch (q0) {
      case 1:
       iq[jz-1] &= 0x7fffff; break;
      case 2:
       iq[jz-1] &= 0x3fffff; break;
      }
    }
    if (ih == 2) {
      z = jvm_dsub(one_kr, z);
      if (carry != 0) {
        z = jvm_dsub(z, jvm_fplib_scalbn(one_kr, q0));
      }
    }
  }

  /* check if recomputation is needed */
  if (jvm_dcmpl(z, zero_kr) == 0) {
    j = 0;
    for (i = jz - 1; i >= jk; i--) j |= iq[i];
    if (j == 0) { /* need recomputation */
    for (k = 1; iq[jk - k] == 0; k++)      /* k = no. of terms needed */
      continue;
    for (i = jz + 1; i <= jz + k; i++) {   /* add q[jz+1] to q[jz+k] */
      f[jx+i] = jvm_i2d(ipio2[jv+i]);
      for(j = 0, fw = 0.0; j <= jx; j++) {
        fw = jvm_dadd(fw, jvm_dmul(x[j], f[jx+i-j]));
      }
      q[i] = fw;
    }
    jz += k;
    goto recompute;
    }
  }

  /* chop off zero terms */
  if(jvm_dcmpl(z, 0.0) == 0) {
    jz -= 1;
    q0 -= 24;
    while(iq[jz] == 0) {
      jz--;
      q0-=24;
    }
  } else { /* break z into 24-bit if necessary */
    z = jvm_fplib_scalbn(z,-q0);
    if (jvm_dcmpl(z, two24_kr) >= 0) {
      fw = jvm_i2d(jvm_d2i(jvm_dmul(twon24,z)));
      iq[jz] = jvm_d2i(jvm_dsub(z, jvm_dmul(two24_kr, fw)));
      jz += 1;
      q0 += 24;
      iq[jz] = jvm_d2i(fw);
    } else {
      iq[jz] = jvm_d2i(z);
    }
  }

  /* convert integer "bit" chunk to floating-point value */
  fw = jvm_fplib_scalbn(one_kr, q0);
  for(i = jz; i >= 0; i--) {
    q[i] = jvm_dmul(fw, jvm_i2d(iq[i]));
    fw = jvm_dmul(fw, twon24);
  }

  /* compute PIo2[0,...,jp]*q[jz,...,0] */
  for(i = jz; i >= 0; i--) {
    for(fw = 0.0,k = 0; k <= jp && k <= jz - i; k++) {
      fw = jvm_dadd(fw, jvm_dmul(PIo2[k], q[i+k]));
    }
    fq[jz-i] = fw;
  }

  /* compress fq[] into y[] */
  switch(prec) {
    case 0:
    fw = 0.0;
    for (i = jz; i >= 0; i--) {
      fw = jvm_dadd(fw, fq[i]);
    }
    y[0] = (ih == 0)? fw: jvm_dneg(fw);
    break;
    case 1:
    case 2:
    fw = 0.0;
    for (i = jz; i >= 0; i--) {
      fw = jvm_dadd(fw, fq[i]);
    }
    y[0] = (ih == 0)? fw: jvm_dneg(fw);
    fw = jvm_dsub(fq[0], fw);
    for (i = 1; i <= jz; i++) {
      fw = jvm_dadd(fw, fq[i]);
    }
    y[1] = (ih == 0)? fw: jvm_dneg(fw);
    break;
    case 3: /* painful */
    for (i = jz; i > 0; i--) {
        fw      = jvm_dadd(fq[i-1], fq[i]);
        fq[i]   = jvm_dadd(fq[i], jvm_dsub(fq[i-1], fw));
        fq[i-1] = fw;
    }
    for (i = jz; i > 1; i--) {
        fw      = jvm_dadd(fq[i-1], fq[i]);
        fq[i]   = jvm_dadd(fq[i], jvm_dsub(fq[i-1], fw));
        fq[i-1] = fw;
    }
    for (fw = 0.0, i = jz; i >= 2; i--) {
      fw = jvm_dadd(fw, fq[i]);
    }
    if(ih == 0) {
        y[0] =  fq[0];
        y[1] =  fq[1];
        y[2] =  fw;
    } else {
        y[0] = jvm_dneg(fq[0]);
        y[1] = jvm_dneg(fq[1]);
        y[2] = jvm_dneg(fw);
    }
  }
  return n & 7;
}

#ifdef __cplusplus
}
#endif

#endif // ENABLE_FLOAT
