time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

log1p.js (9445B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2018 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 *
     18 *
     19 * ## Notice
     20 *
     21 * The following copyright, license, and long comment were part of the original implementation available as part of [FDLIBM]{@link http://www.netlib.org/fdlibm/s_log1p.c}. The implementation follows the original, but has been modified for JavaScript.
     22 *
     23 * ```text
     24 * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
     25 *
     26 * Developed at SunPro, a Sun Microsystems, Inc. business.
     27 * Permission to use, copy, modify, and distribute this
     28 * software is freely granted, provided that this notice
     29 * is preserved.
     30 * ```
     31 */
     32 
     33 'use strict';
     34 
     35 // MODULES //
     36 
     37 var isnan = require( './../../../../base/assert/is-nan' );
     38 var getHighWord = require( '@stdlib/number/float64/base/get-high-word' );
     39 var setHighWord = require( '@stdlib/number/float64/base/set-high-word' );
     40 var PINF = require( '@stdlib/constants/float64/pinf' );
     41 var NINF = require( '@stdlib/constants/float64/ninf' );
     42 var FLOAT64_EXPONENT_BIAS = require( '@stdlib/constants/float64/exponent-bias' );
     43 var polyval = require( './polyval_lp.js' );
     44 
     45 
     46 // VARIABLES //
     47 
     48 // High and low words of ln(2):
     49 var LN2_HI = 6.93147180369123816490e-01; // 0x3fe62e42 0xfee00000
     50 var LN2_LO = 1.90821492927058770002e-10; // 0x3dea39ef 0x35793c76
     51 
     52 // sqrt(2)-1:
     53 var SQRT2M1 = 4.142135623730950488017e-01; // 0x3fda8279 0x99fcef34
     54 
     55 // sqrt(2)/2-1:
     56 var SQRT2HALFM1 = -2.928932188134524755992e-01; // 0xbfd2bec3 0x33018866
     57 
     58 // 2**-29:
     59 var SMALL = 1.862645149230957e-09; // 0x3e200000 0x00000000
     60 
     61 // 2**-54:
     62 var TINY = 5.551115123125783e-17;
     63 
     64 // Max integer (unsafe) => 2**53:
     65 var TWO53 = 9007199254740992;
     66 
     67 // 2/3:
     68 var TWO_THIRDS = 6.666666666666666666e-01;
     69 
     70 
     71 // MAIN //
     72 
     73 /**
     74 * Evaluates the natural logarithm of \\(1+x\\).
     75 *
     76 * ## Method
     77 *
     78 * 1.  Argument Reduction: find \\(k\\) and \\(f\\) such that
     79 *
     80 *     ```tex
     81 *     1+x = 2^k (1+f)
     82 *     ```
     83 *
     84 *     where
     85 *
     86 *     ```tex
     87 *     \frac{\sqrt{2}}{2} < 1+f < \sqrt{2}
     88 *     ```
     89 *
     90 *     <!-- <note> -->
     91 *
     92 *     If \\(k=0\\), then \\(f=x\\) is exact. However, if \\(k \neq 0\\), then \\(f\\) may not be representable exactly. In that case, a correction term is needed. Let
     93 *
     94 *     ```tex
     95 *     u = \operatorname{round}(1+x)
     96 *     ```
     97 *
     98 *     and
     99 *
    100 *     ```tex
    101 *     c = (1+x) - u
    102 *     ```
    103 *
    104 *     then
    105 *
    106 *     ```tex
    107 *     \ln (1+x) - \ln u \approx \frac{c}{u}
    108 *     ```
    109 *
    110 *     We can thus proceed to compute \\(\ln(u)\\), and add back the correction term \\(c/u\\).
    111 *
    112 *     <!-- </note> -->
    113 *
    114 *     <!-- <note> -->
    115 *
    116 *     When \\(x > 2^{53}\\), one can simply return \\(\ln(x)\\).
    117 *
    118 *     <!-- </note> -->
    119 *
    120 * 2.  Approximation of \\(\operatorname{log1p}(f)\\). Let
    121 *
    122 *     ```tex
    123 *     s = \frac{f}{2+f}
    124 *     ```
    125 *
    126 *     based on
    127 *
    128 *     ```tex
    129 *     \begin{align*}
    130 *     \ln 1+f &= \ln (1+s) - \ln (1-s) \\
    131 *             &= 2s + \frac{2}{3} s^3 + \frac{2}{5} s^5 + ... \\
    132 *             &= 2s + sR \\
    133 *     \end{align*}
    134 *     ```
    135 *
    136 *     We use a special Reme algorithm on \\(\[0,0.1716\]\\) to generate a polynomial of degree \\(14\\) to approximate \\(R\\). The maximum error of this polynomial approximation is bounded by \\(2^{-58.45}\\). In other words,
    137 *
    138 *     ```tex
    139 *     R(z) \approx \mathrm{Lp}_1 s^2 + \mathrm{Lp}_2 s^4 + \mathrm{Lp}_3 s^6 + \mathrm{Lp}_4 s^8 + \mathrm{Lp}_5 s^{10} + \mathrm{Lp}_6 s^{12} + \mathrm{Lp}_7 s^{14}
    140 *     ```
    141 *
    142 *     and
    143 *
    144 *     ```tex
    145 *     | \mathrm{Lp}_1 s^2 + \ldots + \mathrm{Lp}_7 s^14 - R(z) | \leq 2^{-58.45}
    146 *     ```
    147 *
    148 *     <!-- <note> -->
    149 *
    150 *     The values of \\(Lp1\\) to \\(Lp7\\) may be found in the source.
    151 *
    152 *     <!-- </note> -->
    153 *
    154 *     Note that
    155 *
    156 *     ```tex
    157 *     \begin{align*}
    158 *     2s &= f - sf \\
    159 *        &= f - \frac{f^2}{2} + s \frac{f^2}{2} \\
    160 *     \end{align*}
    161 *     ```
    162 *
    163 *     In order to guarantee error in \\(\ln\\) below \\(1\ \mathrm{ulp}\\), we compute the log by
    164 *
    165 *     ```tex
    166 *     \operatorname{log1p}(f) = f - \biggl(\frac{f^2}{2} - s\biggl(\frac{f^2}{2}+R\biggr)\biggr)
    167 *     ```
    168 *
    169 * 3.  Finally,
    170 *
    171 *     ```tex
    172 *     \begin{align*}
    173 *     \operatorname{log1p}(x) &= k \cdot \mathrm{ln2} + \operatorname{log1p}(f) \\
    174 *     &= k \cdot \mathrm{ln2}_{hi}+\biggl(f-\biggl(\frac{f^2}{2}-\biggl(s\biggl(\frac{f^2}{2}+R\biggr)+k \cdot \mathrm{ln2}_{lo}\biggr)\biggr)\biggr) \\
    175 *     \end{align*}
    176 *     ```
    177 *
    178 *     Here \\(\mathrm{ln2}\\) is split into two floating point numbers:
    179 *
    180 *     ```tex
    181 *     \mathrm{ln2}_{hi} + \mathrm{ln2}_{lo}
    182 *     ```
    183 *
    184 *     where \\(n \cdot \mathrm{ln2}_{hi}\\) is always exact for \\(|n| < 2000\\).
    185 *
    186 *
    187 * ## Special Cases
    188 *
    189 * -   \\(\operatorname{log1p}(x) = \mathrm{NaN}\\) with signal if \\(x < -1\\) (including \\(-\infty\\))
    190 * -   \\(\operatorname{log1p}(+\infty) = +\infty\\)
    191 * -   \\(\operatorname{log1p}(-1) = -\infty\\) with signal
    192 * -   \\(\operatorname{log1p}(\mathrm{NaN})= \mathrm{NaN}\\) with no signal
    193 *
    194 *
    195 * ## Notes
    196 *
    197 * -   According to an error analysis, the error is always less than \\(1\\) ulp (unit in the last place).
    198 *
    199 * -   The hexadecimal values are the intended ones for the used 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.
    200 *
    201 * -   Assuming \\(\ln(x)\\) is accurate, the following algorithm can be used to evaluate \\(\operatorname{log1p}(x)\\) to within a few ULP:
    202 *
    203 *     ```javascript
    204 *     var u = 1.0 + x;
    205 *     if ( u === 1.0 ) {
    206 *         return x;
    207 *     } else {
    208 *         return ln(u) * (x/(u-1.0));
    209 *     }
    210 *     ```
    211 *
    212 *     See HP-15C Advanced Functions Handbook, p.193.
    213 *
    214 *
    215 * @param {number} x - input value
    216 * @returns {number} the natural logarithm of `1+x`
    217 *
    218 * @example
    219 * var v = log1p( 4.0 );
    220 * // returns ~1.609
    221 *
    222 * @example
    223 * var v = log1p( -1.0 );
    224 * // returns -Infinity
    225 *
    226 * @example
    227 * var v = log1p( 0.0 );
    228 * // returns 0.0
    229 *
    230 * @example
    231 * var v = log1p( -0.0 );
    232 * // returns -0.0
    233 *
    234 * @example
    235 * var v = log1p( -2.0 );
    236 * // returns NaN
    237 *
    238 * @example
    239 * var v = log1p( NaN );
    240 * // returns NaN
    241 */
    242 function log1p( x ) {
    243 	var hfsq;
    244 	var hu;
    245 	var y;
    246 	var f;
    247 	var c;
    248 	var s;
    249 	var z;
    250 	var R;
    251 	var u;
    252 	var k;
    253 
    254 	if ( x < -1.0 || isnan( x ) ) {
    255 		return NaN;
    256 	}
    257 	if ( x === -1.0 ) {
    258 		return NINF;
    259 	}
    260 	if ( x === PINF ) {
    261 		return x;
    262 	}
    263 	if ( x === 0.0 ) {
    264 		return x; // handle +-0 (IEEE 754-2008 spec)
    265 	}
    266 	// Set y = |x|:
    267 	if ( x < 0.0 ) {
    268 		y = -x;
    269 	} else {
    270 		y = x;
    271 	}
    272 	// Argument reduction...
    273 	k = 1;
    274 
    275 	// Check if argument reduction is needed and if we can just return a small value approximation requiring less computation but with equivalent accuracy...
    276 	if ( y < SQRT2M1 ) { // if |x| < sqrt(2)-1 => ~0.41422
    277 		if ( y < SMALL ) { // if |x| < 2**-29
    278 			if ( y < TINY ) { // if |x| < 2**-54
    279 				return x;
    280 			}
    281 			// Use a simple two-term Taylor series...
    282 			return x - ( x*x*0.5 );
    283 		}
    284 		// Check if `f=x` can be represented exactly (no need for correction terms), allowing us to bypass argument reduction...
    285 		if ( x > SQRT2HALFM1 ) { // if x > sqrt(2)/2-1 => ~-0.2929
    286 			// -0.2929 < x < 0.41422
    287 			k = 0;
    288 			f = x; // exact
    289 			hu = 1;
    290 		}
    291 	}
    292 	// Address case where `f` cannot be represented exactly...
    293 	if ( k !== 0 ) {
    294 		if ( y < TWO53 ) {
    295 			u = 1.0 + x;
    296 			hu = getHighWord( u );
    297 
    298 			// Bit shift to isolate the exponent and then subtract the bias:
    299 			k = (hu>>20) - FLOAT64_EXPONENT_BIAS;
    300 
    301 			// Correction term...
    302 			if ( k > 0 ) { // positive unbiased exponent
    303 				c = 1.0 - (u-x);
    304 			} else { // nonpositive unbiased exponent
    305 				c = x - (u-1.0);
    306 			}
    307 			c /= u;
    308 		} else {
    309 			u = x;
    310 			hu = getHighWord( u );
    311 
    312 			// Bit shift to isolate the exponent and then subtract the bias:
    313 			k = (hu>>20) - FLOAT64_EXPONENT_BIAS;
    314 
    315 			// Correction term is zero:
    316 			c = 0;
    317 		}
    318 		// Apply a bit mask (0 00000000000 11111111111111111111) to remove the exponent:
    319 		hu &= 0x000fffff; // max value => 1048575
    320 
    321 		// Check if u significand is less than sqrt(2) significand => 0x6a09e => 01101010000010011110
    322 		if ( hu < 434334 ) {
    323 			// Normalize u by setting the exponent to 1023 (bias) => 0x3ff00000 => 0 01111111111 00000000000000000000
    324 			u = setHighWord( u, hu|0x3ff00000 );
    325 		} else {
    326 			k += 1;
    327 
    328 			// Normalize u/2 by setting the exponent to 1022 (bias-1 => 2**-1 = 1/2) => 0x3fe00000 => 0 01111111110 00000000000000000000
    329 			u = setHighWord( u, hu|0x3fe00000 );
    330 
    331 			// Subtract hu significand from next largest hu => 0 00000000001 00000000000000000000 => 0x00100000 => 1048576
    332 			hu = (1048576-hu)>>2;
    333 		}
    334 		f = u - 1.0;
    335 	}
    336 	// Approximation of log1p(f)...
    337 	hfsq = 0.5 * f * f;
    338 	if ( hu === 0 ) { // if |f| < 2**-20
    339 		if ( f === 0.0 ) {
    340 			c += k * LN2_LO;
    341 			return ( k * LN2_HI ) + c;
    342 		}
    343 		R = hfsq * (1.0 - ( TWO_THIRDS*f ) ); // avoid division
    344 		return ( k*LN2_HI ) - ( (R - ( (k*LN2_LO) + c)) - f );
    345 	}
    346 	s = f / (2.0 + f);
    347 	z = s * s;
    348 
    349 	R = z * polyval( z );
    350 
    351 	if ( k === 0 ) {
    352 		return f - ( hfsq - ( s*(hfsq+R) ) );
    353 	}
    354 	return ( k*LN2_HI ) - ( (hfsq - ( (s*(hfsq+R)) + ((k*LN2_LO) + c))) - f );
    355 }
    356 
    357 
    358 // EXPORTS //
    359 
    360 module.exports = log1p;