math-float64-expm1
Version:
Computes exp(x) - 1.
304 lines (272 loc) • 8.46 kB
JavaScript
'use strict';
/**
* NOTE: the original C code, the long comment, copyright, license, and the constants are from [netlib]{http://www.netlib.org/fdlibm/s_expm1.c}.
*
* The implementation follows the original, but has been modified for JavaScript.
*/
/**
* ====================================================
* Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved.
*
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/**
* expm1(x)
* Returns exp(x)-1, the exponential of x minus 1.
*
* Method
* 1. Argument reduction:
* Given x, find r and integer k such that
*
* x = k*ln2 + r, |r| <= 0.5*ln2 ~ 0.34658
*
* Here a correction term c will be computed to compensate the error in r when rounded to a floating-point number.
*
* 2. Approximating expm1(r) by a special rational function on the interval [0,0.34658]:
* Since
*
* r*(exp(r)+1)/(exp(r)-1) = 2+ r^2/6 - r^4/360 + ...
*
* we define R1(r*r) by
*
* r*(exp(r)+1)/(exp(r)-1) = 2+ r^2/6 * R1(r*r)
*
* That is,
*
* R1(r**2) = 6/r *((exp(r)+1)/(exp(r)-1) - 2/r)
* = 6/r * ( 1 + 2.0*(1/(exp(r)-1) - 1/r))
* = 1 - r^2/60 + r^4/2520 - r^6/100800 + ...
*
* We use a special Remes algorithm on [0,0.347] to generate a polynomial of degree 5 in r*r to approximate R1. The maximum error of this polynomial approximation is bounded by 2**-61. In other words,
*
* R1(z) ~ 1.0 + Q1*z + Q2*z**2 + Q3*z**3 + Q4*z**4 + Q5*z**5
*
* where
* Q1 = -1.6666666666666567384E-2
* Q2 = 3.9682539681370365873E-4
* Q3 = -9.9206344733435987357E-6
* Q4 = 2.5051361420808517002E-7
* Q5 = -6.2843505682382617102E-9
*
* (where z=r*r, and the values of Q1 to Q5 are listed below) with error bounded by
*
* | 5 | -61
* | 1.0+Q1*z+...+Q5*z - R1(z) | <= 2
* | |
*
* expm1(r) = exp(r)-1 is then computed by the following specific way which minimizes the accumulated rounding error:
*
* 2 3
* r r [ 3 - (R1 + R1*r/2) ]
* expm1(r) = r + --- + --- * [--------------------]
* 2 2 [ 6 - r*(3 - R1*r/2) ]
*
* To compensate the error in the argument reduction, we use
*
* expm1(r+c) = expm1(r) + c + expm1(r)*c
* ~ expm1(r) + c + r*c
*
* Thus, c+r*c will be added in as the correction terms for expm1(r+c). Now rearrange the term to avoid optimization screw up:
* ( 2 2 )
* ({ ( r [ R1 - (3 - R1*r/2) ] ) } r )
* expm1(r+c) ~ r - ({r*(--- * [--------------------]-c)-c} - --- )
* ({ ( 2 [ 6 - r*(3 - R1*r/2) ] ) } 2 )
* ( )
*
* = r - E
*
* 3. Scale back to obtain expm1(x):
* From step 1, we have
*
* expm1(x) = either 2^k*[expm1(r)+1] - 1
* = or 2^k*[expm1(r) + (1-2^-k)]
*
* 4. Implementation notes:
* (A). To save one multiplication, we scale the coefficient Qi to Qi*2^i, and replace z by (x^2)/2.
* (B). To achieve maximum accuracy, we compute expm1(x) by
* (i) if x < -56*ln2, return -1.0, (raise inexact if x!=inf)
* (ii) if k=0, return r-E
* (iii) if k=-1, return 0.5*(r-E)-0.5
* (iv) if k=1 if r < -0.25, return 2*((r+0.5)- E)
* else return 1.0+2.0*(r-E);
* (v) if (k<-2||k>56), return 2^k(1-(E-r)) - 1 (or exp(x)-1)
* (vi) if k <= 20, return 2^k((1-2^-k)-(E-r)), else
* (vii) return 2^k(1-((E+2^-k)-r))
*
* Special cases:
* expm1(INF) is INF
* expm1(NaN) is NaN
* expm1(-INF) is -1
* for finite argument, only expm1(0)=0 is exact.
*
* Accuracy:
* according to an error analysis, the error is always less than 1 ulp (unit in the last place).
*
* Misc. info.
* For IEEE double
* if x > 7.09782712893383973096e+02, then expm1(x) will overflow
*
* Constants:
* The hexadecimal values are the intended ones 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.
*/
// MODULES //
var evalpoly = require( 'math-evalpoly' );
var highWord = require( 'math-float64-get-high-word' );
var setHighWord = require( 'math-float64-set-high-word' );
// CONSTANTS //
var PINF = require( 'const-pinf-float64' );
var NINF = require( 'const-ninf-float64' );
var BIAS = 1023;
var OVERFLOW_THRESHOLD = 7.09782712893383973096e+02; // 0x40862E42 0xFEFA39EF
// High and low words of ln(2):
var LN2_HI = 6.93147180369123816490e-01; // 0x3FE62E42 0xFEE00000
var LN2_LO = 1.90821492927058770002e-10; // 0x3DEA39EF 0x35793C76
// 1 / ln(2):
var LN2_INV = 1.44269504088896338700e+00; // 0x3FF71547 0x652B82FE
// ln(2) * 56:
var LN2x56 = 3.88162421113569373274e+01; // 0x4043687A 0x9F1AF2B1
// ln(2) * 0.5:
var LN2_HALF = 3.46573590279972654709e-01; // 0x3FD62E42 0xFEFA39EF
// ln(2) * 1.5:
var LN2_HALFx3 = 1.03972077083991796413e+00; // 0x3FF0A2B2 0x3F3BAB73
// Scaled polynomial coefficients:
var Q = [
-3.33333333333331316428e-02, // 0xBFA11111 0x111110F4
1.58730158725481460165e-03, // 0x3F5A01A0 0x19FE5585
-7.93650757867487942473e-05, // 0xBF14CE19 0x9EAADBB7
4.00821782732936239552e-06, // 0x3ED0CFCA 0x86E65239
-2.01099218183624371326e-07 // 0xBE8AFDB7 0x6E09C32D
];
// FUNCTIONS //
var polyval = evalpoly.factory( Q );
// EXPM1 //
/**
* FUNCTION: expm1( x )
* Computes e^x - 1.
*
* @param {Number} x - input value
* @returns {Number} function value
*/
function expm1( x ) {
var half_x;
var sign;
var hi;
var lo;
var hx;
var r1;
var y;
var z;
var c;
var t;
var e;
var k;
if ( x === PINF || x !== x ) {
return x;
}
if ( x === NINF ) {
return -1;
}
if ( x === 0 ) {
return x; // handles +-0 (IEEE 754-2008)
}
// Set y = |x|:
if ( x < 0 ) {
sign = true;
y = -x;
} else {
sign = false;
y = x;
}
// Filter out huge and non-finite arguments...
if ( y >= LN2x56 ) { // if |x| >= 56*ln(2)
if ( sign ) { // if x <= -56*ln(2)
return -1;
}
if ( y >= OVERFLOW_THRESHOLD ) { // if |x| >= 709.78...
return PINF;
}
}
// Extract the more significant bits from |x|:
hx = highWord( y );
// Argument reduction...
if ( y > LN2_HALF ) { // if |x| > 0.5*ln(2)
if ( y < LN2_HALFx3 ) { // if |x| < 1.5*ln(2)
if ( sign ) {
hi = x + LN2_HI;
lo = -LN2_LO;
k = -1;
} else {
hi = x - LN2_HI;
lo = LN2_LO;
k = 1;
}
} else {
if ( sign ) {
k = LN2_INV*x - 0.5;
} else {
k = LN2_INV*x + 0.5;
}
k = k|0; // use a bitwise OR to cast `k` to an integer (see also asm.js type annotations: http://asmjs.org/spec/latest/#annotations)
t = k;
hi = x - t*LN2_HI; // t*ln2_hi is exact here
lo = t * LN2_LO;
}
x = hi - lo;
c = (hi-x) - lo;
}
// if |x| < 2**-54 => high word: 0 01111001001 00000000000000000000 => 0x3c900000 = 1016070144 => exponent = 01111001001 = 969 = 1023-54
else if ( hx < 1016070144 ) {
return x;
}
else {
k = 0;
}
// x is now in primary range...
half_x = 0.5 * x;
z = x * half_x;
r1 = 1.0 + z*polyval( z );
t = 3.0 - r1*half_x;
e = z * ( (r1-t) / (6.0 - x*t) );
if ( k === 0 ) {
return x - ( x*e - z ); // c is 0
}
e = x*(e-c) - c;
e -= z;
if ( k === -1 ) {
return 0.5*(x-e) - 0.5;
}
if ( k === 1 ) {
if ( x < -0.25 ) {
return -2.0 * ( e - (x+0.5) );
}
return 1 + 2.0*(x-e);
}
if ( k <= -2 || k > 56 ) { // suffice to return exp(x)-1
y = 1 - (e-x);
// Add k to y's exponent:
hi = highWord( y ) + (k<<20);
y = setHighWord( y, hi );
return y - 1;
}
t = 1;
if ( k < 20 ) {
// 0x3ff00000 - (0x200000>>k) = 1072693248 - (0x200000>>k) => 0x200000 = 0 00000000010 00000000000000000000
hi = 1072693248 - (0x200000>>k);
t = setHighWord( t, hi ); // t=1-2^-k
y = t - (e-x);
} else {
hi = ( (BIAS-k)<<20 );
t = setHighWord( t, hi ); // t=2^-k
y = x - (e+t);
y += 1;
}
// Add k to y's exponent:
hi = highWord( y ) + (k<<20);
y = setHighWord( y, hi );
return y;
} // end FUNCTION expm1()
// EXPORTS //
module.exports = expm1;