@@ -203,6 +203,52 @@ pub fn tgamma(x: f64) -> Result<f64, Error> {
203203 }
204204}
205205
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+
206252#[ cfg( test) ]
207253mod tests {
208254 use super :: * ;
@@ -259,5 +305,26 @@ mod tests {
259305 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) ;
260306 } ) ;
261307 }
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+ }
262329 }
263330}
0 commit comments