/// <summary> /// Computes the digamma function. /// </summary> /// <param name="x">The argument.</param> /// <returns>The value of ψ(x).</returns> /// <remarks> /// <para>The psi function, also called the digamma function, is the logrithmic derivative of the Γ function.</para> /// <img src="../images/DiGamma.png" /> /// <para>To evaluate the Psi function for complex arguments, use <see cref="AdvancedComplexMath.Psi" />.</para> /// </remarks> /// <seealso cref="Gamma(double)"/> /// <seealso cref="AdvancedComplexMath.Psi"/> /// <seealso href="http://en.wikipedia.org/wiki/Digamma_function" /> /// <seealso href="http://mathworld.wolfram.com/DigammaFunction.html" /> public static double Psi(double x) { if (x <= 0.0) { if (x == Math.Ceiling(x)) { // there are poles at zero and negative integers return(Double.NaN); } else { // use the reflection formula to change to a positive x return(Psi(1.0 - x) - Math.PI / Math.Tan(Math.PI * x)); } } else if (x < 16.0) { return(Lanczos.Psi(x)); } else { // for large arguments, the Stirling asymptotic expansion is faster than the Lanzcos approximation return(Stirling.Psi(x)); //return (Psi_Stirling(x)); } }
/// <summary> /// Computes the logarithm of the Beta function. /// </summary> /// <param name="a">The first parameter, which must be positive.</param> /// <param name="b">The second parameter, which must be positive.</param> /// <returns>The value of ln(B(a,b)).</returns> /// <remarks> /// <para>This function accurately computes ln(B(a,b)) even for values of a and b for which B(a,b) is /// too small or large to be represented by a double.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="a"/> or <paramref name="b"/> is negative or zero.</exception> /// <seealso cref="Beta(System.Double,System.Double)"/> public static double LogBeta(double a, double b) { if ((a > 16.0) && (b > 16.0)) { return(Stirling.LogBeta(a, b)); } else if (a < 0.0) { throw new ArgumentOutOfRangeException(nameof(a)); } else if (b < 0.0) { throw new ArgumentOutOfRangeException(nameof(b)); } else if (Double.IsNaN(a) || Double.IsNaN(b)) { return(Double.NaN); } else if (a == 0.0 || b == 0.0) { return(Double.PositiveInfinity); } else { return(Lanczos.LogBeta(a, b)); } }
/// <summary> /// Computes the Gamma function. /// </summary> /// <param name="x">The argument.</param> /// <returns>The value of Γ(x).</returns> /// <remarks> /// <para>The Gamma function is a generalization of the factorial (see <see cref="AdvancedIntegerMath.Factorial"/>) to arbitrary real values.</para> /// <img src="../images/GammaIntegral.png" /> /// <para>For positive integer arguments, this integral evaluates to Γ(n+1)=n!, but it can also be evaluated for non-integer z.</para> /// <para>Like the factorial, Γ(x) grows rapidly with increasing x; Γ(x) overflows <see cref="System.Double" /> /// for all x larger than ~171. For arguments in this range, you may find it useful to work with the <see cref="LogGamma" /> method, which /// returns accurate values for ln(Γ(x)) even in the range for which Γ(x) overflows.</para> /// <para>To evaluate the Gamma function for a complex argument, use <see cref="AdvancedComplexMath.Gamma" />.</para> /// <h2>Domain, Range, and Accuracy</h2> /// <para>The function is defined for all x. It has poles at all negative integers and at zero; the method returns <see cref="Double.NaN"/> for these arguments. For positive /// arguments, the value of the function increases rapidly with increasing argument. For values of x greater than about 170, the value of the function exceeds /// <see cref="Double.MaxValue"/>; for these arguments the method returns <see cref="Double.PositiveInfinity"/>. The method is accurate to full precision over its entire /// domain.</para> /// </remarks> /// <seealso cref="AdvancedIntegerMath.Factorial" /> /// <seealso cref="LogGamma" /> /// <seealso cref="AdvancedComplexMath.Gamma" /> /// <seealso href="http://en.wikipedia.org/wiki/Gamma_function" /> /// <seealso href="http://mathworld.wolfram.com/GammaFunction.html" /> /// <seealso href="http://dlmf.nist.gov/5">DLMF on the Gamma Function</seealso> public static double Gamma(double x) { if (x < 0.5) { // Use \Gamma(x) \Gamma(1-x) = \frac{\pi}{\sin(\pi x)} to move values close to and left of origin to x > 0 return(Math.PI / MoreMath.SinPi(x) / Gamma(1.0 - x)); } else if (x < 1.5) { return(GammaSeries.GammaOnePlus(x - 1.0)); } else if (x < 2.5) { return(GammaSeries.GammaTwoPlus(x - 2.0)); } else if (x < 16.0) { return(Lanczos.Gamma(x)); } else if (x < 172.0) { return(Stirling.Gamma(x)); } else if (x <= Double.PositiveInfinity) { // For x >~ 172, Gamma(x) overflows. return(Double.PositiveInfinity); } else { return(Double.NaN); } }
/// <summary> /// Computes the digamma function. /// </summary> /// <param name="x">The argument.</param> /// <returns>The value of ψ(x).</returns> /// <remarks> /// <para>The psi function, also called the digamma function, is the logrithmic derivative of the Γ function.</para> /// <img src="../images/DiGamma.png" /> /// <para>Because it is defined as a <i>logarithmic</i> derivative, the digamma function does not overflow <see cref="System.Double"/> /// even for arguments for which <see cref="Gamma(double)"/> does.</para> /// <para>To evaluate the psi function for complex arguments, use <see cref="AdvancedComplexMath.Psi" />.</para> /// </remarks> /// <seealso cref="Gamma(double)"/> /// <seealso cref="AdvancedComplexMath.Psi"/> /// <seealso href="http://en.wikipedia.org/wiki/Digamma_function" /> /// <seealso href="http://mathworld.wolfram.com/DigammaFunction.html" /> public static double Psi(double x) { if (x < 0.5) { return(Psi(1.0 - x) - Math.PI / MoreMath.TanPi(x)); } else if (x < 1.5) { return(GammaSeries.PsiOnePlus(x - 1.0)); } else if (x < 2.5) { return(GammaSeries.PsiTwoPlus(x - 2.0)); } else if (x < 16.0) { return(Lanczos.Psi(x)); } else if (x <= Double.PositiveInfinity) { // For large arguments, the Stirling asymptotic expansion is faster than the Lanzcos approximation return(Stirling.Psi(x)); } else { return(Double.NaN); } }
// The point of this method is to compute // e G_{e}(x) = \frac{1}{\Gamma(x)} - \frac{1}{\Gamma(x + e)} // To do this, we will re-use machinery that we developed to accurately compute the Pochhammer symbol // (x)_e = \frac{\Gamma(x + e)}{\Gamma(x)} // To do this, we use the reduced log Pochhammer function L_{e}(x). // \ln((x)_e) = e L_{e}(x) // To see why we developed this function, see the Pochhamer code. The Lanczos apparatus allows us to compute // L_{e}(x) accurately, even in the small-e limit. To see how look at the Pochhammer code. // To connect G_{e}(x) to L_{e}(x), write // e G_{e}(x) = \frac{(x)_e - 1}{\Gamma(x + e)} // = \frac{\exp(\ln((x)_e)) - 1}{\Gamma(x + e)} // = \frac{\exp(e L_{e}(x)) - 1}{\Gamma(x + e)} // G_{e}(x) = \frac{E_{e}(L_{e}(x))}{\Gamma(x + e)} // where e E_{e}(x) = \exp(e x) - 1, which we also know how to compute accurately even in the small-e limit. // This deals with G_{e}(x) for positive x. But L_{e}(x) and \Gamma(x + e) still blow up for x or x + e // near a non-positive integer, and our Lanczos machinery for L_{e}(x) assumes positive x. To deal with // the left half-plane, use the reflection formula // \Gamma(z) \Gamma(1 - z) = \frac{\pi}{\sin(\pi z)} // on both Gamma functions in the definition of G_{e}(x) to get // e G_{e}(x) = \frac{\sin(\pi x)}{\pi} \Gamma(1 - x) - \frac{\sin(\pi x + \pi e)}{\pi} \Gamma(1 - x - e) // Use the angle addition formula on the second \sin and the definition of the Pochhammer symbol // to get all terms proportional to one Gamma function with a guaranteed positive argument. // \frac{e G_{e}(x)}{\Gamma(1 - x - e)} = // \frac{\sin(\pi x)}{\pi} \left[ (1 - x - e)_{e} - \cos(\pi e) \right] - \frac{\cos(\pi x) \sin(\pi e)}{\pi} // We need the RHS ~e to for small e. That's manifestly true for the second term because of the factor \sin(\pi e). // It's true for the second term because (1 - x - e)_{e} and \cos(\pi e) are both 1 + O(e), but to avoid cancelation // we need to make it manifest. Write // (y)_{e} = \exp(e L_{e}(y)) - 1 + 1 = e E_{e}(L_{e}(y)) + 1 // and // 1 - \cos(\pi e) = 2 \sin^2(\half \pi /e) // Now we can divide through by e. // \frac{G_{e}(x)}{\Gamma(y)} = // \sin(\pi x) \left[ \frac{E_{e}(L_{e}(y))}{\pi} + \frac{\sin^2(\half \pi e)}{\half \pi e} \right] - // \cos(\pi x) \frac{\sin(\pi e)}{\pi e} // and everything can be safely computed. // This is a different approach than the one in the Michel & Stoitsov paper. Their approach also used // the Lanczos evaluation of the Pochhammer symbol, but had some deficiencies. // For example, for e <~ 1.0E-15 and x near a negative integer, it gives totally wrong // answers, and the answers loose accuracy for even larger e. This is because the // computation relies on a ratio of h to Gamma, both of which blow up in this region. private static double NewG(double x, double e) { Debug.Assert(Math.Abs(e) <= 0.5); // It would be better to compute G outright from Lanczos, rather than via h. Can we do this? // Also, we should probably pick larger of 1 - x and 1 - x - e to use as argument of // Gamma function factor. double y = x + e; if ((x < 0.5) || (y < 0.5)) { double h = MoreMath.ReducedExpMinusOne(Lanczos.ReducedLogPochhammer(1.0 - y, e), e); if (e == 0.0) { double t = MoreMath.SinPi(x) * h / Math.PI - MoreMath.CosPi(x); return(AdvancedMath.Gamma(1.0 - y) * t); } else { double s = MoreMath.SinPi(e) / (Math.PI * e); double s2 = MoreMath.Sqr(MoreMath.SinPi(e / 2.0)) / (Math.PI * e / 2.0); double t = MoreMath.SinPi(x) * (h / Math.PI + s2) - MoreMath.CosPi(x) * s; return(AdvancedMath.Gamma(1.0 - y) * t); } } return(MoreMath.ReducedExpMinusOne(Lanczos.ReducedLogPochhammer(x, e), e) / AdvancedMath.Gamma(x + e)); }
// one-argument functions /// <summary> /// Computes the natural logarithm of the Gamma function. /// </summary> /// <param name="x">The argument, which must be positive.</param> /// <returns>The log Gamma function ln(Γ(x)).</returns> /// <remarks> /// <para>Because Γ(x) grows rapidly for increasing positive x, it is often necessary to /// work with its logarithm in order to avoid overflow. This function returns accurate /// values of ln(Γ(x)) even for values of x which would cause Γ(x) to overflow.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative.</exception> /// <seealso cref="Gamma(double)" /> public static double LogGamma(double x) { if (x < 0.0) { throw new ArgumentOutOfRangeException(nameof(x)); } else if (x < 16.0) { // For small arguments, use the Lanczos approximation. return(Lanczos.LogGamma(x)); } else if (x < Double.PositiveInfinity) { // For large arguments, the asymptotic series is even faster than the Lanczos approximation. return(Stirling.LogGamma(x)); } else if (x == Double.PositiveInfinity) { // Precisely at infinity x * Math.Log(x) - x => NaN, so special-case it. return(Double.PositiveInfinity); } else { return(Double.NaN); } }
/// <summary> /// Computes the complex digamma (ψ) function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The value of ψ(z).</returns> /// <remarks> /// <para>The image below shows the complex ψ function near the origin using domain coloring.</para> /// <img src="../images/ComplexPsiPlot.png" /> /// </remarks> /// <seealso cref="AdvancedMath.Psi(double)" /> public static Complex Psi(Complex z) { if (z.Re < 0.5) { // reduce z.Re in order to handle large real values! return(Psi(1.0 - z) - Math.PI / ComplexMath.Tan(Math.PI * z)); } else { // add Stirling for large z return(Lanczos.Psi(z)); } }
/// <summary> /// Compute the complex log Gamma function. /// </summary> /// <param name="z">The complex argument, which must have a non-negative real part.</param> /// <returns>The complex value ln(Γ(z)).</returns> /// <exception cref="ArgumentOutOfRangeException">The real part of <paramref name="z"/> is negative.</exception> /// <seealso cref="AdvancedMath.LogGamma" /> public static Complex LogGamma(Complex z) { if (z.Re < 0.0) { throw new ArgumentOutOfRangeException("z"); } else if (ComplexMath.Abs(z) < 16.0) { return(Lanczos.LogGamma(z)); } else { return(LogGamma_Stirling(z)); } }
/// <summary> /// Computes the complex Gamma function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The complex value of Γ(z).</returns> /// <remarks> /// <para>The image below shows the complex Γ function near the origin using domain coloring.</para> /// <img src="../images/ComplexGammaPlot.png" /> /// </remarks> /// <seealso cref="AdvancedMath.Gamma(double)"/> /// <seealso href="http://en.wikipedia.org/wiki/Gamma_function" /> /// <seealso href="http://mathworld.wolfram.com/GammaFunction.html" /> public static Complex Gamma(Complex z) { if (z.Re < 0.5) { // 1-z form return(Math.PI / Gamma(1.0 - z) / ComplexMath.SinPi(z)); } else if (ComplexMath.Abs(z) < 16.0) { return(Lanczos.Gamma(z)); } else { // Add flag to do z-reduction return(ComplexMath.Exp(LogGamma_Stirling(z))); } }
// one-argument functions /// <summary> /// Computes the natural logrithm of the Gamma function. /// </summary> /// <param name="x">The argument, which must be positive.</param> /// <returns>The log Gamma function ln(Γ(x)).</returns> /// <remarks> /// <para>Because Γ(x) grows rapidly for increasing positive x, it is often necessary to /// work with its logarithm in order to avoid overflow. This function returns accurate /// values of ln(Γ(x)) even for values of x which would cause Γ(x) to overflow.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative or zero.</exception> /// <seealso cref="Gamma(double)" /> public static double LogGamma(double x) { if (x <= 0.0) { throw new ArgumentOutOfRangeException("x"); } else if (x < 16.0) { // For small arguments, use the Lanczos approximation. return(Lanczos.LogGamma(x)); } else { // For large arguments, the asymptotic series is even faster than the Lanczos approximation. return(Stirling.LogGamma(x)); //return (LogGamma_Stirling(x)); } }
/// <summary> /// Computes the lograrithm of the Beta function. /// </summary> /// <param name="a">The first parameter, which must be positive.</param> /// <param name="b">The second parameter, which must be positive.</param> /// <returns>The value of ln(B(a,b)).</returns> /// <remarks> /// <para>This function accurately computes ln(B(a,b)) even for values of a and b for which B(a,b) is /// too small or large to be represented by a double.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="a"/> or <paramref name="b"/> is non-positive.</exception> /// <seealso cref="Beta(System.Double,System.Double)"/> public static double LogBeta(double a, double b) { if (a <= 0.0) { throw new ArgumentOutOfRangeException("a"); } if (b <= 0.0) { throw new ArgumentOutOfRangeException("b"); } if ((a > 16.0) && (b > 16.0)) { return(Stirling.LogBeta(a, b)); } else { return(Lanczos.LogBeta(a, b)); } }
// one-argument functions /// <summary> /// Computes the natural logarithm of the Gamma function. /// </summary> /// <param name="x">The argument, which must be positive.</param> /// <returns>The log Gamma function ln(Γ(x)).</returns> /// <remarks> /// <para>Because Γ(x) grows rapidly for increasing positive x, it is often necessary to /// work with its logarithm in order to avoid overflow. This function returns accurate /// values of ln(Γ(x)) even for values of x which would cause Γ(x) to overflow.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative.</exception> /// <seealso cref="Gamma(double)" /> public static double LogGamma(double x) { if (x < 0.0) { throw new ArgumentOutOfRangeException(nameof(x)); } else if (x < 0.5) { // For small arguments, use the Lanczos approximation. return(Lanczos.LogGamma(x)); } else if (x < 1.5) { // Use the series expansion near 1. return(GammaSeries.LogGammaOnePlus(x - 1.0)); } else if (x < 2.5) { // The series expansion can be adapted near 2, too. return(GammaSeries.LogGammaTwoPlus(x - 2.0)); } else if (x < 16.0) { // In between, we still use Lanczos. return(Lanczos.LogGamma(x)); } else if (x < Double.PositiveInfinity) { // For large arguments, the asymptotic series is even faster than the Lanczos approximation. return(Stirling.LogGamma(x)); } else if (x == Double.PositiveInfinity) { // Precisely at infinity x * Math.Log(x) - x => NaN, so special-case it. return(Double.PositiveInfinity); } else { return(Double.NaN); } }
/// <summary> /// Compute the complex log Gamma function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The principal complex value y for which exp(y) = Γ(z).</returns> /// <seealso cref="AdvancedMath.LogGamma" /> /// <seealso href="http://mathworld.wolfram.com/LogGammaFunction.html"/> public static Complex LogGamma(Complex z) { if (z.Im == 0.0 && z.Re < 0.0) { // Handle the pure negative case explicitly. double re = Math.Log(Math.PI / Math.Abs(MoreMath.SinPi(z.Re))) - AdvancedMath.LogGamma(1.0 - z.Re); double im = Math.PI * Math.Floor(z.Re); return(new Complex(re, im)); } else if (z.Re > 16.0 || Math.Abs(z.Im) > 16.0) { // According to https://dlmf.nist.gov/5.11, the Stirling asymptoic series is valid everywhere // except on the negative real axis. So at first I tried to use it for |z.Im| > 0, |z| > 16. But in practice, // it exhibits false convergence close to the negative real axis, e.g. z = -16 + i. So I have // moved to requiring |z| large and reasonably far from the negative real axis. return(Stirling.LogGamma(z)); } else if (z.Re >= 0.125) { return(Lanczos.LogGamma(z)); } else { // For the remaining z < 0, we need to use the reflection formula. // For large z.Im, SinPi(z) \propto e^{\pi |z.Im|} overflows even though its log does not. // It's possible to do some algebra to get around that problem, but it's not necessary // because for z.Im that big we would have used the Stirling series. Complex f = ComplexMath.Log(Math.PI / ComplexMath.SinPi(z)); Complex g = Lanczos.LogGamma(1.0 - z); // The reflection formula doesn't stay on the principal branch, so we need to add a multiple of 2 \pi i // to fix it up. See Hare, "Computing the Principal Branch of Log Gamma" for how to do this. // https://pdfs.semanticscholar.org/1c9d/8865836a312836500126cb47c3cbbed3043e.pdf Complex h = new Complex(0.0, 2.0 * Math.PI * Math.Floor(0.5 * (z.Re + 0.5))); if (z.Im < 0.0) { h = -h; } return(f - g + h); } }
/// <summary> /// Computes the Gamma function. /// </summary> /// <param name="x">The argument.</param> /// <returns>The value of Γ(x).</returns> /// <remarks> /// <para>The Gamma function is a generalization of the factorial (see <see cref="AdvancedIntegerMath.Factorial"/>) to arbitrary real values.</para> /// <img src="../images/GammaIntegral.png" /> /// <para>For positive integer arguments, this integral evaluates to Γ(n+1)=n!, but it can also be evaluated for non-integer z.</para> /// <para>Because Γ(x) grows beyond the largest value that can be represented by a <see cref="System.Double" /> at quite /// moderate values of x, you may find it useful to work with the <see cref="LogGamma" /> method, which returns ln(Γ(x)).</para> /// <para>To evaluate the Gamma function for a complex argument, use <see cref="AdvancedComplexMath.Gamma" />.</para> /// <h2>Domain, Range, and Accuracy</h2> /// <para>The function is defined for all x. It has poles at all negative integers and at zero; the method returns <see cref="Double.NaN"/> for these arguments. For positive /// arguments, the value of the function increases rapidly with increasing argument. For values of x greater than about 170, the value of the function exceeds /// <see cref="Double.MaxValue"/>; for these arguments the method returns <see cref="Double.PositiveInfinity"/>. The method is accurate to full precision over its entire /// domain.</para> /// </remarks> /// <seealso cref="AdvancedIntegerMath.Factorial" /> /// <seealso cref="LogGamma" /> /// <seealso cref="AdvancedComplexMath.Gamma" /> /// <seealso href="http://en.wikipedia.org/wiki/Gamma_function" /> /// <seealso href="http://mathworld.wolfram.com/GammaFunction.html" /> /// <seealso href="http://dlmf.nist.gov/5">DLMF on the Gamma Function</seealso> public static double Gamma(double x) { if (x <= 0.0) { if (x == Math.Ceiling(x)) { // poles at zero and negative integers return(Double.NaN); } else { return(Math.PI / Gamma(-x) / (-x) / AdvancedMath.Sin(0.0, x / 2.0)); } } else if (x < 16.0) { return(Lanczos.Gamma(x)); } else { return(Stirling.Gamma(x)); //return (Math.Exp(LogGamma_Stirling(x))); } }
/// <summary> /// Computes the Pochammer symbol (x)<sub>y</sub>. /// </summary> /// <param name="x">The first argument.</param> /// <param name="y">The second argument.</param> /// <returns>The value of (x)<sub>y</sub>.</returns> /// <remarks> /// <para>The Pochhammer symbol is defined as a ratio of Gamma functions.</para> /// <para>For positive integer y, this is equal to a rising factorial product.</para> /// <para>Note that while the Pochhammer symbol notation is very common, combinatorialists sometimes /// use the same notation for a falling factorial.</para> /// <para>If you need to compute a ratio of Gamma functions, in most cases you should compute it by calling this /// function instead of taking the quotient of two calls to the <see cref="Gamma(double)"/> function, /// because there is a large fraction of the parameter space where Gamma(x+y) and Gamma(x) overflow, /// but their quotient does not, and is correctly computed by this method.</para> /// <para>This function accepts both positive and negative values for both x and y.</para> /// </remarks> /// <seealso href="http://mathworld.wolfram.com/PochhammerSymbol.html"/> public static double Pochhammer(double x, double y) { // Handle simplest cases quickly if (y == 0.0) { return(1.0); } else if (y == 1.0) { return(x); } // Should we also handle small integer y explicitly? // The key case of x and y positive is pretty simple. Both the Lanczos and Stirling forms of \Gamma // admit forms that preserve accuracy in \Gamma(x+y) / \Gamma(x) - 1 as y -> 0. It turns // out, however, to be ridiculously complicated to handle all the corner cases that appear in // other regions of the x-y plane. double z = x + y; if (x < 0.25) { bool xNonPositiveInteger = (Math.Round(x) == x); bool zNonPositiveInteger = (z <= 0.0) && (Math.Round(z) == z); if (xNonPositiveInteger) { if (zNonPositiveInteger) { long m; double p; if (z >= x) { m = (long)(z - x); p = Pochhammer(-z + 1, m); } else { m = (long)(x - z); p = 1.0 / Pochhammer(-x + 1, m); } if (m % 2 != 0) { p = -p; } return(p); } else { return(0.0); } } if (y < 0.0) { // x negative, y negative; we can transform to make x and y positive return(1.0 / Pochhammer(1.0 - x, -y) * MoreMath.SinPi(x) / MoreMath.SinPi(z)); } else if (z <= 0.25) { // x negative, y positive, but not positive enough, so z still negative // we can transform to make x and y positive return(Pochhammer(1.0 - z, y) * MoreMath.SinPi(x) / MoreMath.SinPi(z)); } else { // x negative, y positive enough to make z positive return(Gamma(1.0 - x) * Gamma(z) * MoreMath.SinPi(x) / Math.PI); } } else { if (z >= 0.25) { // This is the standard case: x positive, y may be negative, but not negative enough to make z negative double P; if ((x > 16.0) && (z > 16.0)) { P = Stirling.ReducedLogPochhammer(x, y); } else { P = Lanczos.ReducedLogPochhammer(x, y); } return(Math.Exp(y * P)); } else { // if y is very negative, z will also be negative, so we can transform one gamma if (Math.Round(z) == z) { return(Double.PositiveInfinity); } else { return(Math.PI / (Gamma(x) * Gamma(1.0 - z) * MoreMath.SinPi(z))); } } } }