/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
//
//	ExpDD.c
//	
//	Double-double Function Library
//	Copyright: © 1995-96 by Apple Computer, Inc., all rights reserved
//	
//	long double expl( long double x );
//	long double exp2l( long double x );
//	long double expm1l( long double x );
//	
//			_ExpInnerLD() is exported for use by other functions.
//

#include "math.h"
#include "fp_private.h"
#if (__WANT_LONG_DOUBLE_FORMAT__ - 0L == 128L)
#include "DD.h"

// Floating-point constants

static const double kLn2  = 6.9314718055994529e-1;		// {0x3FE62E42, 0xFEFA39EF}
static const double kLn2b = 2.3190468138462996e-17;		// {0x3C7ABC9E, 0x3B39803F}
static const double kLn2c = 5.7077084384162121e-34;		// {0x3907B57A, 0x079A1934}
static const double kLn2d = -3.5824322106018114e-50;	// {0xB5AACE93, 0xA4EBE5D1}
static const double k1ByLn2 = 1.4426950408889634;		// {0x3FF71547, 0x652B82FE}
static const double kBig = 6.755399441055744e+15;		// {0x43380000, 0x00000000}
static const double r13 = 1.6059043836821613e-10;		// {0x3DE61246, 0x13A86D09}

static const Double kInfinityu = {{0x7ff00000, 0x0}};

static const double coeff[] = {
	6227020800.0, 3113510400.0, 1037836800.0, 259459200.0, 51891840.0, 8648640.0,
	1235520.0, 154440.0, 17160.0, 1716.0, 156.0, 13.0, 1.0
};

struct ExpTableEntry {
   double x;
   double fhead;
   double ftail;
};

extern uint32_t ExpTableLD[];

long double expl( long double x )
{
	double fpenv;
	DblDbl u;
	double extra;
	
	u.f = x;
	
	if (((u.i[0] & 0x7fffffffu) | u.i[1]) == 0)				// x = 0.0
		return 1.0L;
	
	if ((u.i[0] & 0x7ff00000u) != 0x7ff00000u) {				// x is not NaN, Infinity
                FEGETENVD(fpenv);
                FESETENVD(0.0);
		u.f = _ExpInnerLD(u.d[0], u.d[1], 0.0, &extra, 0);
		if ((u.i[0] & 0x7ff00000) == 0x7ff00000)
			u.d[1] = 0.0;
                FESETENVD(fpenv);
		return u.f;
	}
	
	if (u.d[0] != u.d[0])										// NaN case
		return x;
	
	if ((u.i[0] & 0xfff00000u) == 0x7ff00000u)				// +Infinity 
		return x;
	else														// -Infinity
		return 0.0L;
}

long double exp2l( long double x )
{
	double fpenv;
	DblDbl u;
	double head, tail, extra, uu, vv, ww, xx, yy, c, top, bottom;
	
	u.f = x;
	
	if (((u.i[0] & 0x7fffffffu) | u.i[1]) == 0)				// x = 0.0
		return 1.0L;
	
	if ((u.i[0] & 0x7ff00000u) != 0x7ff00000u) {  			// x is not NaN, Infinity
                FEGETENVD(fpenv);
                FESETENVD(0.0);
		head = u.d[0];
		tail = u.d[1];
		uu = __FADD( head * kLn2c, tail * kLn2b );
		vv = head * kLn2b;
		ww = __FMSUB( head, kLn2b, vv );
		xx = tail * kLn2;
		yy = __FMSUB( tail, kLn2, xx );
		c = ww + yy + uu;
		top = head * kLn2;
		ww = __FMSUB( head, kLn2, top );
		
		uu = __FADD( vv, xx );
		if (fabs(xx) > fabs(vv))
			c = xx - uu + vv + c;
		else
			c = vv - uu + xx + c;
			
		bottom = __FADD( uu, ww );
		if (fabs(ww) > fabs(uu))
			c = ww - bottom + uu + c;
		else
			c = uu - bottom + ww + c;
			
		head = __FADD( top, bottom );
		ww = (top - head) + bottom;
		tail = __FADD( ww, c );
		c = (ww - tail) + c;
		
		u.f = _ExpInnerLD(head, tail, c, &extra, 3);
		if ((u.i[0] & 0x7ff00000) == 0x7ff00000) {
			u.d[1] = 0.0;
		}
                FESETENVD(fpenv);
		return u.f;
	} 
	
	if (u.d[0] != u.d[0])									// NaN case
		return x;
	if ((u.i[0] & 0xfff00000u) == 0x7ff00000u)			// +Inifnity
		return x;
	else													// -Infinity
		return 0.0L;
}

