// series near the origin; this is entirely analogous to the Bessel series near the origin // it has a corresponding radius of rapid convergence, x < 4 + 2 Sqrt(nu) // This is exactly the same as BesselJ_Series with xx -> -xx. // We could even factor this out into a common method with an additional parameter. private static void ModifiedBesselI_Series(double nu, double x, out double I, out double IP) { if (x == 0.0) { if (nu == 0.0) { I = 1.0; IP = 0.0; } else if (nu < 1.0) { I = 0.0; IP = Double.PositiveInfinity; } else if (nu == 1.0) { I = 0.0; IP = 0.5; } else { I = 0.0; IP = 0.0; } } else { double x2 = x / 2.0; double xx = x2 * x2; double dI; if (nu < 128.0) { dI = Math.Pow(x2, nu) / AdvancedMath.Gamma(nu + 1.0); } else { dI = Math.Exp(nu * Math.Log(x2) - AdvancedMath.LogGamma(nu + 1.0)); } I = dI; IP = nu * dI; for (int k = 1; k < Global.SeriesMax; k++) { double I_old = I; double IP_old = IP; dI *= xx / k / (nu + k); I += dI; IP += (nu + 2 * k) * dI; if ((I == I_old) && (IP == IP_old)) { IP = IP / x; return; } } } }
private static double LogDoubleFactorial_Gamma(int n) { if (n % 2 == 0) { // m = n/2, n!! = 2^m Gamma(m+1) int m = n / 2; return(m * Global.LogTwo + AdvancedMath.LogGamma(m + 1.0)); } else { // m = (n+1)/2, n!! = 2^m Gamma(m+1/2) / Sqrt(PI) int m = (n + 1) / 2; return(m * Global.LogTwo + AdvancedMath.LogGamma(m + 0.5) - Math.Log(Math.PI) / 2.0); } }
/// <summary> /// Computes the logrithm of the factorial of an integer. /// </summary> /// <param name="n">The argument, which must be non-negative.</param> /// <returns>The value of ln(n!).</returns> /// <remarks> /// <para>This function provides accurate values of ln(n!) even for values of n which would cause n! to overflow.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <seealso cref="Factorial"/> public static double LogFactorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException(nameof(n)); } else if (n < factorialTable.Length) { return(Math.Log((double)factorialTable[n])); } else { return(AdvancedMath.LogGamma(n + 1)); } }
/// <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); } }