/// <summary> /// Returns Γ(x + 1) - 1 with accuracy improvements in [-0.5, 2] /// </summary> /// <param name="x">Tgamma1pm1 function argument</param> public static double Tgamma1pm1(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Tgamma1pm1(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Tgamma1pm1(x: {0}): Requires x != -Infinity", x); return(double.NaN); } return(double.PositiveInfinity); } double result; if (x < -0.5 || x >= 2) { return(Math2.Tgamma(1 + x) - 1); // Best method is simply to subtract 1 from tgamma: } if (Math.Abs(x) < GammaSmall.UpperLimit) { return(GammaSmall.Tgamma1pm1(x)); } if (x < 0) { // compute exp( log( Γ(x+2)/(1+x) ) ) - 1 result = Math2.Expm1(-Math2.Log1p(x) + _Lgamma.Rational_1_3(x + 2, x + 1, x)); } else { // compute exp( log(Γ(x+1)) ) - 1 result = Math2.Expm1(_Lgamma.Rational_1_3(x + 1, x, x - 1)); } return(result); }
/// <summary> /// Returns Log(Beta(a,b)) /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double LogBeta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(a); if (b == 1) return -Math.Log(a); if (a == 1) return -Math.Log(b); // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if (a < SmallX) { if (c < SmallX) { // In this range, Beta(a, b) ~= 1/a + 1/b // so, we won't have an argument overflow as long as // the min(a, b) >= 2*DoubleLimits.MinNormalValue if (b < 2 * DoubleLimits.MinNormalValue) return Math.Log(c / a) - Math.Log(b); return Math.Log((c / a) / b); } Debug.Assert(c <= GammaSmall.UpperLimit); return -Math.Log((GammaSmall.Tgamma(c) * a * b)); } if (b < SmallX) return Math.Log(Tgamma(a) / (b * Tgamma(c))); return Math.Log((Tgamma(a) / Tgamma(c)) * Tgamma(b)); } else if (a >= StirlingGamma.LowerLimit) { if ( b >= StirlingGamma.LowerLimit ) return StirlingGamma.LogBeta(a, b); return StirlingGamma.LgammaDelta(a, b) + Lgamma(b); } return Lanczos.LogBeta(a, b); }
/// <summary> /// Returns the Beta function /// <para>B(a,b) = Γ(a)*Γ(b)/Γ(a+b)</para> /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double Beta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(a); if (b == 1) return 1 / a; if (a == 1) return 1 / b; // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if ( a < SmallX ) { if (c < SmallX) return (c / a) / b; Debug.Assert(c <= GammaSmall.UpperLimit); return 1/(GammaSmall.Tgamma(c) * a * b); } if ( b < SmallX ) return Tgamma(a) / (b * Tgamma(c)); return Tgamma(a) * ( Tgamma(b)/Tgamma(c) ); } // Our Stirling series is more accurate than Lanczos if (a >= StirlingGamma.LowerLimit ) { if (b >= StirlingGamma.LowerLimit) return StirlingGamma.Beta(a, b); // for large a, Γ(a)/Γ(a + b) ~ a^-b, so don't underflow too soon if (b < 1 || b * Math.Log(a) <= -DoubleLimits.MinLogValue) return Tgamma(b) * StirlingGamma.TgammaDeltaRatio(a, b); // fall through for very large a small 1 < b < LowerLimit } return Lanczos.Beta(a, b); }
/// <summary> /// Returns the natural log of the Gamma Function = ln(|Γ(x)|). /// Sets the sign = Sign(Γ(x)); 0 for poles or indeterminate. /// </summary> /// <param name="x">Lgamma function argument</param> /// <param name="sign">Lgamma output value = Sign(Γ(x))</param> public static double Lgamma(double x, out int sign) { sign = 0; if (double.IsNaN(x)) { Policies.ReportDomainError("Lgamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Lgamma(x: {0}): Requires x != -Infinity", x); return(double.PositiveInfinity); } sign = 1; return(double.PositiveInfinity); } double result = 0; sign = 1; if (x <= 0) { if (IsInteger(x)) { sign = 0; Policies.ReportPoleError("Lgamma(x: {0}): Evaluation at zero, or a negative integer", x); return(double.PositiveInfinity); } if (x > -GammaSmall.UpperLimit) { return(GammaSmall.Lgamma(x, out sign)); } if (x <= -StirlingGamma.LowerLimit) { // Our Stirling routine does the reflection // So no need to reflect here result = StirlingGamma.Lgamma(x, out sign); return(result); } else { double product = 1; double xm2 = x - 2; double xm1 = x - 1; while (x < 1) { product *= x; xm2 = xm1; xm1 = x; x += 1; } if (product < 0) { sign = -1; product = -product; } result = -Math.Log(product) + _Lgamma.Rational_1_3(x, xm1, xm2); return(result); } } else if (x < 1) { if (x < GammaSmall.UpperLimit) { return(GammaSmall.Lgamma(x, out sign)); } // Log(Γ(x)) = Log(Γ(x+1)/x) result = -Math.Log(x) + _Lgamma.Rational_1_3(x + 1, x, x - 1); } else if (x < 3) { result = _Lgamma.Rational_1_3(x, x - 1, x - 2); } else if (x < 8) { // use recurrence to shift x to [2, 3) double product = 1; while (x >= 3) { product *= --x; } result = Math.Log(product) + _Lgamma.Rational_1_3(x, x - 1, x - 2); } else { // regular evaluation: result = StirlingGamma.Lgamma(x); } return(result); }
/// <summary> /// Returns the Gamma Function = Γ(x) /// </summary> /// <param name="x">Tgamma function argument</param> public static double Tgamma(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Tgamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Tgamma(x: {0}): Requires x is not negative infinity", x); return(double.NaN); } return(double.PositiveInfinity); } double result = 1; if (x <= 0) { if (IsInteger(x)) { Policies.ReportPoleError("Tgamma(x: {0}): Requires x != 0 and x be a non-negative integer", x); return(double.NaN); } if (x >= -GammaSmall.UpperLimit) { return(GammaSmall.Tgamma(x)); } if (x <= -StirlingGamma.LowerLimit) { const double MinX = -185; if (x < MinX) { return(0); } // If we use the reflection formula directly for x < -NegMaxGamma, Tgamma will overflow; // so, use recurrence to get x > -NegMaxGamma. // The result is likely to be subnormal or zero. double shiftedX = x; double recurrenceMult = 1.0; double NegMaxGamma = -(Math2.MaxFactorialIndex + 1); while (shiftedX < NegMaxGamma) { recurrenceMult *= shiftedX; shiftedX += 1; } result = -(Math.PI / StirlingGamma.Tgamma(-shiftedX)) / (shiftedX * recurrenceMult * Math2.SinPI(shiftedX)); return(result); } // shift x to > 0: double product = 1; while (x < 0) { product *= x++; } result /= product; // fall through } if (x < 1) { if (x <= GammaSmall.UpperLimit) { result *= GammaSmall.Tgamma(x); } else { // Γ(x) = Exp(LogΓ(x+1))/x result *= Math.Exp(_Lgamma.Rational_1_3(x + 1, x, x - 1)) / x; } } else if (IsInteger(x) && (x <= Math2.FactorialTable.Length)) { // Γ(n) = (n-1)! result *= Math2.FactorialTable[(int)x - 1]; } else if (x < StirlingGamma.LowerLimit) { // ensure x is in [1, 3) // if x >= 3, use recurrence to shift x to [2, 3) while (x >= 3) { result *= --x; } result *= Math.Exp(_Lgamma.Rational_1_3(x, x - 1, x - 2)); } else { result *= StirlingGamma.Tgamma(x); } return(result); }