long double expm1l( long double x )
{
	double fpenv;
	DblDbl u;
	double extra;
	
	u.f = x;
	
	if (((u.i[0] & 0x7fffffffu) | u.i[1]) == 0)			// x = 0.0
		return 0.0L;
	
	if ((u.i[0] & 0x7ff00000u) != 0x7ff00000u) {			// x is not NaN, Infinity
                FEGETENVD(fpenv);
                FESETENVD(0.0);
		if ((u.i[0] & 0x7ff00000u) >= 0x2ef00000u) // |x| > 2^(-110)
			u.f = _ExpInnerLD(u.d[0], u.d[1], 0.0, &extra, 2);
		if ((u.i[0] & 0x7ff00000) == 0x7ff00000)
			u.d[1] = 0.0;
                FESETENVD(fpenv);
		return u.f;
	}
	
	if (u.d[0] != u.d[0])									// NaN case
		return x;
	if ((u.i[0] & 0xfff00000u) == 0x7ff00000u)			// +Inifnity
		return x;
	else													// -Infinity
		return -1.0L;
}

//***********************************************************************************
//
//    FUNCTION:  long double _ExpInnerLD(double alpha, double beta, double gamma,
//               double *extra, int exptype)
//
//    DESCRIPTION: This routine is called internally by the following functions:
//
//                 long double Exp(long double);
//                 long double Exp2(long double);
//                 long double ExpMinus1(long double);
//                 long double Power(long double, long double);
//                 family of long double hyperbolic functions;
//
//                 1) exptype = 0 (called by Exp()):
//                    on entry: (alpha, beta)
//                    on exit:  (head, tail) of  Exp(alpha + beta) 
//                 
//                 2) exptype = 1 (called by hyperbolic functions):
//                    on entry: (alpha, beta)
//                    on exit:  (head, tail, extra) of Exp(alpha + beta)/2.0
//
//                 3) exptype = 2 (called by ExpMinus1()):
//                    on entry: (alpha, beta)
//                    on exit:  (head, tail) of Exp(alpha + beta) - 1.0
//
//                 4) exptype = 3 (called by Power(), Exp2()):
//                    on entry: (alpha, beta, gamma)
//                    on exit:  (head, tail) of Exp(alpha + beta + gamma)
//                 
//                 5) exptype = 4 (called by gamma functions):
//                    on entry: (alpha, beta, gamma)
//                    on exit:  (head, tail, extra) of Exp(alpha + beta + gamma)
//                 
//                 This routine assumes that the rounding direction on entry
//                 is round-to-nearest,  and infinity, NaN and 0 have been
//                 pre-filtered so that the input is a normal/denormal nonzero
//                 values.
//
//***********************************************************************************

