root/trunk/mathextra/etcgamma.d

Revision 79, 14.8 kB (checked in by Don Clugston, 2 years ago)

Final changes to whitespace in etcgamma.

Line 
1 /**
2  * Implementation of lgamma() and tgamma() for those C systems that are missing it.
3  *
4 Macros:
5  *  TABLE_SV = <table border=1 cellpadding=4 cellspacing=0>
6  *      <caption>Special Values</caption>
7  *      $0</table>
8  *  SVH = $(TR $(TH $1) $(TH $2))
9  *  SV  = $(TR $(TD $1) $(TD $2))
10  *  GAMMA =  &#915;
11  *  INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
12  *  POWER = $1<sup>$2</sup>
13  *  NAN = $(RED NAN)
14  */
15 module mathextra.etcgamma;
16 /* Cephes code Copyright 1994 by Stephen L. Moshier
17  * Converted to D and enhanced by Don Clugston; changes placed into the public domain.
18  */
19 private import std.math;
20 debug import std.stdio;
21
22 unittest {
23     debug writefln("--- UnitTest: " __FILE__ " ---");
24 } 
25
26 //------------------------------------------------------------------
27 private {
28
29 const real SQRT2PI = 2.50662827463100050242E0L; // sqrt(2pi)
30 // exp(gamma(x)) == inf if x>MAXGAMMA
31 const real MAXGAMMA = 1755.455L;
32
33 // Polynomial approximations for gamma and loggamma.
34
35 const real GammaNumeratorCoeffs[] = [
36     0x1p+0,                  // 1
37     0x1.acf42d903366539ep-1,     // 0.83780043015731267283
38     0x1.73a991c8475f1aeap-2,     // 0.36295154366402391688
39     0x1.c7e918751d6b2a92p-4,     // 0.1113062816019361559
40     0x1.86d162cca32cfe86p-6,     // 0.023853632434611082525
41     0x1.0c378e2e6eaf7cd8p-8,     // 0.0040926668283940355009
42     0x1.dc5c66b7d05feb54p-12, // 0.00045429319606080091555
43     0x1.616457b47e448694p-15     // 4.2127604874716220134e-05
44 ];
45
46 const real GammaDenominatorCoeffs[] = [
47     0x1p+0,                   // 1
48     0x1.a8f9faae5d8fc8bp-2,   // 0.41501609505884554346
49     -0x1.cb7895a6756eebdep-3,  // -0.22435109056703291645
50     -0x1.7b9bab006d30652ap-5,  // -0.046338876712445342138
51     0x1.c671af78f312082ep-6,      // 0.027737065658400729792
52     -0x1.a11ebbfaf96252dcp-11, // -0.00079559336824947383209
53     -0x1.447b4d2230a77ddap-10, // -0.0012377992466531522311
54     0x1.ec1d45bb85e06696p-13,  // 0.00023465840591606352443
55     -0x1.d4ce24d05bd0a8e6p-17  // -1.3971485174761704409e-05
56 ];
57
58 const real SmallStirlingCoeffs[] = [
59     0x1.55555555555543aap-4,      // 0.083333333333333318004
60     0x1.c71c71c720dd8792p-9,      // 0.0034722222222300753277
61     -0x1.5f7268f0b5907438p-9,  // -0.0026813271618763044182
62     -0x1.e13cd410e0477de6p-13, // -0.00022947197478731854057
63     0x1.9b0f31643442616ep-11,  // 0.00078403348427447530038
64     0x1.2527623a3472ae08p-14,  // 6.9893322606231931717e-05
65     -0x1.37f6bc8ef8b374dep-11, // -0.00059502375540563301557
66     -0x1.8c968886052b872ap-16, // -2.3638488095017590616e-05
67     0x1.76baa9c6d3eeddbcp-11      // 0.0007147391378143610789
68 ];
69
70 const real LargeStirlingCoeffs[] = [
71     1.0L,
72     8.33333333333333333333E-2L,
73     3.47222222222222222222E-3L,
74     -2.68132716049382716049E-3L,
75     -2.29472093621399176955E-4L,
76     7.84039221720066627474E-4L,
77     6.97281375836585777429E-5L
78 ];
79
80 const real GammaSmallCoeffs[] = [
81     0x1p+0,                  // 1
82     0x1.2788cfc6fb618f52p-1,     // 0.57721566490153286082
83     -0x1.4fcf4026afa2f7ecp-1, // -0.65587807152025406846
84     -0x1.5815e8fa24d7e306p-5, // -0.042002635034033440541
85     0x1.5512320aea2ad71ap-3,     // 0.16653861137208052067
86     -0x1.59af0fb9d82e216p-5,     // -0.042197733607059154702
87     -0x1.3b4b61d3bfdf244ap-7, // -0.0096220233604062716456
88     0x1.d9358e9d9d69fd34p-8,     // 0.0072205994780369096722
89     -0x1.38fc4bcbada775d6p-10 // -0.0011939450513815100956
90 ];
91
92 const real GammaSmallNegCoeffs[] = [
93     -0x1p+0,                     // -1
94     0x1.2788cfc6fb618f54p-1,     // 0.57721566490153286086
95     0x1.4fcf4026afa2bc4cp-1,     // 0.65587807152025365473
96     -0x1.5815e8fa2468fec8p-5, // -0.042002635034021129105
97     -0x1.5512320baedaf4b6p-3, // -0.16653861139444135193
98     -0x1.59af0fa283baf07ep-5, // -0.042197733437311917216
99     0x1.3b4a70de31e05942p-7,     // 0.0096219111550359767339
100     0x1.d9398be3bad13136p-8,     // 0.0072208372618931703258
101     0x1.291b73ee05bcbba2p-10     // 0.001133374167243894382
102 ];
103
104 const real logGammaStirlingCoeffs[] = [
105     0x1.5555555555553f98p-4,      // 0.083333333333333314473
106     -0x1.6c16c16c07509b1p-9,      // -0.0027777777777503496034
107     0x1.a01a012461cbf1e4p-11,  // 0.00079365077958550707556
108     -0x1.3813089d3f9d164p-11,  // -0.00059523458517656885149
109     0x1.b911a92555a277b8p-11,  // 0.00084127232973224980805
110     -0x1.ed0a7b4206087b22p-10, // -0.0018808019381193769072
111     0x1.402523859811b308p-8   // 0.0048850261424322707812
112 ];
113
114 const real logGammaNumerator[] = [
115     -0x1.0edd25913aaa40a2p+23, // -8875666.7836507038022
116     -0x1.31c6ce2e58842d1ep+24, // -20039374.181038151756
117     -0x1.f015814039477c3p+23,  // -16255680.62543700591
118     -0x1.74ffe40c4b184b34p+22, // -6111225.0120052143001
119     -0x1.0d9c6d08f9eab55p+20,  // -1104326.8146914642612
120     -0x1.54c6b71935f1fc88p+16, // -87238.715228435114593
121     -0x1.0e761b42932b2aaep+11  // -2163.6908276438128575
122 ];
123
124 const real logGammaDenominator[] = [
125     -0x1.4055572d75d08c56p+24, // -20993367.177578958762
126     -0x1.deeb6013998e4d76p+24, // -31386464.076561826621
127     -0x1.106f7cded5dcc79ep+24, // -17854332.870450781569
128     -0x1.25e17184848c66d2p+22, // -4814940.3794118821866
129     -0x1.301303b99a614a0ap+19, // -622744.11640662195015
130     -0x1.09e76ab41ae965p+15,      // -34035.708405343046707
131     -0x1.00f95ced9e5f54eep+9,  // -513.94814844353701437
132     0x1p+0                    // 1
133 ];
134
135 /*
136  * Helper function: Gamma function computed by Stirling's formula.
137  *
138  * Stirling's formula for the gamma function is:
139  *
140  * $(GAMMA)(x) = sqrt(2 &pi;) x<sup>x-0.5</sup> exp(-x) (1 + 1/x P(1/x))
141  *
142  */
143 real gammaStirling(real x)
144 {
145     // CEPHES code Copyright 1994 by Stephen L. Moshier 
146
147     real w = 1.0L/x;
148     real y = exp(x);
149     if ( x > 1024.0L ) {
150         // For large x, use rational coefficients from the analytical expansion.
151         w = poly(w, LargeStirlingCoeffs);
152         // Avoid overflow in pow()
153         real v = pow( x, 0.5L * x - 0.25L );
154         y = v * (v / y);
155     }
156     else {
157         w = 1.0L + w * poly( w, SmallStirlingCoeffs);
158         y = pow( x, x - 0.5L ) / y;
159     }
160     y = SQRT2PI * y * w;
161     return  y;
162 }
163
164 } // private
165
166 /****************
167  * The sign of $(GAMMA)(x).
168  *
169  * Returns -1 if $(GAMMA)(x) < 0,  +1 if $(GAMMA)(x) > 0,
170  * $(NAN) if sign is indeterminate.
171  */
172 real sgnGamma(real x)
173 {
174     /* Author: Don Clugston. License: Public domain. */
175     if (x > 0) return 1.0;
176     if (x < -1/real.epsilon) {
177         // Large negatives lose all precision
178         return real.nan;
179     }
180 //  if (remquo(x, -1.0, n) == 0) {
181     int n = cast(int)(x);
182     if (x == n) {
183         return x == 0 ?  copysign(1, x) : real.nan;
184     }
185     return n & 1 ? 1.0 : -1.0;
186 }
187
188 unittest {
189     assert(sgnGamma(5.0) == 1.0);
190     assert(isnan(sgnGamma(-3.0)));
191     assert(sgnGamma(-0.1) == -1.0);
192     assert(sgnGamma(-55.1) == 1.0);
193     assert(isnan(sgnGamma(-real.infinity)));
194 }
195
196 /*****************************************************
197  *  The Gamma function, $(GAMMA)(x)
198  *
199  *  $(GAMMA)(x) is a generalisation of the factorial function
200  *  to real and complex numbers.
201  *  Like x!, $(GAMMA)(x+1) = x*$(GAMMA)(x).
202  *
203  *  Mathematically, if z.re > 0 then
204  *   $(GAMMA)(z) = $(INTEGRATE 0, &infin;) $(POWER t, z-1)$(POWER e, -t) dt
205  *
206  *  $(TABLE_SV
207  *    $(SVH  x,          $(GAMMA)(x) )
208  *    $(SV  $(NAN),      $(NAN)      )
209  *    $(SV  &plusmn;0.0, &plusmn;&infin;)
210  *    $(SV integer > 0,  (x-1)!      )
211  *    $(SV integer < 0,  $(NAN)      )
212  *    $(SV +&infin;,     +&infin;    )
213  *    $(SV -&infin;,     $(NAN)      )
214  *  )
215  */
216 real tgamma(real x)
217 {
218 /* Author: Don Clugston. Based on code from the CEPHES library.
219  * CEPHES code Copyright 1994 by Stephen L. Moshier
220  *
221  * Arguments |x| <= 13 are reduced by recurrence and the function
222  * approximated by a rational function of degree 7/8 in the
223  * interval (2,3).  Large arguments are handled by Stirling's
224  * formula. Large negative arguments are made positive using
225  * a reflection formula.
226  */
227
228     real q, z;
229     if (isnan(x)) return x;
230     if (x == -x.infinity) return real.nan;
231     if ( fabs(x) > MAXGAMMA ) return real.infinity;
232     if (x==0) return 1.0/x; // +- infinity depending on sign of x, create an exception.
233    
234     q = fabs(x);
235        
236     if ( q > 13.0L )    {
237         // Large arguments are handled by Stirling's
238         // formula. Large negative arguments are made positive using
239         // the reflection formula. 
240    
241         if ( x < 0.0L ) {
242             int sgngam = 1; // sign of gamma.
243             real p  = floor(q);
244             if (p == q)
245                   return real.nan; // poles for all integers <0.
246             int intpart = cast(int)(p);
247             if ( (intpart & 1) == 0 )
248                 sgngam = -1;
249             z = q - p;
250             if ( z > 0.5L ) {
251                 p += 1.0L;
252                 z = q - p;
253             }
254             z = q * sin( PI * z );
255             z = fabs(z) * gammaStirling(q);
256             if ( z <= PI/real.max ) return sgngam * real.infinity;
257             return sgngam * PI/z;
258         } else {
259             return gammaStirling(x);
260         }
261     }
262    
263     // Arguments |x| <= 13 are reduced by recurrence and the function
264     // approximated by a rational function of degree 7/8 in the
265     // interval (2,3).
266
267     z = 1.0L;
268     while ( x >= 3.0L ) {
269         x -= 1.0L;
270         z *= x;
271     }
272    
273     while ( x < -0.03125L ) {
274         z /= x;
275         x += 1.0L;
276     }
277    
278     if ( x <= 0.03125L ) {
279         if ( x == 0.0L )
280             return real.nan;
281         else {
282             if ( x < 0.0L ) {
283                 x = -x;
284                 return z / (x * poly( x, GammaSmallNegCoeffs ));
285             } else {
286                 return z / (x * poly( x, GammaSmallCoeffs ));
287             }
288         }
289     }
290    
291     while ( x < 2.0L ) {
292         z /= x;
293         x += 1.0L;
294     }
295     if ( x == 2.0L ) return z;
296
297     x -= 2.0L;
298     return z * poly( x, GammaNumeratorCoeffs ) / poly( x, GammaDenominatorCoeffs );
299 }
300
301 unittest {
302     // gamma(n) = factorial(n-1) if n is an integer.
303     real fact = 1.0L;
304     for (int i=1; fact<real.max; ++i) {
305         // Require exact equality for small factorials
306         if (i<14) assert(tgamma(i*1.0L) == fact);
307         assert(feqrel(tgamma(i*1.0L), fact) > real.mant_dig-15);
308         fact *= (i*1.0L);
309     }
310     assert(tgamma(0.0) == real.infinity);
311     assert(tgamma(-0.0) == -real.infinity);
312     assert(isnan(tgamma(-1.0)));
313     assert(isnan(tgamma(-15.0)));
314     assert(isnan(tgamma(real.nan)));
315     assert(tgamma(real.infinity) == real.infinity);
316     assert(tgamma(real.max) == real.infinity);
317     assert(isnan(tgamma(-real.infinity)));
318     assert(tgamma(real.min*real.epsilon) == real.infinity);
319    
320     // Test some high-precision values (50 decimal digits)
321     const real SQRT_PI = 1.77245385090551602729816748334114518279754945612238L;
322
323     assert(feqrel(tgamma(0.5L), SQRT_PI) == real.mant_dig);
324
325     assert(feqrel(tgamma(1.0/3.L),  2.67893853470774763365569294097467764412868937795730L) >= real.mant_dig-2);
326     assert(feqrel(tgamma(0.25L),
327         3.62560990822190831193068515586767200299516768288006) >= real.mant_dig-1);
328     assert(feqrel(tgamma(1.0/5.0L),
329         4.59084371199880305320475827592915200343410999829340L) >= real.mant_dig-1);
330 }
331
332 /*****************************************************
333  * Natural logarithm of gamma function.
334  *
335  * Returns the base e (2.718...) logarithm of the absolute
336  * value of the gamma function of the argument.
337  *
338  * For reals, lgamma is equivalent to log(fabs(gamma(x))).
339  *
340  *  $(TABLE_SV
341  *    $(SVH  x,             lgamma(x)   )
342  *    $(SV  $(NAN),         $(NAN)      )
343  *    $(SV integer <= 0,    +&infin;    )
344  *    $(SV &plusmn;&infin;, +&infin;    )
345  *  )
346  */
347 real lgamma(real x)
348 {
349     /* Author: Don Clugston. Based on code from the CEPHES library.
350      * CEPHES code Copyright 1994 by Stephen L. Moshier
351      *
352      * For arguments greater than 33, the logarithm of the gamma
353      * function is approximated by the logarithmic version of
354      * Stirling's formula using a polynomial approximation of
355      * degree 4. Arguments between -33 and +33 are reduced by
356      * recurrence to the interval [2,3] of a rational approximation.
357      * The cosecant reflection formula is employed for arguments
358      * less than -33.
359      */
360     real q, w, z, f, nx;
361    
362     if (isnan(x)) return x;
363     if (fabs(x) == x.infinity) return x.infinity;
364    
365     if( x < -34.0L ) {
366         q = -x;
367         w = lgamma(q);
368         real p = floor(q);
369         if ( p == q ) return real.infinity;
370         int intpart = cast(int)(p);
371         real sgngam = 1;
372         if ( (intpart & 1) == 0 )
373             sgngam = -1;
374         z = q - p;
375         if ( z > 0.5L ) {
376             p += 1.0L;
377             z = p - q;
378         }
379         z = q * sin( PI * z );
380         if ( z == 0.0L ) return sgngam * real.infinity;
381     /*  z = LOGPI - logl( z ) - w; */
382         z = log( PI/z ) - w;
383         return z;
384     }
385
386     if( x < 13.0L ) {
387         z = 1.0L;
388         nx = floor( x +  0.5L );
389         f = x - nx;
390         while ( x >= 3.0L ) {
391             nx -= 1.0L;
392             x = nx + f;
393             z *= x;
394         }
395         while ( x < 2.0L ) {
396             if( fabs(x) <= 0.03125 ) {
397                     if ( x == 0.0L ) return real.infinity;
398                     if ( x < 0.0L ) {
399                         x = -x;
400                         q = z / (x * poly( x, GammaSmallNegCoeffs));
401                     } else
402                         q = z / (x * poly( x, GammaSmallCoeffs));
403                     return log( fabs(q) );
404             }   
405             z /= nx +  f;
406             nx += 1.0L;
407             x = nx + f;
408         }
409         z = fabs(z);
410         if ( x == 2.0L )
411             return log(z);
412         x = (nx - 2.0L) + f;
413         real p = x * poly( x, logGammaNumerator ) / poly( x, logGammaDenominator);
414         return log(z) + p;
415     }
416    
417     // const real MAXLGM = 1.04848146839019521116e+4928L;
418     //  if( x > MAXLGM ) return sgngaml * real.infinity;
419
420     const real LOGSQRT2PI  =  0.91893853320467274178L; // log( sqrt( 2*pi ) )
421    
422     q = ( x - 0.5L ) * log(x) - x + LOGSQRT2PI;
423     if (x > 1.0e10L) return q;
424     real p = 1.0L / (x*x);
425     q += poly( p, logGammaStirlingCoeffs ) / x;
426     return q ;
427 }
428
429 unittest {
430     assert(isnan(lgamma(real.nan)));
431     assert(lgamma(real.infinity) == real.infinity);
432     assert(lgamma(-1.0) == real.infinity);
433     assert(lgamma(0.0) == real.infinity);
434     assert(lgamma(-50.0) == real.infinity);
435     assert(isPosZero(lgamma(1.0L)));
436     assert(isPosZero(lgamma(2.0L)));
437     assert(lgamma(real.min*real.epsilon) == real.infinity);
438     assert(lgamma(-real.min*real.epsilon) == real.infinity);
439  
440     // x, correct loggamma(x), correct d/dx loggamma(x).
441     static real[] testpoints = [
442     8.0L,                    8.525146484375L      + 1.48766904143001655310E-5,   2.01564147795560999654E0L,
443     8.99993896484375e-1L,    6.6375732421875e-2L  + 5.11505711292524166220E-6L, -7.54938684259372234258E-1,
444     7.31597900390625e-1L,    2.2369384765625e-1   + 5.21506341809849792422E-6L, -1.13355566660398608343E0L,
445     2.31639862060546875e-1L, 1.3686676025390625L  + 1.12609441752996145670E-5L, -4.56670961813812679012E0,
446     1.73162841796875L,      -8.88214111328125e-2L + 3.36207740803753034508E-6L, 2.33339034686200586920E-1L,
447     1.23162841796875L,      -9.3902587890625e-2L  + 1.28765089229009648104E-5L, -2.49677345775751390414E-1L,
448     7.3786976294838206464e19L,   3.301798506038663053312e21L - 1.656137564136932662487046269677E5L,
449                           4.57477139169563904215E1L,
450     1.08420217248550443401E-19L, 4.36682586669921875e1L + 1.37082843669932230418E-5L,
451                          -9.22337203685477580858E18L,
452     1.0L, 0.0L, -5.77215664901532860607E-1L,
453     2.0L, 0.0L, 4.22784335098467139393E-1L,
454     -0.5L,  1.2655029296875L    + 9.19379714539648894580E-6L, 3.64899739785765205590E-2L,
455     -1.5L,  8.6004638671875e-1L + 6.28657731014510932682E-7L, 7.03156640645243187226E-1L,
456     -2.5L, -5.6243896484375E-2L + 1.79986700949327405470E-7,  1.10315664064524318723E0L,
457     -3.5L,  -1.30902099609375L  + 1.43111007079536392848E-5L, 1.38887092635952890151E0L
458     ];
459    // TODO: test derivatives as well.
460     for (int i=0; i<testpoints.length; i+=3) {
461         assert( feqrel(lgamma(testpoints[i]), testpoints[i+1]) > real.mant_dig-5);
462         if (testpoints[i]<MAXGAMMA) {
463             assert( feqrel(log(fabs(tgamma(testpoints[i]))), testpoints[i+1]) > real.mant_dig-5);
464         }
465     }
466     assert(lgamma(-50.2) == log(fabs(tgamma(-50.2))));
467     assert(lgamma(-0.008) == log(fabs(tgamma(-0.008))));
468     assert(feqrel(lgamma(-38.8),log(fabs(tgamma(-38.8)))) > real.mant_dig-4);
469     assert(feqrel(lgamma(1500.0L),log(tgamma(1500.0L))) > real.mant_dig-2);
470 }
Note: See TracBrowser for help on using the browser.