/** Macros: GAMMA = Γ INTEGRAL = ∫ */ module statistical; import std.math; private import std.stdio; import realtest; /*********************************** * Evaluate polynamial at x, using Horner's rule. * Returns: coeffs[0] + coeffs[1]*x + coeffs[2]*x*x + ... * * Completely unoptimised. */ real poly(real x, real[] coeffs) { assert(coeffs.length>0); real p=coeffs[$-1]; for (int i=coeffs.length-2; i>=0; --i) { p *= x; p +=coeffs[i]; } return p; } unittest { real x = 3.1; static real pp[] = [56.1, 32.7, 6]; assert( poly(x,pp) ==(56.1L + (32.7L + 6L*x)*x) ); } //------------------------------------------------------------------ const real SQRT2PI = 2.50662827463100050242E0L; // sqrt(2pi) // exp(tgamma(x)) == inf if x>MAXGAMMA const real MAXGAMMA = 1755.455L; // Polynomial approximations for gamma and loggamma. static real GammaNumeratorCoeffs[] = [ 0x1p+0, // 1 0x1.acf42d903366539ep-1, // 0.83780043015731267283 0x1.73a991c8475f1aeap-2, // 0.36295154366402391688 0x1.c7e918751d6b2a92p-4, // 0.1113062816019361559 0x1.86d162cca32cfe86p-6, // 0.023853632434611082525 0x1.0c378e2e6eaf7cd8p-8, // 0.0040926668283940355009 0x1.dc5c66b7d05feb54p-12, // 0.00045429319606080091555 0x1.616457b47e448694p-15 // 4.2127604874716220134e-05 ]; static real GammaDenominatorCoeffs[] = [ 0x1p+0, // 1 0x1.a8f9faae5d8fc8bp-2, // 0.41501609505884554346 -0x1.cb7895a6756eebdep-3, // -0.22435109056703291645 -0x1.7b9bab006d30652ap-5, // -0.046338876712445342138 0x1.c671af78f312082ep-6, // 0.027737065658400729792 -0x1.a11ebbfaf96252dcp-11, // -0.00079559336824947383209 -0x1.447b4d2230a77ddap-10, // -0.0012377992466531522311 0x1.ec1d45bb85e06696p-13, // 0.00023465840591606352443 -0x1.d4ce24d05bd0a8e6p-17 // -1.3971485174761704409e-05 ]; static real SmallStirlingCoeffs[] = [ 0x1.55555555555543aap-4, // 0.083333333333333318004 0x1.c71c71c720dd8792p-9, // 0.0034722222222300753277 -0x1.5f7268f0b5907438p-9, // -0.0026813271618763044182 -0x1.e13cd410e0477de6p-13, // -0.00022947197478731854057 0x1.9b0f31643442616ep-11, // 0.00078403348427447530038 0x1.2527623a3472ae08p-14, // 6.9893322606231931717e-05 -0x1.37f6bc8ef8b374dep-11, // -0.00059502375540563301557 -0x1.8c968886052b872ap-16, // -2.3638488095017590616e-05 0x1.76baa9c6d3eeddbcp-11 // 0.0007147391378143610789 ]; static real LargeStirlingCoeffs[] = [ 1.0L, 8.33333333333333333333E-2L, 3.47222222222222222222E-3L, -2.68132716049382716049E-3L, -2.29472093621399176955E-4L, 7.84039221720066627474E-4L, 6.97281375836585777429E-5L ]; static real GammaSmallCoeffs[] = [ 0x1p+0, // 1 0x1.2788cfc6fb618f52p-1, // 0.57721566490153286082 -0x1.4fcf4026afa2f7ecp-1, // -0.65587807152025406846 -0x1.5815e8fa24d7e306p-5, // -0.042002635034033440541 0x1.5512320aea2ad71ap-3, // 0.16653861137208052067 -0x1.59af0fb9d82e216p-5, // -0.042197733607059154702 -0x1.3b4b61d3bfdf244ap-7, // -0.0096220233604062716456 0x1.d9358e9d9d69fd34p-8, // 0.0072205994780369096722 -0x1.38fc4bcbada775d6p-10 // -0.0011939450513815100956 ]; static real GammaSmallNegCoeffs[] = [ -0x1p+0, // -1 0x1.2788cfc6fb618f54p-1, // 0.57721566490153286086 0x1.4fcf4026afa2bc4cp-1, // 0.65587807152025365473 -0x1.5815e8fa2468fec8p-5, // -0.042002635034021129105 -0x1.5512320baedaf4b6p-3, // -0.16653861139444135193 -0x1.59af0fa283baf07ep-5, // -0.042197733437311917216 0x1.3b4a70de31e05942p-7, // 0.0096219111550359767339 0x1.d9398be3bad13136p-8, // 0.0072208372618931703258 0x1.291b73ee05bcbba2p-10 // 0.001133374167243894382 ]; static real logGammaStirlingCoeffs[] = [ 0x1.5555555555553f98p-4, // 0.083333333333333314473 -0x1.6c16c16c07509b1p-9, // -0.0027777777777503496034 0x1.a01a012461cbf1e4p-11, // 0.00079365077958550707556 -0x1.3813089d3f9d164p-11, // -0.00059523458517656885149 0x1.b911a92555a277b8p-11, // 0.00084127232973224980805 -0x1.ed0a7b4206087b22p-10, // -0.0018808019381193769072 0x1.402523859811b308p-8 // 0.0048850261424322707812 ]; static real logGammaNumerator[] = [ -0x1.0edd25913aaa40a2p+23, // -8875666.7836507038022 -0x1.31c6ce2e58842d1ep+24, // -20039374.181038151756 -0x1.f015814039477c3p+23, // -16255680.62543700591 -0x1.74ffe40c4b184b34p+22, // -6111225.0120052143001 -0x1.0d9c6d08f9eab55p+20, // -1104326.8146914642612 -0x1.54c6b71935f1fc88p+16, // -87238.715228435114593 -0x1.0e761b42932b2aaep+11 // -2163.6908276438128575 ]; static real logGammaDenominator[] = [ -0x1.4055572d75d08c56p+24, // -20993367.177578958762 -0x1.deeb6013998e4d76p+24, // -31386464.076561826621 -0x1.106f7cded5dcc79ep+24, // -17854332.870450781569 -0x1.25e17184848c66d2p+22, // -4814940.3794118821866 -0x1.301303b99a614a0ap+19, // -622744.11640662195015 -0x1.09e76ab41ae965p+15, // -34035.708405343046707 -0x1.00f95ced9e5f54eep+9, // -513.94814844353701437 0x1p+0 // 1 ]; /* Helper function: Gamma function computed by Stirling's formula. Stirling's formula for the gamma function is: $(GAMMA)(x) = sqrt(2 π) xx-0.5 exp(-x) (1 + 1/x P(1/x)) */ real gammaStirling(real x) { real w = 1.0L/x; real y = exp(x); if ( x > 1024.0L ) { // For large x, use rational coefficients from the analytical expansion. w = poly(w, LargeStirlingCoeffs); // Avoid overflow in pow() real v = pow( x, 0.5L * x - 0.25L ); y = v * (v / y); } else { w = 1.0L + w * poly( w, SmallStirlingCoeffs); y = pow( x, x - 0.5L ) / y; } y = SQRT2PI * y * w; return y; } /***************************************************** * The Gamma function, $(GAMMA)(x) * * Generalizes the factorial function to real and complex numbers. * Like x!, $(GAMMA)(x+1) = x*$(GAMMA)(x). * * Mathematically, if z.re > 0 then * $(GAMMA)(z) =$(INTEGRAL)0&infintz-1e-tdt * * This function is equivalent to tgamma() in the C programming language. * * * *
Special Values
x $(GAMMA)(x) invalid? *
NAN NAN yes *
±0.0 ±∞ yes *
integer > 0 (x-1)! no *
integer < 0 NAN yes *
+∞ +∞ no *
-∞ NAN yes *
* * References: * cephes, http://en.wikipedia.org/wiki/Gamma_function * */ real tgamma(real x) { /* Author: Don Clugston. Based on code from the CEPHES library. * * Arguments |x| <= 13 are reduced by recurrence and the function * approximated by a rational function of degree 7/8 in the * interval (2,3). Large arguments are handled by Stirling's * formula. Large negative arguments are made positive using * a reflection formula. */ real q, z; if (isnan(x)) return x; if (x==-x.infinity) return real.nan; if ( fabs(x) > MAXGAMMA ) return real.infinity; if (x==0) return 1.0/x; // +- infinity depending on sign of x, create an exception. q = fabs(x); if ( q > 13.0L ) { // Large arguments are handled by Stirling's // formula. Large negative arguments are made positive using // the reflection formula. if ( x < 0.0L ) { int sgngam = 1; // sign of gamma. real p = floor(q); if ( p == q ) return real.nan; // poles for all integers <0. int intpart = cast(int)(p); if ( (intpart & 1) == 0 ) sgngam = -1; z = q - p; if ( z > 0.5L ) { p += 1.0L; z = q - p; } z = q * sin( PI * z ); z = fabs(z) * gammaStirling(q); if ( z <= PI/real.max ) return sgngam * real.infinity; return sgngam * PI/z; } else { return gammaStirling(x); } } // Arguments |x| <= 13 are reduced by recurrence and the function // approximated by a rational function of degree 7/8 in the // interval (2,3). z = 1.0L; while ( x >= 3.0L ) { x -= 1.0L; z *= x; } while ( x < -0.03125L ) { z /= x; x += 1.0L; } if ( x <= 0.03125L ) { if ( x == 0.0L ) return real.nan; else { if ( x < 0.0L ) { x = -x; return z / (x * poly( x, GammaSmallNegCoeffs )); } else { return z / (x * poly( x, GammaSmallCoeffs )); } } } while ( x < 2.0L ) { z /= x; x += 1.0L; } if ( x == 2.0L ) return z; x -= 2.0L; return z * poly( x, GammaNumeratorCoeffs ) / poly( x, GammaDenominatorCoeffs ); } unittest { // gamma(n) = factorial(n-1) if n is an integer. double fact=1.0L; for (int i=1; factreal.mant_dig-15); //writefln(i, " %a ---> %a %a ", i*1.0L, tgamma(i*1.0L), fact, feqrel(tgamma(i*1.0L), fact)); fact*=(i*1.0L); } assert(tgamma(0.0)==real.infinity); assert(tgamma(-0.0)==-real.infinity); assert(isnan(tgamma(-1.0))); assert(isnan(tgamma(real.nan))); assert(tgamma(real.infinity)==real.infinity); assert(isnan(tgamma(-real.infinity))); assert(tgamma(real.min*real.epsilon)==real.infinity); assert(feqrel(tgamma(0.5),sqrt(PI))>real.mant_dig-3); } /***************************************************** * Natural logarithm of gamma function. * * Returns the base e (2.718...) logarithm of the absolute * value of the gamma function of the argument. * * For reals, lgamma is equivalent to log(fabs(tgamma(x)). * * * *
Special Values
x log$(GAMMA)(x) invalid? *
NaN NaN yes *
integer <= 0 +∞ yes *
1, 2 +0.0 no *
±∞ +∞ no *
* */ real lgamma(real x) { /* Author: Don Clugston. Based on code from the CEPHES library. * * For arguments greater than 33, the logarithm of the gamma * function is approximated by the logarithmic version of * Stirling's formula using a polynomial approximation of * degree 4. Arguments between -33 and +33 are reduced by * recurrence to the interval [2,3] of a rational approximation. * The cosecant reflection formula is employed for arguments * less than -33. */ real p, q, w, z, f, nx; if (isnan(x)) return x; if (fabs(x)==x.infinity) return x.infinity; if( x < -34.0L ) { q = -x; w = lgamma(q); real p = floor(q); if ( p == q ) return real.infinity; int intpart = cast(int)(p); real sgngam = 1; if ( (intpart & 1) == 0 ) sgngam = -1; z = q - p; if ( z > 0.5L ) { p += 1.0L; z = p - q; } z = q * sin( PI * z ); if ( z == 0.0L ) return sgngam * real.infinity; /* z = LOGPI - logl( z ) - w; */ z = log( PI/z ) - w; return( z ); } if( x < 13.0L ) { z = 1.0L; nx = floor( x + 0.5L ); f = x - nx; while ( x >= 3.0L ) { nx -= 1.0L; x = nx + f; z *= x; } while ( x < 2.0L ) { if( fabs(x) <= 0.03125 ) { if ( x == 0.0L ) return real.infinity; if ( x < 0.0L ) { x = -x; q = z / (x * poly( x, GammaSmallNegCoeffs)); } else q = z / (x * poly( x, GammaSmallCoeffs)); return log( fabs(q) ); } z /= nx + f; nx += 1.0L; x = nx + f; } z = fabs(z); if ( x == 2.0L ) return log(z); x = (nx - 2.0L) + f; p = x * poly( x, logGammaNumerator ) / poly( x, logGammaDenominator); return ( log(z) + p ); } //const real MAXLGM = 1.04848146839019521116e+4928L; // if( x > MAXLGM ) return sgngaml * real.infinity; /* log( sqrt( 2*pi ) ) */ const real LOGSQRT2PI = 0.91893853320467274178L; q = ( x - 0.5L ) * log(x) - x + LOGSQRT2PI; if (x > 1.0e10L) return q; p = 1.0L/(x*x); q += poly( p, logGammaStirlingCoeffs ) / x; return q ; } unittest { assert(isnan(lgamma(real.nan))); assert(lgamma(real.infinity)==real.infinity); assert(lgamma(-1.0)==real.infinity); assert(lgamma(0.0)==real.infinity); assert(isPosZero(lgamma(1.0L))); assert(isPosZero(lgamma(2.0L))); // x, correct loggamma(x), correct d/dx loggamma(x). static real[] testpoints = [ 8.0L, 8.525146484375L + 1.48766904143001655310E-5, 2.01564147795560999654E0L, 8.99993896484375e-1L, 6.6375732421875e-2L + 5.11505711292524166220E-6L, -7.54938684259372234258E-1, 7.31597900390625e-1L, 2.2369384765625e-1 + 5.21506341809849792422E-6L, -1.13355566660398608343E0L, 2.31639862060546875e-1L, 1.3686676025390625L + 1.12609441752996145670E-5L, -4.56670961813812679012E0, 1.73162841796875L, -8.88214111328125e-2L + 3.36207740803753034508E-6L, 2.33339034686200586920E-1L, 1.23162841796875L, -9.3902587890625e-2L + 1.28765089229009648104E-5L, -2.49677345775751390414E-1L, 7.3786976294838206464e19L, 3.301798506038663053312e21L - 1.656137564136932662487046269677E5L, 4.57477139169563904215E1L, 1.08420217248550443401E-19L, 4.36682586669921875e1L + 1.37082843669932230418E-5L, -9.22337203685477580858E18L, // 1.0L, 0.0L, -5.77215664901532860607E-1L, // 2.0L, 0.0L, 4.22784335098467139393E-1L, -0.5L, 1.2655029296875L + 9.19379714539648894580E-6L, 3.64899739785765205590E-2L, -1.5L, 8.6004638671875e-1L + 6.28657731014510932682E-7L, 7.03156640645243187226E-1L, -2.5L, -5.6243896484375E-2L + 1.79986700949327405470E-7, 1.10315664064524318723E0L, -3.5L, -1.30902099609375L + 1.43111007079536392848E-5L, 1.38887092635952890151E0L ]; // TODO: test derivatives as well. for (int i=0; i real.mant_dig-5); } static real logabsgamma(real x) { // For poles, tgamma(x) returns nan, but lgamma() returns infinity. if (x<0 && x==floor(x)) return real.infinity; return log(fabs(tgamma(x))); } static real exploggamma(real x) { return exp(lgamma(x)); } static real absgamma(real x) { if (x<0 && x==floor(x)) return real.infinity; return fabs(tgamma(x)); } // Check that loggamma(x) = log(gamma(x)) provided x is not -1, -2, -3, ... assert(consistencyTwoFuncs(&lgamma, &logabsgamma, -1000, 1700) > real.mant_dig-7); assert(consistencyTwoFuncs(&exploggamma, &absgamma, -2000, real.infinity) > real.mant_dig-16); } int main() { return 0; }