1+ /* 
2+  * Implementation of the gamma function based on CPython's implementation. 
3+  * This is a standalone version without dependencies on CPython. 
4+  */ 
5+ 
6+ #include  <math.h> 
7+ #include  <errno.h> 
8+ #include  <float.h> 
9+ #include  <assert.h> 
10+ #include  <stdbool.h> 
11+ 
12+ /* Constants and definitions needed for gamma calculation */ 
13+ static  const  double  pi  =  3.141592653589793238462643383279502884197 ;
14+ static  const  double  logpi  =  1.144729885849400174143427351353058711647 ;
15+ 
16+ /* Check if a value is finite */ 
17+ #ifndef  Py_IS_FINITE 
18+ #define  Py_IS_FINITE (x ) isfinite(x)
19+ #endif 
20+ 
21+ /* Check if a value is infinity */ 
22+ #ifndef  Py_IS_INFINITY 
23+ #define  Py_IS_INFINITY (x ) isinf(x)
24+ #endif 
25+ 
26+ /* Check if a value is NaN */ 
27+ #ifndef  Py_IS_NAN 
28+ #define  Py_IS_NAN (x ) isnan(x)
29+ #endif 
30+ 
31+ /* Define HUGE_VAL if not available */ 
32+ #ifndef  Py_HUGE_VAL 
33+ #define  Py_HUGE_VAL  HUGE_VAL
34+ #endif 
35+ 
36+ /* Define mathematical constants required for Lanczos approximation */ 
37+ #define  LANCZOS_N  13
38+ static  const  double  lanczos_g  =  6.024680040776729583740234375 ;
39+ static  const  double  lanczos_g_minus_half  =  5.524680040776729583740234375 ;
40+ static  const  double  lanczos_num_coeffs [LANCZOS_N ] =  {
41+     23531376880.410759688572007674451636754734846804940 ,
42+     42919803642.649098768957899047001988850926355848959 ,
43+     35711959237.355668049440185451547166705960488635843 ,
44+     17921034426.037209699919755754458931112671403265390 ,
45+     6039542586.3520280050642916443072979210699388420708 ,
46+     1439720407.3117216736632230727949123939715485786772 ,
47+     248874557.86205415651146038641322942321632125127801 ,
48+     31426415.585400194380614231628318205362874684987640 ,
49+     2876370.6289353724412254090516208496135991145378768 ,
50+     186056.26539522349504029498971604569928220784236328 ,
51+     8071.6720023658162106380029022722506138218516325024 ,
52+     210.82427775157934587250973392071336271166969580291 ,
53+     2.5066282746310002701649081771338373386264310793408 
54+ };
55+ 
56+ /* denominator is x*(x+1)*...*(x+LANCZOS_N-2) */ 
57+ static  const  double  lanczos_den_coeffs [LANCZOS_N ] =  {
58+     0.0 , 39916800.0 , 120543840.0 , 150917976.0 , 105258076.0 , 45995730.0 ,
59+     13339535.0 , 2637558.0 , 357423.0 , 32670.0 , 1925.0 , 66.0 , 1.0 };
60+ 
61+ /* gamma values for small positive integers, 1 though NGAMMA_INTEGRAL */ 
62+ #define  NGAMMA_INTEGRAL  23
63+ static  const  double  gamma_integral [NGAMMA_INTEGRAL ] =  {
64+     1.0 , 1.0 , 2.0 , 6.0 , 24.0 , 120.0 , 720.0 , 5040.0 , 40320.0 , 362880.0 ,
65+     3628800.0 , 39916800.0 , 479001600.0 , 6227020800.0 , 87178291200.0 ,
66+     1307674368000.0 , 20922789888000.0 , 355687428096000.0 ,
67+     6402373705728000.0 , 121645100408832000.0 , 2432902008176640000.0 ,
68+     51090942171709440000.0 , 1124000727777607680000.0 ,
69+ };
70+ 
71+ /* Helper function for sin(pi*x) used in gamma calculation */ 
72+ static  double  m_sinpi (double  x )
73+ {
74+     double  y , r ;
75+     int  n ;
76+     /* this function should only ever be called for finite arguments */ 
77+     assert (Py_IS_FINITE (x ));
78+     y  =  fmod (fabs (x ), 2.0 );
79+     n  =  (int )round (2.0 * y );
80+     assert (0  <= n  &&  n  <= 4 );
81+     switch  (n ) {
82+     case  0 :
83+         r  =  sin (pi * y );
84+         break ;
85+     case  1 :
86+         r  =  cos (pi * (y - 0.5 ));
87+         break ;
88+     case  2 :
89+         /* N.B. -sin(pi*(y-1.0)) is *not* equivalent: it would give 
90+            -0.0 instead of 0.0 when y == 1.0. */ 
91+         r  =  sin (pi * (1.0 - y ));
92+         break ;
93+     case  3 :
94+         r  =  - cos (pi * (y - 1.5 ));
95+         break ;
96+     case  4 :
97+         r  =  sin (pi * (y - 2.0 ));
98+         break ;
99+     default :
100+         /* Should never happen due to the assert above */ 
101+         r  =  0.0 ;
102+     }
103+     return  copysign (1.0 , x )* r ;
104+ }
105+ 
106+ /* Implementation of Lanczos sum for gamma function */ 
107+ static  double  lanczos_sum (double  x )
108+ {
109+     double  num  =  0.0 , den  =  0.0 ;
110+     int  i ;
111+     assert (x  >  0.0 );
112+     /* evaluate the rational function lanczos_sum(x). For large 
113+        x, the obvious algorithm risks overflow, so we instead 
114+        rescale the denominator and numerator of the rational 
115+        function by x**(1-LANCZOS_N) and treat this as a 
116+        rational function in 1/x. This also reduces the error for 
117+        larger x values. The choice of cutoff point (5.0 below) is 
118+        somewhat arbitrary; in tests, smaller cutoff values than 
119+        this resulted in lower accuracy. */ 
120+     if  (x  <  5.0 ) {
121+         for  (i  =  LANCZOS_N ; -- i  >= 0 ; ) {
122+             num  =  num  *  x  +  lanczos_num_coeffs [i ];
123+             den  =  den  *  x  +  lanczos_den_coeffs [i ];
124+         }
125+     }
126+     else  {
127+         for  (i  =  0 ; i  <  LANCZOS_N ; i ++ ) {
128+             num  =  num  / x  +  lanczos_num_coeffs [i ];
129+             den  =  den  / x  +  lanczos_den_coeffs [i ];
130+         }
131+     }
132+     return  num /den ;
133+ }
134+ 
135+ /** 
136+  * Computes the gamma function value for x. 
137+  * 
138+  * The implementation is based on the Lanczos approximation with parameters  
139+  * N=13 and g=6.024680040776729583740234375, which provides excellent accuracy 
140+  * across the domain of the function. 
141+  * 
142+  * @param x The input value 
143+  * @return The gamma function value 
144+  * 
145+  * Special cases: 
146+  * - If x is NaN, returns NaN 
147+  * - If x is +Inf, returns +Inf 
148+  * - If x is -Inf, sets errno to EDOM and returns NaN 
149+  * - If x is 0, sets errno to EDOM and returns +/-Inf (with the sign of x) 
150+  * - If x is a negative integer, sets errno to EDOM and returns NaN 
151+  */ 
152+ double  tgamma (double  x ) {
153+     double  absx , r , y , z , sqrtpow ;
154+ 
155+     /* special cases */ 
156+     if  (!Py_IS_FINITE (x )) {
157+         if  (Py_IS_NAN (x ) ||  x  >  0.0 )
158+             return  x ;  /* tgamma(nan) = nan, tgamma(inf) = inf */ 
159+         else  {
160+             errno  =  EDOM ;
161+             return  NAN ;  /* tgamma(-inf) = nan, invalid */ 
162+         }
163+     }
164+     if  (x  ==  0.0 ) {
165+         errno  =  EDOM ;
166+         /* tgamma(+-0.0) = +-inf, divide-by-zero */ 
167+         return  copysign (INFINITY , x );
168+     }
169+ 
170+     /* integer arguments */ 
171+     if  (x  ==  floor (x )) {
172+         if  (x  <  0.0 ) {
173+             errno  =  EDOM ;  /* tgamma(n) = nan, invalid for */ 
174+             return  NAN ; /* negative integers n */ 
175+         }
176+         if  (x  <= NGAMMA_INTEGRAL )
177+             return  gamma_integral [(int )x  -  1 ];
178+     }
179+     absx  =  fabs (x );
180+ 
181+     /* tiny arguments:  tgamma(x) ~ 1/x for x near 0 */ 
182+     if  (absx  <  1e-20 ) {
183+         r  =  1.0 /x ;
184+         if  (Py_IS_INFINITY (r ))
185+             errno  =  ERANGE ;
186+         return  r ;
187+     }
188+ 
189+     /* large arguments: assuming IEEE 754 doubles, tgamma(x) overflows for 
190+        x > 200, and underflows to +-0.0 for x < -200, not a negative 
191+        integer. */ 
192+     if  (absx  >  200.0 ) {
193+         if  (x  <  0.0 ) {
194+             return  0.0 /m_sinpi (x );
195+         }
196+         else  {
197+             errno  =  ERANGE ;
198+             return  Py_HUGE_VAL ;
199+         }
200+     }
201+ 
202+     y  =  absx  +  lanczos_g_minus_half ;
203+     /* compute error in sum */ 
204+     if  (absx  >  lanczos_g_minus_half ) {
205+         /* note: the correction can be foiled by an optimizing 
206+            compiler that (incorrectly) thinks that an expression like 
207+            a + b - a - b can be optimized to 0.0. This shouldn't 
208+            happen in a standards-conforming compiler. */ 
209+         double  q  =  y  -  absx ;
210+         z  =  q  -  lanczos_g_minus_half ;
211+     }
212+     else  {
213+         double  q  =  y  -  lanczos_g_minus_half ;
214+         z  =  q  -  absx ;
215+     }
216+     z  =  z  *  lanczos_g  / y ;
217+     if  (x  <  0.0 ) {
218+         r  =  - pi  / m_sinpi (absx ) / absx  *  exp (y ) / lanczos_sum (absx );
219+         r  -=  z  *  r ;
220+         if  (absx  <  140.0 ) {
221+             r  /= pow (y , absx  -  0.5 );
222+         }
223+         else  {
224+             sqrtpow  =  pow (y , absx  / 2.0  -  0.25 );
225+             r  /= sqrtpow ;
226+             r  /= sqrtpow ;
227+         }
228+     }
229+     else  {
230+         r  =  lanczos_sum (absx ) / exp (y );
231+         r  +=  z  *  r ;
232+         if  (absx  <  140.0 ) {
233+             r  *= pow (y , absx  -  0.5 );
234+         }
235+         else  {
236+             sqrtpow  =  pow (y , absx  / 2.0  -  0.25 );
237+             r  *= sqrtpow ;
238+             r  *= sqrtpow ;
239+         }
240+     }
241+     if  (Py_IS_INFINITY (r ))
242+         errno  =  ERANGE ;
243+     return  r ;
244+ }
245+ 
246+ /** 
247+  * Computes the natural logarithm of the absolute value of the gamma function. 
248+  * 
249+  * @param x The input value 
250+  * @return The log of the absolute gamma function value 
251+  * 
252+  * Special cases: 
253+  * - If x is NaN, returns NaN 
254+  * - If x is +/-Inf, returns +Inf 
255+  * - If x is a non-positive integer, sets errno to EDOM and returns +Inf 
256+  * - If x is 1 or 2, returns 0.0 
257+  */ 
258+ double  lgamma (double  x ) {
259+     double  r ;
260+     double  absx ;
261+ 
262+     /* special cases */ 
263+     if  (!Py_IS_FINITE (x )) {
264+         if  (Py_IS_NAN (x ))
265+             return  x ;  /* lgamma(nan) = nan */ 
266+         else 
267+             return  HUGE_VAL ; /* lgamma(+-inf) = +inf */ 
268+     }
269+ 
270+     /* integer arguments */ 
271+     if  (x  ==  floor (x ) &&  x  <= 2.0 ) {
272+         if  (x  <= 0.0 ) {
273+             errno  =  EDOM ;  /* lgamma(n) = inf, divide-by-zero for */ 
274+             return  HUGE_VAL ; /* integers n <= 0 */ 
275+         }
276+         else  {
277+             return  0.0 ; /* lgamma(1) = lgamma(2) = 0.0 */ 
278+         }
279+     }
280+ 
281+     absx  =  fabs (x );
282+     /* tiny arguments: lgamma(x) ~ -log(fabs(x)) for small x */ 
283+     if  (absx  <  1e-20 )
284+         return  - log (absx );
285+ 
286+     /* Lanczos' formula. We could save a fraction of a ulp in accuracy by 
287+        having a second set of numerator coefficients for lanczos_sum that 
288+        absorbed the exp(-lanczos_g) term, and throwing out the lanczos_g 
289+        subtraction below; it's probably not worth it. */ 
290+     r  =  log (lanczos_sum (absx )) -  lanczos_g ;
291+     r  +=  (absx  -  0.5 ) *  (log (absx  +  lanczos_g  -  0.5 ) -  1 );
292+     if  (x  <  0.0 )
293+         /* Use reflection formula to get value for negative x. */ 
294+         r  =  logpi  -  log (fabs (m_sinpi (absx ))) -  log (absx ) -  r ;
295+     if  (Py_IS_INFINITY (r ))
296+         errno  =  ERANGE ;
297+     return  r ;
298+ }
299+ 
300+ #include  <stdio.h> 
301+ #include  <stdint.h> 
302+ 
303+ union  Result  {
304+     double  d ;
305+     uint64_t  u ;
306+ };
307+ 
308+ int  main () {
309+     union  Result  result ;
310+     result .d  =  tgamma (-3.8510064710745118 );
311+     printf ("The result of tgamma(-3.8510064710745118) is: %f\n" , result .d );
312+ 
313+     // Print the binary representation of a double 
314+     printf ("Bit representation of result: %llu\n" , result .u );
315+     return  0 ;
316+ }
0 commit comments