/*
 * Copyright (C) 2004-2011 David Defour, Catherine Daramy-Loirat,
 * Florent de Dinechin, Matthieu Gallet, Nicolas Gast, Christoph Quirin Lauter,
 * and Jean-Michel Muller
 *
 * This file is part of crlibm, the correctly rounded mathematical library,
 * which has been developed by the Arénaire project at École normale supérieure
 * de Lyon.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "crlibm.h"
#include "crlibm_private.h"
#include "triple-double.h"
#include "expm1.h"

#define AVOID_FMA 0


extern void expm1_direct_td(double *expm1h, double *expm1m, double *expm1l, 
	         	    double x, double xSqHalfh, double xSqHalfl, double xSqh, double xSql, int expoX);

extern void expm1_common_td(double *expm1h, double *expm1m, double *expm1l, 
			    double rh, double rm, double rl, 
			    double tbl1h, double tbl1m, double tbl1l, 
			    double tbl2h, double tbl2m, double tbl2l, 
			    int M);


/* Function expm1_13

   Computes exp(x)-1 with an accuracy of 120 bits (128 for |x| <= 2^(-5)) as

   (expm1h + expm1m + expm1l) \approx exp(x) - 1

   There is no special case handling. 

   |x| is supposed to be greater than 2^(-53)

   x is supposed to be in the range -37...709

   The triple-double exph + expm + expl is non-overlapping.

*/

void expm1_13(double *expm1h, double *expm1m, double *expm1l, double x) {
  db_number xdb, shiftedXMultdb;
  int xIntHi, expoX, k, M, index1, index2;
  double xSqh, xSql, xSqHalfh, xSqHalfl;
  double t1, t2;
  double msLog2Div2LMultKh, msLog2Div2LMultKm, msLog2Div2LMultKl;
  double xMultLog2InvMult2L, shiftedXMult, kd, rh, rm, rl;
  double tbl1h, tbl1m, tbl1l, tbl2h, tbl2m, tbl2l;

  
  xdb.d = x; 
  xIntHi = xdb.i[HI] & 0x7fffffff;

  /* Test if we have |x| <= 1/4-1/2ulp(1/4) for knowing if we use exp(x) or approximate directly */

  if (xIntHi < DIRECTINTERVALBOUND) {
    /* We approximate expm1 directly after a range reduction as follows

       expm1(x) = (expm1(x/2) + 2) * expm1(x/2)

       We perform the range reduction in such a way that finally |x| < 1/32 
    */

    /* Extract the exponent of |x| and add 5 (2^5 = 32) */
    expoX = ((xIntHi & 0x7ff00000) >> 20) - (1023 - 5);
    
    /* If this particularily biased exponent expoX is negative, we are already less than 1/32 */
    if (expoX >= 0) {
      /* If we are here, we must perform range reduction */


      /* We multiply x by 2^(-expoX-1) by bit manipulation 
	 x cannot be denormalized so there is no danger
      */
      xdb.i[HI] += (-expoX-1) << 20;

      /* We reassign the new x and maintain xIntHi */

      xIntHi = xdb.i[HI] & 0x7fffffff;
      x = xdb.d;
    }
    
    /* Here, we have always |x| < 1/32 */


    /* Double precision evaluation steps and one double-double step */

    Mul12(&xSqh,&xSql,x,x);
    xSqHalfh = 0.5 * xSqh;
    xSqHalfl = 0.5 * xSql;

    expm1_direct_td(expm1h, expm1m, expm1l, x, xSqHalfh, xSqHalfl, xSqh, xSql, expoX);
       
  } else {

    /* If we are here, we can use expm1(x) = exp(x) - 1 */
    
    /* Range reduction - exact part: compute k as double and as int */
    
    xMultLog2InvMult2L = x * log2InvMult2L;
    shiftedXMult = xMultLog2InvMult2L + shiftConst;
    kd = shiftedXMult - shiftConst;
    shiftedXMultdb.d = shiftedXMult;
    k = shiftedXMultdb.i[LO];
    M = k >> 12;
    index1 = k & INDEXMASK1;
    index2 = (k & INDEXMASK2) >> 6;
    
    
    Mul133(&msLog2Div2LMultKh,&msLog2Div2LMultKm,&msLog2Div2LMultKl,kd,msLog2Div2Lh,msLog2Div2Lm,msLog2Div2Ll);
    t1 = x + msLog2Div2LMultKh;
    Add12Cond(rh,t2,t1,msLog2Div2LMultKm);
    Add12Cond(rm,rl,t2,msLog2Div2LMultKl);
    
    tbl1h = twoPowerIndex1[index1].hi;
    tbl1m = twoPowerIndex1[index1].mi;
    tbl2h = twoPowerIndex2[index2].hi;
    tbl2m = twoPowerIndex2[index2].mi;
    tbl1l = twoPowerIndex1[index1].lo;
    tbl2l = twoPowerIndex2[index2].lo;
    
    expm1_common_td(expm1h, expm1m, expm1l, rh, rm, rl, tbl1h, tbl1m, tbl1l, tbl2h, tbl2m, tbl2l, M); 
    
  }
}
