# Visit http://www.johndcook.com/stand_alone_code.html for the source of this code and more like it.
# Note that the functions Gamma and LogGamma are mutually dependent.
proc gamma {x} {
if {$x <= 0} {
error "Invalid input" $x
}
# Split the function domain into three intervals:
# (0, 0.001), [0.001, 12), and (12, infinity)
###########################################################################
# First interval: (0, 0.001)
#
# For small x, 1/Gamma(x) has power series x + gamma x^2 - ...
# So in this range, 1/Gamma(x) = x + gamma x^2 with error on the order of x^3.
# The relative error over this interval is less than 6e-7.
# Euler's gamma constant
set gamma 0.577215664901532860606512090
if {$x < 0.001} {
return [expr 1.0/($x*(1.0 + $gamma*$x))]
}
###########################################################################
# Second interval: [0.001, 12)
if {$x < 12.0} {
# The algorithm directly approximates gamma over (1,2) and uses
# reduction identities to reduce other arguments to this interval.
set y $x
set n 0
set arg_was_less_than_one [expr $y < 1.0]
# Add or subtract integers as necessary to bring y into (1,2)
# Will correct for this below
if {$arg_was_less_than_one} {
set y [expr $y + 1.0]
} else {
# will use n later
set n [expr entier($y) - 1]
set y [expr $y - $n]
}
# numerator coefficients for approximation over the interval (1,2)
set p [list \
-1.71618513886549492533811E+0 \
2.47656508055759199108314E+1 \
-3.79804256470945635097577E+2 \
6.29331155312818442661052E+2 \
8.66966202790413211295064E+2 \
-3.14512729688483675254357E+4 \
-3.61444134186911729807069E+4 \
6.64561438202405440627855E+4 \
]
# denominator coefficients for approximation over the interval (1,2)
set q [list \
-3.08402300119738975254353E+1 \
3.15350626979604161529144E+2 \
-1.01515636749021914166146E+3 \
-3.10777167157231109440444E+3 \
2.25381184209801510330112E+4 \
4.75584627752788110767815E+3 \
-1.34659959864969306392456E+5 \
-1.15132259675553483497211E+5 \
]
set num 0.0
set den 1.0
set z [expr $y - 1]
for {set i 0} {$i < 8} {incr i} {
set num [expr ($num + [lindex $p $i])*$z]
set den [expr $den*$z + [lindex $q $i]]
}
set result [expr $num/$den + 1.0]
# Apply correction if argument was not initially in (1,2)
if {$arg_was_less_than_one} {
# Use identity gamma(z) = gamma(z+1)/z
# The variable "result" now holds gamma of the original y + 1
# Thus we use y-1 to get back the orginal y.
set result [expr $result / ($y-1.0)]
} else {
# Use the identity gamma(z+n) = z*(z+1)* ... *(z+n-1)*gamma(z)
for {set i 0} {$i < $n} {incr i} {
set result [expr $result * $y]
set y [expr $y + 1]
}
}
return $result
}
###########################################################################
# Third interval: [12, infinity)
if {$x > 171.624} {
# Correct answer too large to display.
return 1.0/0 # float infinity
}
return [expr exp([log_gamma $x])]
}
proc log_gamma {x} {
if {$x <= 0} {
error "Invalid input"
}
if {$x < 12.0} {
return [expr log(abs([gamma $x]))]
}
# Abramowitz and Stegun 6.1.41
# Asymptotic series should be good to at least 11 or 12 figures
# For error analysis, see Whittiker and Watson
# A Course in Modern Analysis (1927), page 252
set c [list \
1.0/12.0 \
-1.0/360.0 \
1.0/1260.0 \
-1.0/1680.0 \
1.0/1188.0 \
-691.0/360360.0 \
1.0/156.0 \
-3617.0/122400.0 \
]
set z [expr 1.0/($x*$x)]
set sum [lindex $c 7]
for {set i 6} {$i >= 0} {incr i -1} {
set sum [expr $sum * $z]
set sum [expr $sum + [lindex $c $i]]
}
set series [expr $sum/$x]
set halfLogTwoPi 0.91893853320467274178032973640562
set logGamma [expr ($x - 0.5)*log($x) - $x + $halfLogTwoPi + $series]
return $logGamma
}