UNPKG

@stdlib/math-base-special-gammaln

Version:

Natural logarithm of the gamma function.

406 lines (386 loc) 10.2 kB
/** * @license Apache-2.0 * * Copyright (c) 2018 The Stdlib Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * ## Notice * * The following copyright, license, and long comment were part of the original implementation available as part of [FreeBSD]{@link https://svnweb.freebsd.org/base/release/9.3.0/lib/msun/src/e_lgamma_r.c}. The implementation follows the original, but has been modified for JavaScript. * * ```text * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. * * Developed at SunPro, a Sun Microsystems, Inc. business. * Permission to use, copy, modify, and distribute this * software is freely granted, provided that this notice * is preserved. * ``` */ 'use strict'; // MODULES // var isnan = require( '@stdlib/math-base-assert-is-nan' ); var isInfinite = require( '@stdlib/math-base-assert-is-infinite' ); var abs = require( '@stdlib/math-base-special-abs' ); var ln = require( '@stdlib/math-base-special-ln' ); var trunc = require( '@stdlib/math-base-special-trunc' ); var sinpi = require( '@stdlib/math-base-special-sinpi' ); var PI = require( '@stdlib/constants-float64-pi' ); var PINF = require( '@stdlib/constants-float64-pinf' ); var polyvalA1 = require( './polyval_a1.js' ); var polyvalA2 = require( './polyval_a2.js' ); var polyvalR = require( './polyval_r.js' ); var polyvalS = require( './polyval_s.js' ); var polyvalT1 = require( './polyval_t1.js' ); var polyvalT2 = require( './polyval_t2.js' ); var polyvalT3 = require( './polyval_t3.js' ); var polyvalU = require( './polyval_u.js' ); var polyvalV = require( './polyval_v.js' ); var polyvalW = require( './polyval_w.js' ); // VARIABLES // var A1C = 7.72156649015328655494e-02; // 0x3FB3C467E37DB0C8 var A2C = 3.22467033424113591611e-01; // 0x3FD4A34CC4A60FAD var RC = 1.0; var SC = -7.72156649015328655494e-02; // 0xBFB3C467E37DB0C8 var T1C = 4.83836122723810047042e-01; // 0x3FDEF72BC8EE38A2 var T2C = -1.47587722994593911752e-01; // 0xBFC2E4278DC6C509 var T3C = 6.46249402391333854778e-02; // 0x3FB08B4294D5419B var UC = -7.72156649015328655494e-02; // 0xBFB3C467E37DB0C8 var VC = 1.0; var WC = 4.18938533204672725052e-01; // 0x3FDACFE390C97D69 var YMIN = 1.461632144968362245; var TWO52 = 4503599627370496; // 2**52 var TWO58 = 288230376151711744; // 2**58 var TINY = 8.470329472543003e-22; var TC = 1.46163214496836224576e+00; // 0x3FF762D86356BE3F var TF = -1.21486290535849611461e-01; // 0xBFBF19B9BCC38A42 var TT = -3.63867699703950536541e-18; // 0xBC50C7CAA48A971F => TT = -(tail of TF) // MAIN // /** * Evaluates the natural logarithm of the gamma function. * * ## Method * * 1. Argument reduction for \\(0 < x \leq 8\\). Since \\(\Gamma(1+s) = s \Gamma(s)\\), for \\(x \in \[0,8]\\), we may reduce \\(x\\) to a number in \\(\[1.5,2.5]\\) by * * ```tex * \operatorname{lgamma}(1+s) = \ln(s) + \operatorname{lgamma}(s) * ``` * * For example, * * ```tex * \begin{align*} * \operatorname{lgamma}(7.3) &= \ln(6.3) + \operatorname{lgamma}(6.3) \\ * &= \ln(6.3 \cdot 5.3) + \operatorname{lgamma}(5.3) \\ * &= \ln(6.3 \cdot 5.3 \cdot 4.3 \cdot 3.3 \cdot2.3) + \operatorname{lgamma}(2.3) * \end{align*} * ``` * * 2. Compute a polynomial approximation of \\(\mathrm{lgamma}\\) around its minimum (\\(\mathrm{ymin} = 1.461632144968362245\\)) to maintain monotonicity. On the interval \\(\[\mathrm{ymin} - 0.23, \mathrm{ymin} + 0.27]\\) (i.e., \\(\[1.23164,1.73163]\\)), we let \\(z = x - \mathrm{ymin}\\) and use * * ```tex * \operatorname{lgamma}(x) = -1.214862905358496078218 + z^2 \cdot \operatorname{poly}(z) * ``` * * where \\(\operatorname{poly}(z)\\) is a \\(14\\) degree polynomial. * * 3. Compute a rational approximation in the primary interval \\(\[2,3]\\). Let \\( s = x - 2.0 \\). We can thus use the approximation * * ```tex * \operatorname{lgamma}(x) = \frac{s}{2} + s\frac{\operatorname{P}(s)}{\operatorname{Q}(s)} * ``` * * with accuracy * * ```tex * \biggl|\frac{\mathrm{P}}{\mathrm{Q}} - \biggr(\operatorname{lgamma}(x)-\frac{s}{2}\biggl)\biggl| < 2^{-61.71} * ``` * * The algorithms are based on the observation * * ```tex * \operatorname{lgamma}(2+s) = s(1 - \gamma) + \frac{\zeta(2) - 1}{2} s^2 - \frac{\zeta(3) - 1}{3} s^3 + \ldots * ``` * * where \\(\zeta\\) is the zeta function and \\(\gamma = 0.5772156649...\\) is the Euler-Mascheroni constant, which is very close to \\(0.5\\). * * 4. For \\(x \geq 8\\), * * ```tex * \operatorname{lgamma}(x) \approx \biggl(x-\frac{1}{2}\biggr) \ln(x) - x + \frac{\ln(2\pi)}{2} + \frac{1}{12x} - \frac{1}{360x^3} + \ldots * ``` * * which can be expressed * * ```tex * \operatorname{lgamma}(x) \approx \biggl(x-\frac{1}{2}\biggr)(\ln(x)-1)-\frac{\ln(2\pi)-1}{2} + \ldots * ``` * * Let \\(z = \frac{1}{x}\\). We can then use the approximation * * ```tex * f(z) = \operatorname{lgamma}(x) - \biggl(x-\frac{1}{2}\biggr)(\ln(x)-1) * ``` * * by * * ```tex * w = w_0 + w_1 z + w_2 z^3 + w_3 z^5 + \ldots + w_6 z^{11} * ``` * * where * * ```tex * |w - f(z)| < 2^{-58.74} * ``` * * 5. For negative \\(x\\), since * * ```tex * -x \Gamma(-x) \Gamma(x) = \frac{\pi}{\sin(\pi x)} * ``` * * where \\(\Gamma\\) is the gamma function, we have * * ```tex * \Gamma(x) = \frac{\pi}{\sin(\pi x)(-x)\Gamma(-x)} * ``` * * Since \\(\Gamma(-x)\\) is positive, * * ```tex * \operatorname{sign}(\Gamma(x)) = \operatorname{sign}(\sin(\pi x)) * ``` * * for \\(x < 0\\). Hence, for \\(x < 0\\), * * ```tex * \mathrm{signgam} = \operatorname{sign}(\sin(\pi x)) * ``` * * and * * ```tex * \begin{align*} * \operatorname{lgamma}(x) &= \ln(|\Gamma(x)|) \\ * &= \ln\biggl(\frac{\pi}{|x \sin(\pi x)|}\biggr) - \operatorname{lgamma}(-x) * \end{align*} * ``` * * <!-- <note> --> * * Note that one should avoid computing \\(\pi (-x)\\) directly in the computation of \\(\sin(\pi (-x))\\). * * <!-- </note> --> * * ## Special Cases * * ```tex * \begin{align*} * \operatorname{lgamma}(2+s) &\approx s (1-\gamma) & \mathrm{for\ tiny\ s} \\ * \operatorname{lgamma}(x) &\approx -\ln(x) & \mathrm{for\ tiny\ x} \\ * \operatorname{lgamma}(1) &= 0 & \\ * \operatorname{lgamma}(2) &= 0 & \\ * \operatorname{lgamma}(0) &= \infty & \\ * \operatorname{lgamma}(\infty) &= \infty & \\ * \operatorname{lgamma}(-\mathrm{integer}) &= \pm \infty * \end{align*} * ``` * * @param {number} x - input value * @returns {number} function value * * @example * var v = gammaln( 1.0 ); * // returns 0.0 * * @example * var v = gammaln( 2.0 ); * // returns 0.0 * * @example * var v = gammaln( 4.0 ); * // returns ~1.792 * * @example * var v = gammaln( -0.5 ); * // returns ~1.266 * * @example * var v = gammaln( 0.5 ); * // returns ~0.572 * * @example * var v = gammaln( 0.0 ); * // returns Infinity * * @example * var v = gammaln( NaN ); * // returns NaN */ function gammaln( x ) { var isNegative; var nadj; var flg; var p3; var p2; var p1; var p; var q; var t; var w; var y; var z; var r; // Special cases: NaN, +-infinity if ( isnan( x ) || isInfinite( x ) ) { return x; } // Special case: 0 if ( x === 0.0 ) { return PINF; } if ( x < 0.0 ) { isNegative = true; x = -x; } else { isNegative = false; } // If |x| < 2**-70, return -ln(|x|) if ( x < TINY ) { return -ln( x ); } if ( isNegative ) { // If |x| >= 2**52, must be -integer if ( x >= TWO52 ) { return PINF; } t = sinpi( x ); if ( t === 0.0 ) { return PINF; } nadj = ln( PI / abs( t*x ) ); } // If x equals 1 or 2, return 0 if ( x === 1.0 || x === 2.0 ) { return 0.0; } // If x < 2, use lgamma(x) = lgamma(x+1) - log(x) if ( x < 2.0 ) { if ( x <= 0.9 ) { r = -ln( x ); // 0.7316 <= x <= 0.9 if ( x >= ( YMIN - 1.0 + 0.27 ) ) { y = 1.0 - x; flg = 0; } // 0.2316 <= x < 0.7316 else if ( x >= (YMIN - 1.0 - 0.27) ) { y = x - (TC - 1.0); flg = 1; } // 0 < x < 0.2316 else { y = x; flg = 2; } } else { r = 0.0; // 1.7316 <= x < 2 if ( x >= (YMIN + 0.27) ) { y = 2.0 - x; flg = 0; } // 1.2316 <= x < 1.7316 else if ( x >= (YMIN - 0.27) ) { y = x - TC; flg = 1; } // 0.9 < x < 1.2316 else { y = x - 1.0; flg = 2; } } switch ( flg ) { // eslint-disable-line default-case case 0: z = y * y; p1 = A1C + (z*polyvalA1( z )); p2 = z * (A2C + (z*polyvalA2( z ))); p = (y*p1) + p2; r += ( p - (0.5*y) ); break; case 1: z = y * y; w = z * y; p1 = T1C + (w*polyvalT1( w )); p2 = T2C + (w*polyvalT2( w )); p3 = T3C + (w*polyvalT3( w )); p = (z*p1) - (TT - (w*(p2+(y*p3)))); r += ( TF + p ); break; case 2: p1 = y * (UC + (y*polyvalU( y ))); p2 = VC + (y*polyvalV( y )); r += (-0.5*y) + (p1/p2); break; } } // 2 <= x < 8 else if ( x < 8.0 ) { flg = trunc( x ); y = x - flg; p = y * (SC + (y*polyvalS( y ))); q = RC + (y*polyvalR( y )); r = (0.5*y) + (p/q); z = 1.0; // gammaln(1+s) = ln(s) + gammaln(s) switch ( flg ) { // eslint-disable-line default-case case 7: z *= y + 6.0; /* Falls through */ case 6: z *= y + 5.0; /* Falls through */ case 5: z *= y + 4.0; /* Falls through */ case 4: z *= y + 3.0; /* Falls through */ case 3: z *= y + 2.0; r += ln( z ); } } // 8 <= x < 2**58 else if ( x < TWO58 ) { t = ln( x ); z = 1.0 / x; y = z * z; w = WC + (z*polyvalW( y )); r = ((x-0.5)*(t-1.0)) + w; } // 2**58 <= x <= Inf else { r = x * ( ln(x)-1.0 ); } if ( isNegative ) { r = nadj - r; } return r; } // EXPORTS // module.exports = gammaln;