public static double TgammaLowerImp(double a, double x) { const Routine routine = Routine.Lower; // special values if (x == 0.0) { return(0.0); } if (double.IsInfinity(x)) { return(Math2.Tgamma(a)); } if (a == 1.0) { return(-Math2.Expm1(-x)); // 1.0 - Math.Exp(-x); } if (a == 0.5) { return(Constants.SqrtPI * Math2.Erf(Math.Sqrt(x))); } // Process small a, large x with asymptotic series, which is faster than CF if (x >= Asym_MinLargeZ(a)) { return(Math2.Tgamma(a) - Asym_SeriesLargeZ(a, x) * Prefix(a, x) / x); } // is a small if ((a < 30) && (x >= a + 1) && (x < DoubleLimits.MaxLogValue)) { double frac = a - Math.Floor(a); if (frac == 0) { return(IntegerA(routine, a, x)); } else if (frac == 0.5) { return(HalfIntegerA(routine, a, x)); } } if (x < (a + 1)) { if (a < 1) { return(SmallA(routine, a, x)); } return(Prefix(a, x, 1 / a) * LowerSeries(a, x)); } // Use CF for x > a+1 return(TgammaMinusUpperFraction(a, x)); }
/// <summary> /// Returns (x^y)-1 with improved accuracy when x is close to 1, or when y is very small /// </summary> /// <param name="x">Powm1 function value</param> /// <param name="y">Powm1 function exponent</param> /// <returns></returns> /// <remarks> /// Note: The .NET treatment of Math.Pow for NaN parameters is different from that of ISO C. /// Math.Pow: for any parameter that is NaN, return NaN. /// ISO C: 1^y == 1, x^0 = 1, even if x,y are NaN /// see: http://msdn.microsoft.com/en-us/library/system.math.pow.aspx /// see also: http://pubs.opengroup.org/onlinepubs/009695399/functions/pow.html /// for the conditions on Math.Pow. /// This implementation treats NaN similarly to .NET - treat every NaN as an error condition. /// </remarks> public static double Powm1(double x, double y) { if (double.IsNaN(x) || double.IsNaN(y)) { Policies.ReportDomainError("Powm1(x: {0}, y: {1}): Requires x, y not NaN", x, y); return(double.NaN); } if (y == 0 || x == 1.0) { return(0); // x^0 = 1, 1^y = 1 } if (x == 0) { return((y > 0) ? -1 : double.PositiveInfinity); } double xOrig = x; double yOrig = y; if (x < 0) { if (!IsInteger(y)) { Policies.ReportDomainError("Powm1(x: {0}, y: {1}): Requires integer y when x < 0", x, y); return(double.NaN); } if (IsOdd(y)) { return(Math.Pow(x, y) - 1); } x = -x; } Debug.Assert(x > 0, "Expecting x > 0"); // log(x) = log((z+1)/(z-1)) = 2z * (1+z^2/3 .. ), where z = (x-1)/(x+1) // see https://en.wikipedia.org/wiki/Natural_logarithm // Series converges fastest when z is closest to 1 if ((x - 1) * y <= 0.25 * (x + 1)) { double p = Math.Log(x) * y; if (p <= 1) { return(Math2.Expm1(p)); } // otherwise fall though: } return(Math.Pow(x, y) - 1); }
/// <summary> /// Returns Sqrt(x+1)-1 with improved accuracy for |x| ≤ 0.75 /// </summary> /// <param name="x">The function argument</param> public static double Sqrt1pm1(double x) { if (!(x >= -1)) { Policies.ReportDomainError("Sqrt1pm1(x: {0}): Requires x >= -1", x); return(double.NaN); } if (double.IsInfinity(x)) { return(x); } if (Math.Abs(x) <= 0.75) { return(Math2.Expm1(Math2.Log1p(x) / 2)); } return(Math.Sqrt(1 + x) - 1); }
/// <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); }
public static double GammaPImp(double a, double x) { const Routine routine = Routine.P; // special values if (x == 0.0) { return(0.0); } if (double.IsInfinity(x)) { return(1.0); } if (a == 0) { return(1); } if (a == 1.0) { return(-Math2.Expm1(-x)); // 1.0 - Math.Exp(-x) } if (a == 0.5) { return(Math2.Erf(Math.Sqrt(x))); } // Process small a, large x with asymptotic series, which is faster than CF if (x >= Asym_MinLargeZ(a)) { return(1 - Asym_SeriesLargeZ(a, x) * PrefixRegularized(a, x) / x); } // is a small if ((a < 30) && (x >= a + 1) && (x < DoubleLimits.MaxLogValue)) { double frac = a - Math.Floor(a); if (frac == 0) { return(IntegerA(routine, a, x)); } else if (frac == 0.5) { return(HalfIntegerA(routine, a, x)); } } // // Begin by testing whether we're in the "bad" zone // where the result will be near 0.5 and the usual // series and continued fractions are slow to converge: // if (a > 20) { // This second limit below is chosen so that we use Temme's expansion // only if the result would be larger than about 10^-6. // Below that the regular series and continued fractions // converge OK, and if we use Temme's method we get increasing // errors from the dominant erfc term as it's (inexact) argument // increases in magnitude. double sigma = Math.Abs((x - a) / a); if (sigma < 0.4 || (a > 200 && 20 / a > sigma * sigma)) { double t = TemmeSymmetricAsym.GammaP(a, x); return((x >= a) ? 1 - t : t); } } if (x < (a + 1)) { if (a < 1) { return(SmallA(routine, a, x)); } return(PrefixRegularized(a, x) * LowerSeries(a, x) / a); } // Use CF for x > a+1 return(1.0 - PrefixRegularized(a, x) * UpperFraction(a, x)); }
public static double ImpP(double z, double p, double q) { Debug.Assert(p > 0 && p < 0.5); // Set outside limits // Since P(a, z) >= 1/2 for a >= z, so minA < z double minA = z; double maxA = double.MaxValue; double aGuess = 0; double step = 0.5; if (z <= 1) { var e = -Math2.Expm1(-z); if (p >= e) { if (p == e) { return(1); } // This is one of the outside limits of the GammaP/GammaQ // From: http://dlmf.nist.gov/8.10#E11 // If a >= 1, P(a, x) <= (1-e^-x)^a // If a <= 1, P(a, x) >= (1-e^-x)^a // not a bad guess, for small z and small a // but gets much worse as x increases or a >> 1 maxA = 1; var limit = Math.Log(p) / Math.Log(e); if (limit < 1) { minA = Math.Max(minA, limit); } // could use bisection, but the root often lies nearer to minA step = (1 - minA) * 0.125; aGuess = minA * (1 + step); var r = SolveA(z, p, q, aGuess, step, minA, maxA); ExtraDebugLog(z, p, q, r.Result, aGuess, step, minA, maxA, r.Iterations, ""); return(r.Result); } else { minA = 1; var limit = Math.Log(p) / Math.Log(e); if (limit > 1) { maxA = Math.Min(maxA, limit); } } } aGuess = 0.5 + InversePoissonCornishFisher(z, q, p); if (aGuess > 100) { step = 0.01; } else if (aGuess > 10) { step = 0.01; } else { step = 0.05; } #if EXTRA_DEBUG double og = aGuess; #endif aGuess = Math.Max(aGuess, minA); aGuess = Math.Min(aGuess, maxA); if (p < DoubleLimits.MachineEpsilon) { step *= 10; } #if EXTRA_DEBUG var gs = new Stack <double>(); if (og != aGuess) { gs.Push(og); } #endif // When p is very small, our Inverse Poisson Cornish Fisher can be significantly off. // Refining the guess here can significantly reduce iterations later int refineIter = 0; if (aGuess >= 5 && z / (1 + aGuess) <= 0.7) { #if EXTRA_DEBUG gs.Push(aGuess); #endif (aGuess, refineIter) = RefineP_LargeA_SmallZ(z, p, q, aGuess, ref minA, ref maxA); step = aGuess * 1e-6; aGuess = Math.Max(aGuess, minA); aGuess = Math.Min(aGuess, maxA); } var rr = SolveA(z, p, q, aGuess, step, minA, maxA); #if EXTRA_DEBUG if (rr.Iterations + refineIter > 6) { string refString = (refineIter == 0) ? string.Empty : "(" + rr.Iterations + "+" + refineIter + ")"; string guesses = (gs.Count == 0) ? string.Empty : ", og: (" + string.Join(", ", gs) + ")"; Debug.WriteLine($"PInvA(z: {z}, p: {p}) = {rr.Result}, g: {aGuess}, TIter = {rr.Iterations + refineIter}{refString}{guesses}"); } #endif return(rr.Result); }
public static double ImpQ(double z, double p, double q) { Debug.Assert(q <= 0.5); if (q == 0) { return(DoubleLimits.MinNormalValue); } #if EXTRA_DEBUG var gs = new Stack <double>(); #endif // Set outside limits // Since Q(a, z) > 1/2 for a >= z+1, so maxA < z+1 double minA = DoubleLimits.MinNormalValue; double maxA = z + 1; if (maxA == z) { maxA = Math2.FloatNext(z); } double step = 0.5; double aGuess = 0; // We can use the relationship between the incomplete // gamma function and the poisson distribution to // calculate an approximate inverse. // Mostly it is pretty accurate, except when a is small or q is tiny. // Case 1: a <= 1 : Inverse Cornish Fisher is unreliable in this area var e = Math.Exp(-z); if (q <= e) { if (q == e) { return(1); } maxA = 1; // If a <= 1, P(a, x) = 1 - Q(a, x) >= (1-e^-x)^a var den = (z > 0.5) ? Math2.Log1p(-Math.Exp(-z)) : Math.Log(-Math2.Expm1(-z)); var limit = Math2.Log1p(-q) / den; if (limit < 1) { minA = Math.Max(minA, limit); } step = (1 - minA) * 0.125; aGuess = minA * (1 + step); if (minA < 0.20) { // Use a first order approximation Q(a, z) at a=0 // Mathematica: Assuming[ a >= 0 && z > 0, FunctionExpand[Normal[Series[GammaRegularized[a, z], {a, 0, 1}]]]] // q = -a * ExpIntegralEi[-z] // The smaller a is the better the guess // as z->0 term2/term1->1/2*Log[z], which is adequate for double precision aGuess = -q / Math2.Expint(-z); if (aGuess <= DoubleLimits.MachineEpsilon) { ExtraDebugLog(z, p, q, aGuess, aGuess, step, minA, maxA, 0, ""); return(aGuess); } step = aGuess * 0.125; } else if (minA >= 0.40) { // Use a first order approximation Q(a, z) at a=1 // Mathematica: Assuming[ a >= 0 && z > 0, FunctionExpand[Normal[Series[GammaRegularized[a, z], {a, 1, 1}]]]] var d = Math.Exp(-z) * (Math.Log(z) + Constants.EulerMascheroni) + Math2.Expint(1, z); aGuess = (q - Math.Exp(-z)) / d + 1; } aGuess = Math.Max(aGuess, minA); aGuess = Math.Min(aGuess, maxA); var r = SolveA(z, p, q, aGuess, step, minA, maxA); ExtraDebugLog(z, p, q, r.Result, aGuess, step, minA, maxA, r.Iterations, ""); return(r.Result); } // Case 2: a > 1 : Inverse Poisson Corning Fisher approximation improves as a->Infinity minA = 1; aGuess = 0.5 + InversePoissonCornishFisher(z, q, p); if (aGuess > 100) { step = 0.01; } else if (aGuess > 10) { step = 0.01; } else { // our poisson approximation is weak. step = 0.05; } #if EXTRA_DEBUG double og = aGuess; #endif aGuess = Math.Max(aGuess, minA); aGuess = Math.Min(aGuess, maxA); #if EXTRA_DEBUG if (og != aGuess) { gs.Push(og); } #endif // For small values of q, our Inverse Poisson Cornish Fisher guess can be way off // Try to come up with a better guess if (q < DoubleLimits.MachineEpsilon) { step *= 10; if (Math.Abs((aGuess - 1) / z) < 1.0 / 8) { #if EXTRA_DEBUG double old = aGuess; #endif (aGuess, _) = RefineQ_SmallA_LargeZ(z, p, q, aGuess); #if EXTRA_DEBUG if (old != aGuess) { gs.Push(old); } #endif } } var rr = SolveA(z, p, q, aGuess, step, minA, maxA); #if EXTRA_DEBUG if (rr.Iterations > 6) { string guesses = (gs.Count == 0) ? string.Empty : ", og: (" + string.Join(", ", gs) + ")"; ExtraDebugLog(z, p, q, rr.Result, aGuess, step, minA, maxA, rr.Iterations, guesses); } #endif return(rr.Result); }
// get the upper and lower limits on x for the GammaInv functions static (double upperLimit, double lowerLimit) GammaInvLimits(double a, double p, double q, bool getQ) { Debug.Assert(a > 0 && a != 1); Debug.Assert(p >= 0 && p <= 1); Debug.Assert(q >= 0 && q <= 1); // set the upper and lower limits double lower = 0; double upper = double.MaxValue; // Find the upper or lower limits using http://dlmf.nist.gov/8.10.E11 // For Q, the limits are: // limit1 = -ln(1-(1-q)^(1/a)) // limit2 = limit1 * (Gamma(1+a)^(1/a)) // For P, the limits are // limit1 = -ln(1-p^(1/a)) // limit2 = limit1 * (Gamma(1+a)^(1/a)) double limit1; if (getQ) { // if (1-q)^(1/a) > 1/2 double y = Math2.Log1p(-q) / a; if (y > -Constants.Ln2) { limit1 = -Math.Log(-Math2.Expm1(y)); } else { limit1 = -Math2.Log1p(-Math.Exp(y)); } } else { // double y = Math.Exp(Math.Log(p)/a); double y = Math.Pow(p, 1 / a); if (y > 0.5) { limit1 = -Math.Log(-Math2.Expm1(Math.Log(p) / a)); } else { limit1 = -Math2.Log1p(-y); } } if (a < 1) { upper = limit1; lower = limit1 * Math.Exp(Math2.Log1p(Math2.Tgamma1pm1(a)) / a); } else if (a > 1) { lower = limit1; upper = limit1 * Math.Exp(Math2.Lgamma(1 + a) / a); } Debug.Assert(upper >= lower, "Upper < lower"); // Our computations for upper and lower can be slightly off in the last few digits // which can be problematic when the solution is close to max or min // So, give ourselves some wiggle room const double factor = 1.125; return(lower / factor, upper *factor); }