@@ -698,8 +698,9 @@ fn gamma_ln(x: f64) -> f64 {
698698 return f64:: NEG_INFINITY ;
699699 }
700700
701- // Coefficients for g=7, n=9 from Numerical Recipes.
702- const COEFFS : [ f64 ; 9 ] = [
701+ // Lanczos coefficients for g=7, n=9 from Numerical Recipes.
702+ const LANCZOS_G : f64 = 7.0 ;
703+ const LANCZOS_COEFFS : [ f64 ; 9 ] = [
703704 0.999_999_999_999_809_9 ,
704705 676.520_368_121_885_1 ,
705706 -1_259.139_216_722_402_8 ,
@@ -716,11 +717,11 @@ fn gamma_ln(x: f64) -> f64 {
716717 }
717718
718719 let z = x - 1.0 ;
719- let mut a = COEFFS [ 0 ] ;
720- for ( i, coeff) in COEFFS . iter ( ) . enumerate ( ) . skip ( 1 ) {
720+ let mut a = LANCZOS_COEFFS [ 0 ] ;
721+ for ( i, coeff) in LANCZOS_COEFFS . iter ( ) . enumerate ( ) . skip ( 1 ) {
721722 a += coeff / ( z + i as f64 ) ;
722723 }
723- let t = z + 7 .5;
724+ let t = z + LANCZOS_G + 0 .5;
724725
725726 0.5 * ( 2.0 * PI ) . ln ( ) + ( z + 0.5 ) * t. ln ( ) - t + a. ln ( )
726727}
@@ -853,6 +854,7 @@ mod tests {
853854 assert_eq ! ( poisson. variance( ) , 3.0 ) ;
854855
855856 assert_abs_diff_eq ! ( poisson. pmf( 0 ) , ( -3.0_f64 ) . exp( ) , epsilon = 1e-12 ) ;
857+ assert_abs_diff_eq ! ( poisson. log_pmf( 0 ) , -3.0 , epsilon = 1e-12 ) ;
856858 assert_abs_diff_eq ! ( poisson. pmf( 2 ) , 4.5 * ( -3.0_f64 ) . exp( ) , epsilon = 1e-12 ) ;
857859 assert_abs_diff_eq ! (
858860 poisson. log_pmf( 2 ) ,
@@ -892,6 +894,65 @@ mod tests {
892894 assert_abs_diff_eq ! ( gamma_ln( 10.0 ) , 362_880.0_f64 . ln( ) , epsilon = 1e-10 ) ;
893895 }
894896
897+ #[ test]
898+ fn test_gamma_ln_regression_absolute_log_probabilities ( ) {
899+ // These analytical checks pin absolute normalizing constants used by
900+ // distributions that depend on gamma_ln, including non-integer and
901+ // larger combinatorial cases. Decimal expectations were computed from
902+ // the closed-form definitions with Python's math.lgamma at double
903+ // precision so future changes can be independently re-verified.
904+ let exponential_gamma = Gamma :: new ( 1.0 , 1.0 ) . unwrap ( ) ;
905+ assert_abs_diff_eq ! ( exponential_gamma. log_pdf( 1.0 ) , -1.0 , epsilon = 1e-12 ) ;
906+
907+ let non_integer_gamma = Gamma :: new ( 2.5 , 1.5 ) . unwrap ( ) ;
908+ assert_abs_diff_eq ! (
909+ non_integer_gamma. log_pdf( 1.2 ) ,
910+ -0.797_537_765_011_576_5 ,
911+ epsilon = 1e-12
912+ ) ;
913+
914+ let uniform_beta = Beta :: new ( 1.0 , 1.0 ) . unwrap ( ) ;
915+ assert_abs_diff_eq ! ( uniform_beta. log_pdf( 0.5 ) , 0.0 , epsilon = 1e-12 ) ;
916+
917+ let fractional_beta = Beta :: new ( 2.5 , 3.5 ) . unwrap ( ) ;
918+ assert_abs_diff_eq ! (
919+ fractional_beta. log_pdf( 0.4 ) ,
920+ 0.650_335_112_735_843_9 ,
921+ epsilon = 1e-12
922+ ) ;
923+
924+ let cauchy = StudentT :: new ( 1.0 , 0.0 , 1.0 ) . unwrap ( ) ;
925+ assert_abs_diff_eq ! ( cauchy. log_pdf( 0.0 ) , -PI . ln( ) , epsilon = 1e-12 ) ;
926+
927+ let scaled_student_t = StudentT :: new ( 5.0 , 0.5 , 1.25 ) . unwrap ( ) ;
928+ assert_abs_diff_eq ! (
929+ scaled_student_t. log_pdf( 1.75 ) ,
930+ -1.738_727_810_750_798_4 ,
931+ epsilon = 1e-12
932+ ) ;
933+
934+ let poisson = Poisson :: new ( 3.0 ) . unwrap ( ) ;
935+ assert_abs_diff_eq ! ( poisson. log_pmf( 0 ) , -3.0 , epsilon = 1e-12 ) ;
936+ assert_abs_diff_eq ! (
937+ poisson. log_pmf( 4 ) ,
938+ -1.783_604_675_675_505_7 ,
939+ epsilon = 1e-12
940+ ) ;
941+
942+ let fair_coin = Binomial :: new ( 4 , 0.5 ) . unwrap ( ) ;
943+ assert_abs_diff_eq ! ( fair_coin. log_pmf( 2 ) , ( 6.0_f64 / 16.0 ) . ln( ) , epsilon = 1e-12 ) ;
944+ assert_abs_diff_eq ! (
945+ log_binomial_coefficient( 5 , 2 ) ,
946+ 10.0_f64 . ln( ) ,
947+ epsilon = 1e-12
948+ ) ;
949+ assert_abs_diff_eq ! (
950+ log_binomial_coefficient( 10 , 3 ) ,
951+ 120.0_f64 . ln( ) ,
952+ epsilon = 1e-12
953+ ) ;
954+ }
955+
895956 #[ test]
896957 fn test_normal_creation ( ) {
897958 let normal = Normal :: new ( 0.0 , 1.0 ) . unwrap ( ) ;
0 commit comments