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