@@ -203,6 +203,52 @@ pub fn tgamma(x: f64) -> Result<f64, Error> {
203
203
}
204
204
}
205
205
206
+ // natural log of the absolute value of the Gamma function.
207
+ // For large arguments, Lanczos' formula works extremely well here.
208
+ pub fn lgamma ( x : f64 ) -> Result < f64 , Error > {
209
+ // special cases
210
+ if !x. is_finite ( ) {
211
+ if x. is_nan ( ) {
212
+ return Ok ( x) ; // lgamma(nan) = nan
213
+ } else {
214
+ return Ok ( f64:: INFINITY ) ; // lgamma(+-inf) = +inf
215
+ }
216
+ }
217
+
218
+ // integer arguments
219
+ if x == x. floor ( ) && x <= 2.0 {
220
+ if x <= 0.0 {
221
+ // lgamma(n) = inf, divide-by-zero for integers n <= 0
222
+ return Err ( Error :: EDOM ) ;
223
+ } else {
224
+ // lgamma(1) = lgamma(2) = 0.0
225
+ return Ok ( 0.0 ) ;
226
+ }
227
+ }
228
+
229
+ let absx = x. abs ( ) ;
230
+ // tiny arguments: lgamma(x) ~ -log(fabs(x)) for small x
231
+ if absx < 1e-20 {
232
+ return Ok ( -absx. ln ( ) ) ;
233
+ }
234
+
235
+ // Lanczos' formula. We could save a fraction of a ulp in accuracy by
236
+ // having a second set of numerator coefficients for lanczos_sum that
237
+ // absorbed the exp(-lanczos_g) term, and throwing out the lanczos_g
238
+ // subtraction below; it's probably not worth it.
239
+ let mut r = lanczos_sum ( absx) . ln ( ) - LANCZOS_G ;
240
+ r += ( absx - 0.5 ) * ( ( absx + LANCZOS_G - 0.5 ) . ln ( ) - 1.0 ) ;
241
+
242
+ if x < 0.0 {
243
+ // Use reflection formula to get value for negative x
244
+ r = LOG_PI - m_sinpi ( absx) . abs ( ) . ln ( ) - absx. ln ( ) - r;
245
+ }
246
+ if r. is_infinite ( ) {
247
+ return Err ( Error :: ERANGE ) ;
248
+ }
249
+ Ok ( r)
250
+ }
251
+
206
252
#[ cfg( test) ]
207
253
mod tests {
208
254
use super :: * ;
@@ -259,5 +305,26 @@ mod tests {
259
305
assert!( ( py_gamma_repr - rs_gamma_repr) . abs( ) <= 1 , "x = {x} diff: {}, py_gamma = {py_gamma} ({py_gamma_repr:x}), rs_gamma = {rs_gamma} ({rs_gamma_repr:x})" , py_gamma_repr ^ rs_gamma_repr) ;
260
306
} ) ;
261
307
}
308
+
309
+ #[ test]
310
+ fn test_lgamma( x: f64 ) {
311
+ let rs_lgamma = lgamma( x) ;
312
+
313
+ pyo3:: prepare_freethreaded_python( ) ;
314
+ Python :: with_gil( |py| {
315
+ let math = PyModule :: import( py, "math" ) . unwrap( ) ;
316
+ let py_lgamma_func = math
317
+ . getattr( "lgamma" )
318
+ . unwrap( ) ;
319
+ let r = py_lgamma_func. call1( ( x, ) ) ;
320
+ let Some ( ( py_lgamma, rs_lgamma) ) = unwrap( py, r, rs_lgamma) else {
321
+ return ;
322
+ } ;
323
+ let py_lgamma_repr = unsafe { std:: mem:: transmute:: <f64 , i64 >( py_lgamma) } ;
324
+ let rs_lgamma_repr = unsafe { std:: mem:: transmute:: <f64 , i64 >( rs_lgamma) } ;
325
+ // allow 6 bit error for now
326
+ assert!( ( py_lgamma_repr - rs_lgamma_repr) . abs( ) <= 6 , "x = {x} diff: {}, py_lgamma = {py_lgamma} ({py_lgamma_repr:x}), rs_lgamma = {rs_lgamma} ({rs_lgamma_repr:x})" , py_lgamma_repr ^ rs_lgamma_repr) ;
327
+ } ) ;
328
+ }
262
329
}
263
330
}
0 commit comments