time-to-botec

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

erfinv.js (6154B)


      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 original C++ code and copyright notice are from the [Boost library]{@link http://www.boost.org/doc/libs/1_48_0/boost/math/special_functions/detail/erf_inv.hpp}. This implementation follows the original, but has been modified for JavaScript.
     22 *
     23 * ```text
     24 * (C) Copyright John Maddock 2006.
     25 *
     26 * Use, modification and distribution are subject to the
     27 * Boost Software License, Version 1.0. (See accompanying file
     28 * LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
     29 * ```
     30 */
     31 
     32 'use strict';
     33 
     34 // MODULES //
     35 
     36 var isnan = require( './../../../../base/assert/is-nan' );
     37 var sqrt = require( './../../../../base/special/sqrt' );
     38 var ln = require( './../../../../base/special/ln' );
     39 var PINF = require( '@stdlib/constants/float64/pinf' );
     40 var NINF = require( '@stdlib/constants/float64/ninf' );
     41 var rationalFcnR1 = require( './rational_p1q1.js' );
     42 var rationalFcnR2 = require( './rational_p2q2.js' );
     43 var rationalFcnR3 = require( './rational_p3q3.js' );
     44 var rationalFcnR4 = require( './rational_p4q4.js' );
     45 var rationalFcnR5 = require( './rational_p5q5.js' );
     46 
     47 
     48 // VARIABLES //
     49 
     50 var Y1 = 8.91314744949340820313e-2;
     51 var Y2 = 2.249481201171875;
     52 var Y3 = 8.07220458984375e-1;
     53 var Y4 = 9.3995571136474609375e-1;
     54 var Y5 = 9.8362827301025390625e-1;
     55 
     56 
     57 // MAIN //
     58 
     59 /**
     60 * Evaluates the inverse error function.
     61 *
     62 * ## Method
     63 *
     64 * 1.  For \\(|x| \leq 0.5\\), we evaluate the inverse error function using the rational approximation
     65 *
     66 *     ```tex
     67 *     \operatorname{erf^{-1}}(x) = x(x+10)(\mathrm{Y} + \operatorname{R}(x))
     68 *     ```
     69 *
     70 *     where \\(Y\\) is a constant and \\(\operatorname{R}(x)\\) is optimized for a low absolute error compared to \\(|Y|\\).
     71 *
     72 *     <!-- <note> -->
     73 *
     74 *     Max error \\(2.001849\mbox{e-}18\\). Maximum deviation found (error term at infinite precision) \\(8.030\mbox{e-}21\\).
     75 *
     76 *     <!-- </note> -->
     77 *
     78 * 2.  For \\(0.5 > 1-|x| \geq 0\\), we evaluate the inverse error function using the rational approximation
     79 *
     80 *     ```tex
     81 *     \operatorname{erf^{-1}} = \frac{\sqrt{-2 \cdot \ln(1-x)}}{\mathrm{Y} + \operatorname{R}(1-x)}
     82 *     ```
     83 *
     84 *     where \\(Y\\) is a constant, and \\(\operatorname{R}(q)\\) is optimized for a low absolute error compared to \\(Y\\).
     85 *
     86 *     <!-- <note> -->
     87 *
     88 *     Max error \\(7.403372\mbox{e-}17\\). Maximum deviation found (error term at infinite precision) \\(4.811\mbox{e-}20\\).
     89 *
     90 *     <!-- </note> -->
     91 *
     92 * 3.  For \\(1-|x| < 0.25\\), we have a series of rational approximations all of the general form
     93 *
     94 *     ```tex
     95 *     p = \sqrt{-\ln(1-x)}
     96 *     ```
     97 *
     98 *     Accordingly, the result is given by
     99 *
    100 *     ```tex
    101 *     \operatorname{erf^{-1}}(x) = p(\mathrm{Y} + \operatorname{R}(p-B))
    102 *     ```
    103 *
    104 *     where \\(Y\\) is a constant, \\(B\\) is the lowest value of \\(p\\) for which the approximation is valid, and \\(\operatorname{R}(x-B)\\) is optimized for a low absolute error compared to \\(Y\\).
    105 *
    106 *     <!-- <note> -->
    107 *
    108 *     Almost all code will only go through the first or maybe second approximation.  After that we are dealing with very small input values.
    109 *
    110 *     -   If \\(p < 3\\), max error \\(1.089051\mbox{e-}20\\).
    111 *     -   If \\(p < 6\\), max error \\(8.389174\mbox{e-}21\\).
    112 *     -   If \\(p < 18\\), max error \\(1.481312\mbox{e-}19\\).
    113 *     -   If \\(p < 44\\), max error \\(5.697761\mbox{e-}20\\).
    114 *     -   If \\(p \geq 44\\), max error \\(1.279746\mbox{e-}20\\).
    115 *
    116 *     <!-- </note> -->
    117 *
    118 *     <!-- <note> -->
    119 *
    120 *     The Boost library can accommodate \\(80\\) and \\(128\\) bit long doubles. JavaScript only supports a \\(64\\) bit double (IEEE 754). Accordingly, the smallest \\(p\\) (in JavaScript at the time of this writing) is \\(\sqrt{-\ln(\sim5\mbox{e-}324)} = 27.284429111150214\\).
    121 *
    122 *     <!-- </note> -->
    123 *
    124 *
    125 * @param {number} x - input value
    126 * @returns {number} function value
    127 *
    128 * @example
    129 * var y = erfinv( 0.5 );
    130 * // returns ~0.4769
    131 *
    132 * @example
    133 * var y = erfinv( 0.8 );
    134 * // returns ~0.9062
    135 *
    136 * @example
    137 * var y = erfinv( 0.0 );
    138 * // returns 0.0
    139 *
    140 * @example
    141 * var y = erfinv( -0.0 );
    142 * // returns -0.0
    143 *
    144 * @example
    145 * var y = erfinv( -1.0 );
    146 * // returns -Infinity
    147 *
    148 * @example
    149 * var y = erfinv( 1.0 );
    150 * // returns Infinity
    151 *
    152 * @example
    153 * var y = erfinv( NaN );
    154 * // returns NaN
    155 */
    156 function erfinv( x ) {
    157 	var sign;
    158 	var ax;
    159 	var qs;
    160 	var q;
    161 	var g;
    162 	var r;
    163 
    164 	// Special case: NaN
    165 	if ( isnan( x ) ) {
    166 		return NaN;
    167 	}
    168 	// Special case: 1
    169 	if ( x === 1.0 ) {
    170 		return PINF;
    171 	}
    172 	// Special case: -1
    173 	if ( x === -1.0 ) {
    174 		return NINF;
    175 	}
    176 	// Special case: +-0
    177 	if ( x === 0.0 ) {
    178 		return x;
    179 	}
    180 	// Special case: |x| > 1 (range error)
    181 	if ( x > 1.0 || x < -1.0 ) {
    182 		return NaN;
    183 	}
    184 	// Argument reduction (reduce to interval [0,1]). If `x` is negative, we can safely negate the value, taking advantage of the error function being an odd function; i.e., `erf(-x) = -erf(x)`.
    185 	if ( x < 0.0 ) {
    186 		sign = -1.0;
    187 		ax = -x;
    188 	} else {
    189 		sign = 1.0;
    190 		ax = x;
    191 	}
    192 	q = 1.0 - ax;
    193 
    194 	// |x| <= 0.5
    195 	if ( ax <= 0.5 ) {
    196 		g = ax * ( ax + 10.0 );
    197 		r = rationalFcnR1( ax );
    198 		return sign * ( (g*Y1) + (g*r) );
    199 	}
    200 	// 1-|x| >= 0.25
    201 	if ( q >= 0.25 ) {
    202 		g = sqrt( -2.0 * ln(q) );
    203 		q -= 0.25;
    204 		r = rationalFcnR2( q );
    205 		return sign * ( g / (Y2+r) );
    206 	}
    207 	q = sqrt( -ln( q ) );
    208 
    209 	// q < 3
    210 	if ( q < 3.0 ) {
    211 		qs = q - 1.125;
    212 		r = rationalFcnR3( qs );
    213 		return sign * ( (Y3*q) + (r*q) );
    214 	}
    215 	// q < 6
    216 	if ( q < 6.0 ) {
    217 		qs = q - 3.0;
    218 		r = rationalFcnR4( qs );
    219 		return sign * ( (Y4*q) + (r*q) );
    220 	}
    221 	// q < 18
    222 	qs = q - 6.0;
    223 	r = rationalFcnR5( qs );
    224 	return sign * ( (Y5*q) + (r*q) );
    225 }
    226 
    227 
    228 // EXPORTS //
    229 
    230 module.exports = erfinv;