long double _ExpInnerLD(double alpha, double beta, double gamma, double *extra,
	int exptype)
{
	int notnormal, i;
	double tail, factor, redarg, b, blow, redarg1, ca, carry;
	double arg, arg2a, c, d, arg2, sum, temp, suml, hi, prod, residual;
	double sum2, suml2, t1, t2, small, high, prodlow, bump, resnew;
	double close, top, residual2, residual3, bottom;
	Double test, accndx, en, rscale, scale, power;
	DblDbl result;
	struct ExpTableEntry *TblPtr = (struct ExpTableEntry *)ExpTableLD + 24;
	
	test.f = alpha;
	scale.f = 0.0;
	en.f = alpha*k1ByLn2 + kBig;
	factor = en.f - kBig;              
	redarg = __FNMSUB( kLn2, factor, alpha ); // alpha - kLn2*factor;   
	notnormal = 0;                   
	if ((test.i[0] & 0x7fffffffu) > 0x40862000u) { // |alpha| > 708 
		if (alpha > 0) {
			if (alpha <= 1026.0 * kLn2) {
				en.i[1] -= 4;
				notnormal = (1023 + 4) * 1048576;
			}
			else { // result overflow
				if ((exptype == 1) || (exptype == 4))
					*extra = 0.0;
				result.d[0] = kInfinityu.f;
				result.d[1] = 0.0;
				return result.f;
			}
		}
		else {
			if ((alpha >= -800.0) && (exptype != 2)) {
				en.i[1] += 256;       
				notnormal = (1023 - 256)*1048576;
			}
			else {      // result underflow or (alpha <= -708, and exptype = 2 or 4)
				if ((exptype == 1) || (exptype == 4))
					*extra = 0.0;
				result.d[0] = (exptype == 2) ? - 1.0 : 0.0;
				result.d[1] = 0.0;
				return result.f;
			}
		}
	}
	b = kLn2b*factor;              
	blow = __FMSUB( kLn2b, factor, b ); // kLn2b*factor - b;
	redarg1 = __FNMSUB( kLn2b, factor, beta );  // beta - b;
	accndx.f =  redarg*64.0 + kBig;  
	ca = kLn2c*factor;
	if (fabs(b) > fabs(beta))
		carry = beta - (b + redarg1) - blow;
	else
		carry = (beta - redarg1) - b - blow;
	redarg -= TblPtr[(int32_t)accndx.i[1]].x;     
	arg = __FADD( redarg, redarg1 );           
	arg2a = (redarg - arg + redarg1);
	c = __FSUB( carry, ca );                   
	d = __FNMSUB( kLn2c, factor, ca ) - (ca - (carry - c)) - kLn2d*factor;  // (ca - kLn2c*factor) - (ca - (carry - c)) - kLn2d*factor;  
	scale.i[0] = (en.i[1] + 1023) << 20;   
	arg2 = arg2a + c + d;
	if (exptype >= 3)
		arg2 += gamma;  // extpye = 3 or 4

	sum = coeff[12];
	sum = __FMADD( sum, arg, coeff[11] ); // coeff[11] + sum*arg;        
	sum = __FMADD( sum, arg, coeff[10] ); // coeff[10] + sum*arg;        
	sum = __FMADD( sum, arg, coeff[9] ); // coeff[ 9] + sum*arg;
	sum = __FMADD( sum, arg, coeff[8] ); // coeff[ 8] + sum*arg;
	temp = __FMADD( sum, arg, coeff[7] ); // coeff[7] + sum*arg;        
	suml = __FMADD( sum, arg, coeff[7] - temp ); // coeff[7] - temp + sum*arg;   
	sum = temp;
	for (i = 6; i > 0; i--) {
		hi = __FMADD( sum, arg, coeff[i] ); // coeff[i] + sum*arg;
		suml = __FMADD( suml, arg, __FMADD( sum, arg, coeff[i] - hi ) ); // coeff[i] - hi + sum*arg + suml*arg;
		sum = hi;
	}
	prod = sum*r13;                 
	residual = __FNMSUB( coeff[0], prod, sum ) + suml; // (sum - coeff[0]*prod) + suml;
	suml = residual*r13;
	sum2 = __FMADD( prod, arg, 1.0); // __FADD( 1.0, prod*arg );                
	suml2 = __FMADD( suml, arg, __FMADD( prod, arg, 1.0 - sum2 ) ); // 1.0 - sum2 + prod*arg + suml*arg;
	sum = __FMADD( suml2, arg, sum2*arg ); // sum2*arg + (suml2*arg);      
	suml = __FMADD( suml2, arg, __FMSUB( sum2, arg, sum ) ); // (sum2*arg - sum) + (suml2*arg);

	t1 = TblPtr[(int32_t)accndx.i[1]].fhead;    
	t2 = TblPtr[(int32_t)accndx.i[1]].ftail;
	small = t2 + sum*(t2 + arg2*t1);    
	prod = __FMUL( t1, sum );        
	high = __FADD( prod, t1 );        
	prodlow = __FMSUB( t1, sum, prod );     
	residual = (t1 - high) + prod; 
	     
	if (exptype == 2) {
		rscale.f = 0.0;
		rscale.i[0] = 2046*1048576 - scale.i[0];
		temp = __FSUB( high, rscale.f );  
		if (temp > 0) 
			bump = high - temp - rscale.f;
		else             
			bump = high - (temp + rscale.f);
		resnew = __FADD( residual, bump );  
		if (fabs(residual) > fabs(bump))
			small = ((residual - resnew) + bump) + small;  
		else
			small = ((bump - resnew) + residual) + small;
		high = temp;
		residual = resnew;
	}
	
	tail = small + arg2*t1 + prodlow + suml*t1;   
	close = __FADD( tail, residual );     
	top = __FADD( high, close );          
	residual2 = (residual - close) + tail;  
	residual3 = (high - top) + close;   
	bottom = __FADD( residual3, residual2 ); 
	  
	if ((exptype == 1) || (exptype == 4)) {
		if (exptype == 1) scale.f *= 0.5;   
		if (fabs(residual2) > fabs(residual3))
			*extra = (residual2 - bottom + residual3)*scale.f;
		else
			*extra = (residual3 - bottom + residual2)*scale.f;
	}
	
	if (notnormal != 0) {
		power.f = 0.0;
		power.i[0] = notnormal;
		top *= power.f;
		bottom *= power.f;
		if ((exptype == 1) || (exptype == 4))
			*extra *= power.f;
	}
	
	result.d[0] = top*scale.f;
	result.d[1] = bottom*scale.f;
	return result.f;
}
#endif
