Пример #1
0
        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));
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
        /// <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);
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        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));
        }
Пример #6
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        // 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);
        }