// 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) { Debug.Assert(x > 0.0); double x2 = 0.5 * x; double xx = x2 * x2; double dI = AdvancedMath.PowerOverFactorial(x2, nu); 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 += (2 * k + nu) * dI; if ((I == I_old) && (IP == IP_old)) { IP = IP / x; return; } } throw new NonconvergenceException(); }
// Regular Bessel function series about origin // J_{\nu}(x) = (x/2)^{\nu} \sum{k=0}^{\infty} \frac{(-x^2/4)^k}{k! \Gamma(\nu + k + 1)} // This can be turned into a series for J' by term-by-term differentiation // For nu=0, x~1 it requires ~10 terms, x~4 requires ~17 terms, x~8 requires ~24 terms // Since nu appears in the denominator, higher nu converges faster // Since first correction term is ~\frac{x^2}{4\nu}, the radius of fast convergence should grow ~2\sqrt{\nu} private static void BesselJ_Series(double nu, double x, out double J, out double JP) { // We divide by x to deal with derivative, so handle x = 0 separately. Debug.Assert(x > 0.0); // Evaluate the series of J and J', knowing x != 0 // Note that the J' series is just with J series with teach term having one less power, // and each term multipled by the power it has in the J series. double x2 = 0.5 * x; double x22 = -x2 * x2; double dJ = AdvancedMath.PowerOverFactorial(x2, nu); J = dJ; JP = nu * dJ; for (int k = 1; k < Global.SeriesMax; k++) { double J_old = J; double JP_old = JP; dJ *= x22 / (k * (nu + k)); J += dJ; JP += (nu + 2 * k) * dJ; if ((J == J_old) && (JP == JP_old)) { JP = JP / x; return; } } // The J series alone requires 5 ops per term; J' evaulation adds 2 ops per term. throw new NonconvergenceException(); }
private static double ModifiedBesselI_Series(double nu, double x) { double x2 = 0.5 * x; double xx = x2 * x2; double dI = AdvancedMath.PowerOverFactorial(x2, nu); double I = dI; for (int k = 1; k < Global.SeriesMax; k++) { double I_old = I; dI = dI * xx / (k * (nu + k)); I += dI; if (I == I_old) { return(I); } } throw new NonconvergenceException(); }
// **** Real order Bessel functions **** // Series development of Bessel J // J_{\nu}(x) = \sum_{k=0}^{\infty} \frac{( )^{\nu + 2k}}{\Gamma(\nu + 2k + 1)} // As can be seen by comparing leading term and first correction, this is good for z <~ \max(1 , \sqrt{\nu}) // For nu=0, it requires 10 terms at x~1.0, 25 terms at x~10.0, but accuracy in the last few digits suffers that far out // Gets better for higher nu (for nu=10, only 20 terms are required at x~10.0 and all digits are good), but only with sqrt(nu) private static double BesselJ_Series(double nu, double x) { double z = 0.5 * x; double dJ = AdvancedMath.PowerOverFactorial(z, nu); double J = dJ; double zz = -z * z; for (int k = 1; k < Global.SeriesMax; k++) { double J_old = J; dJ *= zz / (k * (nu + k)); J += dJ; if (J == J_old) { return(J); } } throw new NonconvergenceException(); }
// this is just an integer version of the series we implement below for doubles; // having an integer-specific version is slightly faster private static double BesselJ_Series(int n, double x) { Debug.Assert(n >= 0); Debug.Assert(x >= 0.0); double z = 0.5 * x; double dJ = AdvancedMath.PowerOverFactorial(z, n); double J = dJ; double zz = -z * z; for (int k = 1; k < Global.SeriesMax; k++) { double J_old = J; dJ *= zz / ((n + k) * k); J += dJ; if (J == J_old) { return(J); } } throw new NonconvergenceException(); }
/// <summary> /// Computes the given Bernoulli number. /// </summary> /// <param name="n">The index of the Bernoulli number to compute, which must be non-negative.</param> /// <returns>The Bernoulli number B<sub>n</sub>.</returns> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <remarks> /// <para>B<sub>n</sub> vanishes for all odd n except n=1. For n about 260 or larger, B<sub>n</sub> overflows a double.</para> /// </remarks> /// <seealso href="http://en.wikipedia.org/wiki/Bernoulli_number"/> /// <seealso href="http://mathworld.wolfram.com/BernoulliNumber.html"/> public static double BernoulliNumber(int n) { if (n < 0) { throw new ArgumentOutOfRangeException(nameof(n)); } // B_1 is the only odd Bernoulli number. if (n == 1) { return(-1.0 / 2.0); } // For all other odd arguments, return zero. if (n % 2 != 0) { return(0.0); } // If the argument is small enough, look up the answer in our stored array. int m = n / 2; if (m < Bernoulli.Length) { return(Bernoulli[m]); } // Otherwise, use the relationship with the Riemann zeta function to get the Bernoulli number. // Since this is only done for large n, it would probably be faster to just sum the zeta series explicitly here. double B = 2.0 * AdvancedMath.RiemannZeta(n) / AdvancedMath.PowerOverFactorial(Global.TwoPI, n); if (m % 2 == 0) { B = -B; } return(B); }