public static double Gamma(double v0) {
// Calculate the Gamma function using the Lanczos approximation
if (double.IsNegativeInfinity(v0)) {
return double.NaN;
}
double a = Math.Abs(v0);
// Special-case integers
if (a % 1.0 == 0.0) {
// Gamma is undefined on non-positive integers
if (v0 <= 0.0) {
return double.NaN;
}
// factorial(v0 - 1)
if (a <= 25.0) {
if (a <= 2.0) {
return 1.0;
}
a -= 1.0;
v0 -= 1.0;
while (--v0 > 1.0) {
a *= v0;
}
return a;
}
}
// lim(Gamma(v0)) = 1.0 / v0 as v0 approaches 0.0
if (a < 1e-50) {
return 1.0 / v0;
}
double res;
if (v0 < -150.0) {
// If Gamma(1 - v0) could overflow for large v0, use the duplication formula to
// compute Gamma(1 - v0):
// Gamma(x) * Gamma(x + 0,5) = sqrt(pi) * 2**(1 - 2x) * Gamma(2x)
// ==> Gamma(1 - x) = Gamma((1-x)/2) * Gamma((2-x)/2) / (2**x * sqrt(pi))
// Then apply the reflection formula:
// Gamma(x) = pi / sin(pi * x) / Gamma(1 - x)
double halfV0 = v0 / 2.0;
res = Math.Pow(Math.PI, 1.5) / SinPi(v0);
res *= Math.Pow(2.0, v0);
res /= PositiveGamma(0.5 - halfV0);
res /= PositiveGamma(1.0 - halfV0);
} else if (v0 < 0.001) {
// For values less than or close to zero, just use the reflection formula
res = Math.PI / SinPi(v0);
double v1 = 1.0 - v0;
if (v0 == 1.0 - v1) {
res /= PositiveGamma(v1);
} else {
// Computing v1 has resulted in a loss of precision. To avoid this, use the
// recurrence relation Gamma(x + 1) = x * Gamma(x).
res /= -v0 * PositiveGamma(-v0);
}
} else {
res = PositiveGamma(v0);
}
return res;
}