/// <summary> /// Implements the Exponential Series for integer n /// <para>Sum = ((-z)^(n-1)/(n-1)!) * (ψ(n)-Log(z)) - Σ( (k==n-1) ? 0 : ((-1)^k * z^k)/((k-n+1) * k!)) k={0,∞}</para> /// </summary> /// <param name="n"></param> /// <param name="z"></param> /// <returns></returns> /// <see href="http://functions.wolfram.com/GammaBetaErf/ExpIntegralE/06/01/04/01/02/0005/"/> public static double En_Series(int n, double z) { Debug.Assert(n > 0); const double DefaultTolerance = 2 * DoubleLimits.MachineEpsilon; // the sign of the summation is negative // so negate these double sum = (n > 1) ? -1.0 / (1.0 - n) : 0; double term = -1.0; int k = 1; for (; k < n - 1; k++) { term *= -z / k; if (term == 0) { break; } sum += term / (k - n + 1); } sum += Math.Pow(-z, n - 1) * (Math2.Digamma(n) - Math.Log(z)) / Math2.Factorial(n - 1); if (term == 0) { return(sum); } // skip n-1 term term *= -z / k; for (int i = 0; i < Policies.MaxSeriesIterations; i++) { double prevSum = sum; k++; term *= -z / k; double delta = term / (k - n + 1); sum += delta; if (Math.Abs(delta) <= Math.Abs(prevSum) * DefaultTolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge in {0} iterations", Policies.MaxSeriesIterations); return(double.NaN); }
/// <summary> /// Returns Y{n}(z) for positive integer order and z <= sqrt(eps) /// </summary> /// <param name="n"></param> /// <param name="z"></param> /// <returns></returns> static DoubleX YN_SmallArg(int n, double z) { Debug.Assert(n >= 0); Debug.Assert(z <= DoubleLimits.RootMachineEpsilon._2); // // See http://functions.wolfram.com/Bessel-TypeFunctions/BesselY/06/01/04/01/02/ // // Note that when called we assume that x < epsilon and n is a positive integer. // if (n == 0) { return(new DoubleX((2 / Math.PI) * (Math.Log(z / 2) + Constants.EulerMascheroni))); } if (n == 1) { //return (z / Math.PI) * Math.Log(z / 2) - 2 / (Math.PI * z) - (z / (2 * Math.PI)) * (1 - 2 * Constants.EulerMascheroni); return(new DoubleX((z / 2 * (2 * Math.Log(z / 2) - (1 - 2 * Constants.EulerMascheroni)) - 2 / z) / Math.PI)); } if (n == 2) { double z2 = z * z; return(new DoubleX((z2 / 4 * (Math.Log(z / 2) - (3 - 4 * Constants.EulerMascheroni)) - (4 / z2)) / Math.PI)); } // To double check the maximum x value on Mathematica // BesselYDiff[n_, x_] := Block[{ a = BesselY[n, x], b = -(Factorial[n - 1]/Pi)*(x/2)^(-n)}, (b - a)/a] // Table[Block[{eps = 2^-52}, N[BesselYDiff[n, Sqrt[eps]], 20]], {n, 3, 100}] double num = -((Math2.Factorial(n - 1) / Math.PI)); double den = Math.Pow(z / 2, n); if (double.IsInfinity(num) || den == 0) { return(DoubleX.NegativeInfinity); } DoubleX result = new DoubleX(num) / new DoubleX(den); return(result); }
/// <summary> /// Returns the rising factorial: /// <para>(x)_{n} = Γ(x+n)/Γ(x)</para> /// </summary> /// <returns> /// <para> if n > 0, x*(x+1)*(x+2)...*(x+(n-1))</para> /// <para> if n == 0, 1</para> /// <para> if n < 0, 1/((x-1)*(x-2)*...*(x-n))</para> /// </returns> public static double FactorialRising(double x, int n) { // This section uses the following notation: // falling factorial: (x)_{_n} // rising factorial: (x)_{n} // Standard eqn for the rising factorial: // (x)_{n} = Γ(x+n)/Γ(x) double result = double.NaN; // (x)_{0} = 1 if (n == 0) { return(1); } if (double.IsNaN(x)) { Policies.ReportDomainError("FactorialRising(x: {0}, n: {1}): Requires x not NaN", x, n); return(double.NaN); } if (n == int.MinValue) { // avoid -int.MinValue error // this will overflow/underflow, but here it is if (x > 0 && IsInteger(x) && (x + n) <= 0) { Policies.ReportPoleError("FactorialRising(x: {0}, n: {1}): Divide by Zero", x, n); return(double.NaN); } // (x)_{n} = (x)_{n+1}/(x+n) return(FactorialRising(x, n + 1) / (x + n)); } if (x == 0) { // (0)_{n} = 0, n > 0 if (n > 0) { return(0); } // (0)_{-n} = (-1)^n / n! result = Math2.Factorial(-n); if (IsOdd(n)) { result = -result; } return(1 / result); } if (x < 0) { // For x less than zero, we really have a falling // factorial, modulo a possible change of sign. // Note: the falling factorial isn't defined for negative n // so cover that first if (n < -x) { // handle (n < 0 || ( n > 0 && n < -x)) directly // (x)_{n} = Γ(1-x)/Γ(1-x-n) result = Math2.TgammaDeltaRatio(1 - x, -n); } else { Debug.Assert(n > 0); result = FactorialFalling(-x, n); } if (IsOdd(n)) { result = -result; } return(result); } // x > 0 // standard case: (n > 0 || (n < 0 && x > -n)) // (x)_{n} = Γ(x+n)/Γ(x) if (-x < n) { return(1 / Math2.TgammaDeltaRatio(x, n)); } Debug.Assert((x + n) <= 0); // (x)_{n} = (-1)^n * Γ(1-x)/Γ(1-x-n) if (x < 1) { result = TgammaDeltaRatio(1 - x, -n); if (IsOdd(n)) { result = -result; } return(result); } // x >= 1 result = FactorialFalling(x - 1, -n); if (result == 0) { if (IsInteger(x)) { Policies.ReportPoleError("FactorialRising(x: {0}, n: {1}): Divide by Zero", x, n); return(double.NaN); } return(double.PositiveInfinity); } return(1 / result); }
/// <summary> /// Returns the falling factorial: /// <para>(x)_{_n} = x*(x-1)*(x-2)...*(x-(n-1)) = Γ(x+1)/Γ(x-n+1)</para> /// </summary> /// <param name="x"></param> /// <param name="n">Requires n ≥ 0</param> /// <returns> /// <para>if n > 0, x*(x-1)*(x-2)...*(x-(n-1))</para> /// <para>if n == 0, 1</para> /// </returns> public static double FactorialFalling(double x, int n) { // This section uses the following notation: // falling factorial: (x)_{_n} // rising factorial: (x)_{n} // Standard eqn for the falling factorial: // (x)_{_n} = Γ(x+1)/Γ(x+1-n) double result = double.NaN; // (x)_{_0} = 1 if (n == 0) { return(1); } if ((double.IsNaN(x)) || (n < 0)) { Policies.ReportDomainError("FactorialFalling(x: {0}, n: {1}): Requires finite x; n >= 0", x, n); return(double.NaN); } if (n == 1) { return(x); } if (x == 0) { return(0); } if (x < 0) { // // For x < 0 we really have a rising factorial // modulo a possible change of sign: // (x)_{_n} = (-1)^n * (-x)_{n} result = FactorialRising(-x, n); return(IsOdd(n) ? -result : result); } // standard case: Γ(x+1)/Γ(x+1-n) if (x > n - 1) { // (n)_{_n} = n! if (x == n) { return(Math2.Factorial(n)); } // (n)_{_n+1} = n!, n>1 if (x == n + 1) { Debug.Assert(n > 1); return(Math2.Factorial(n + 1)); } return(Math2.TgammaDeltaRatio(x + 1, -n)); } Debug.Assert(x <= n - 1); if (x < 1) { int m = n - 1; double MaxFactorial = Math2.MaxFactorialIndex; if (m < MaxFactorial) { result = x / TgammaDeltaRatio(1 - x, m); } else { // try not to overflow/underflow too soon double m2 = m - MaxFactorial; result = (x / TgammaDeltaRatio(1 - x, MaxFactorial)) / TgammaDeltaRatio(1 - x + MaxFactorial, m2); } return(IsOdd(m) ? -result : result); } // // x+1-n will be negative and TgammaDeltaRatio won't // handle it, split the product up into three parts: // double xp1 = x + 1; int n2 = (int)xp1; if (n2 == xp1) { return(0); } result = Math2.TgammaDeltaRatio(xp1, -n2); x -= n2; result *= x; ++n2; if (n2 < n) { result *= FactorialFalling(x - 1, n - n2); } return(result); }