/// <summary> /// Returns (J{v+n}(x), J{v+n-1}(x)) OR (Y{v+n}(x), Y{v+n-1}(x)) after n forward recurrences /// </summary> /// <param name="v">Order of the current parameter</param> /// <param name="x">Argument</param> /// <param name="n">Number of forward recurrence steps. 0 returns (current,prev)</param> /// <param name="current">J{v}(x) OR Y{v}(x)</param> /// <param name="prev">J{v-1}(x) OR Y{v-1}(x)</param> /// <returns></returns> public static (double JYvpn, double JYvpnm1) ForwardJY(double v, double x, int n, double current, double prev) { int scale = 0; for (int k = 0; k < n; k++) { double fact = 2 * (v + k) / x; double value = fact * current - prev; if (double.IsInfinity(value)) { int exp = 0; current = Math2.Frexp(current, out exp); prev = Math2.Ldexp(prev, -exp); scale += exp; value = fact * current - prev; } prev = current; current = value; } if (scale != 0) { current = Math2.Ldexp(current, scale); prev = Math2.Ldexp(prev, scale); } return(current, prev); }
/// <summary> /// Returns (K{v+n}, K{v+n-1}) after n forward recurrences /// </summary> /// <param name="v">Order of the current parameter</param> /// <param name="x">Argument</param> /// <param name="n">Number of forward recurrence steps. 0 returns (current,prev)</param> /// <param name="current">K{v}(x)</param> /// <param name="prev">K{v-1}(x)</param> /// <returns></returns> public static (double Kvpn, double Kvpnm1) ForwardK(double v, double x, int n, double current, double prev) { Debug.Assert(n >= 0); // binary scale int binaryScale = 0; for (int k = 0; k < n; k++) { double factor = 2 * (v + k) / x; double value = factor * current + prev; if (double.IsInfinity(value)) { int exp = 0; current = Math2.Frexp(current, out exp); prev = Math2.Ldexp(prev, -exp); binaryScale += exp; value = factor * current + prev; } prev = current; current = value; } if (binaryScale != 0) { current = Math2.Ldexp(current, binaryScale); prev = Math2.Ldexp(prev, binaryScale); } return(current, prev); }
/// <summary> /// Returns a tuple (J{v+n}(x), J{v+n-1}(x), BScale) OR (Y{v+n}(x), Y{v+n-1}(x), BScale) after n forward recurrences. /// Note: to get actual values multiply by 2^BScale. /// </summary> /// <param name="v">Order of the current parameter</param> /// <param name="x">Argument</param> /// <param name="n">Number of forward recurrence steps. 0 returns (current,prev)</param> /// <param name="current">J{v}(x) OR Y{v}(x)</param> /// <param name="prev">J{v-1}(x) OR Y{v-1}(x)</param> /// <returns></returns> public static (double JYvpn, double JYvpnm1, int BScale) ForwardJY_B(double v, double x, int n, double current, double prev) { Debug.Assert(n >= 0); Debug.Assert(!double.IsInfinity(2 * (v + n) / x)); int binaryScale = 0; for (int k = 0; k < n; k++) { double fact = 2 * (v + k) / x; double value = fact * current - prev; if (double.IsInfinity(value)) { int exp = 0; current = Math2.Frexp(current, out exp); prev = Math2.Ldexp(prev, -exp); binaryScale += exp; value = fact * current - prev; } prev = current; current = value; } { int exp = 0; current = Math2.Frexp(current, out exp); prev = Math2.Ldexp(prev, -exp); binaryScale += exp; } return(current, prev, binaryScale); }
/// <summary> /// Returns (J{v-n}(x), J{v-n+1}(x)) OR (Y{v-n}(x), Y{v-n+1}(x)) after n backward recurrences /// </summary> /// <param name="v">Order of the current parameter</param> /// <param name="x">Argument</param> /// <param name="n">Number of backward recurrence steps. 0 returns (current,prev)</param> /// <param name="current">J{v}(x) OR Y{v}(x)</param> /// <param name="prev">J{v+1}(x) OR Y{v+1}(x)</param> /// <returns></returns> public static (double JYvmn, double JYvmnp1) BackwardJY(double v, double x, int n, double current, double prev) { Debug.Assert(n >= 0); Debug.Assert(!double.IsInfinity(2 * v / x)); int scale = 0; for (int k = 0; k < n; k++) { double fact = 2 * (v - k) / x; double next = fact * current - prev; if (double.IsInfinity(next)) { int exp = 0; current = Math2.Frexp(current, out exp); prev = Math2.Ldexp(prev, -exp); scale += exp; next = fact * current - prev; } prev = current; current = next; } if (scale != 0) { current = Math2.Ldexp(current, scale); prev = Math2.Ldexp(prev, scale); } return(current, prev); }
/// <summary> /// Compute (J, Y) using Steeds method /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needJ"></param> /// <param name="needY"></param> /// <returns></returns> static (double J, DoubleX Y) JY_Steed(double v, double x, bool needJ, bool needY) { Debug.Assert(x > 0 && v >= 0); Debug.Assert(needJ || needY); double J = double.NaN; DoubleX Y = DoubleX.NaN; int n = (int)Math.Floor(v + 0.5); double u = v - n; Debug.Assert(u >= -0.5 && u < 0.5); // Ensure u in [-1/2, 1/2) // compute J{v+1}(x) / J{v}(x) var(fv, s) = J_CF1(v, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(v, x, n, s, fv * s); double ratio = s / Jvmn; // normalization double fu = Jvmnp1 / Jvmn; // prev/current, can also call CF1_jy() to get fu, not much difference in precision //double fuCF; //int sfu; //Bessel.CF1_jy(u,x,fuCF,sfu); //fu = fuCF; var(p, q) = JY_CF2(u, x); // continued fraction JY_CF2 double t = u / x - fu; // t = J'/J double gamma = (p - t) / q; // // We can't allow gamma to cancel out to zero competely as it messes up // the subsequent logic. So pretend that one bit didn't cancel out // and set to a suitably small value. The only test case we've been able to // find for this, is when v = 8.5 and x = 4*PI. // if (gamma == 0) { gamma = u * DoubleLimits.MachineEpsilon / x; } double W = (2 / Math.PI) / x; // Wronskian double Ju = Math.Sign(Jvmn) * Math.Sqrt(W / (q + gamma * (p - t))); double Jv = Ju * ratio; // normalization J = Math2.Ldexp(Jv, -scale); if (needY) { double Yu = gamma * Ju; double Yu1 = Yu * (u / x - p - q / gamma); var(JYvpn, JYvpnm1, YScale) = Recurrence.ForwardJY_B(u + 1, x, n, Yu1, Yu); Y = DoubleX.Ldexp(JYvpnm1, YScale); } return(J, Y); }
/// <summary> /// Computes the Jacobi elliptic functions using the arithmetic-geometric mean /// </summary> /// <param name="k">The modulus</param> /// <param name="u">The argument</param> /// <param name="anm1"></param> /// <param name="bnm1"></param> /// <param name="N"></param> /// <param name="pTn"></param> /// <returns></returns> /// <see href="http://dlmf.nist.gov/22.20#ii"/> private static double Jacobi_Recurse(double k, double u, double anm1, double bnm1, int N, out double pTn) { ++N; double Tn; double cn = (anm1 - bnm1) / 2; double an = (anm1 + bnm1) / 2; if (cn < DoubleLimits.MachineEpsilon) { Tn = Math2.Ldexp(1, N) * u * an; } else { Tn = Jacobi_Recurse(k, u, an, Math.Sqrt(anm1 * bnm1), N, out double unused); } pTn = Tn; return((Tn + Math.Asin((cn / an) * Math.Sin(Tn))) / 2); }
/// <summary> /// Returns the double factorial = n!! /// <para>If n is odd, returns: n * (n-2) * ... * 5 * 3 * 1</para> /// <para>If n is even, returns: n * (n-2) * ... * 6 * 4 * 2</para> /// <para>If n = 0, -1, returns 1</para> /// </summary> /// <param name="n">Requires n ≥ -1</param> public static double Factorial2(int n) { if (n < -1) { Policies.ReportDomainError("Factorial2(n: {0}): Requires n >= -1", n); return(double.NaN); } // common values if (n == -1 || n == 0) { return(1); } double result; if (IsOdd(n)) { // odd n: if (n < FactorialTable.Length) { int k = (n - 1) / 2; return(Math.Ceiling(Math2.Ldexp(FactorialTable[n] / FactorialTable[k], -k) - 0.5)); } // // Fallthrough: n is too large to use table lookup, try the // gamma function instead. // result = Constants.RecipSqrtPI * Math2.Tgamma(0.5 * n + 1.0); result = Math.Ceiling(Math2.Ldexp(result, (n + 1) / 2) - 0.5); } else { // even n: int k = n / 2; result = Math2.Ldexp(Factorial(k), k); } return(result); }
// // Spherical // /// <summary> /// Returns the associated legendre polynomial with angle parameterization P{l,m}( cos(theta) ) /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <param name="theta"></param> /// <returns></returns> static double LegendrePAngle(int n, int m, double theta) { if (n < 0) { n = -(n + 1); // P{n,m}(x) = P{-n-1,m}(x) } if (m == 0) { return(Math2.LegendreP(n, Math.Cos(theta))); } // if ( abs(m) < n ) return 0 by definition; handling int.MinValue too if (m < -n || m > n) { return(0); } // P{n,-m} = (-1)^m * (n-m)!/(n+m)! * P{n,m} if (m < 0) { double legP = LegendrePAngle(n, -m, theta); if (legP == 0) { return(legP); } if (IsOdd(m)) { legP = -legP; } int MaxFactorial = Math2.FactorialTable.Length - 1; if (n - m <= MaxFactorial) { double ratio = Math2.FactorialTable[n + m] / Math2.FactorialTable[n - m]; return(ratio * legP); } if (n - m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue return((legP * (Math2.FactorialTable[n + m] / Math2.FactorialTable[MaxFactorial])) * Math2.TgammaRatio(MaxFactorial + 1, n + 1 - m)); } #if false // since we are setting Lmax = 128, so Mmax = 128, and Lmax+Mmax = 256 // so this should never be called, but left here for completeness // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratio = Math2.TgammaRatio(l + m + 1, l + 1 - m); if (ratio >= DoubleLimits.MinNormalizedValue) { return(ratio * legP); } double value = Math.Exp(Math2.Lgamma(l + m + 1) - Math2.Lgamma(l + 1 - m) + Math.Log(Math.Abs(legP))); return(Math.Sign(legP) * value); #else Policies.ReportNotImplementedError("LegendrePAngle(l: {0}, m: {1}, theta: {2}): Not implemented for |l| >= 128", n, m, theta); return(double.NaN); #endif } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); // use the recurrence relations // P{n, n} = (-1)^n * (2*n - 1)!! * (1 - x^2)^(n/2) // P{n+1, n} = (2*n + 1) * x * P{n, n} // (n-m+1)*P{n+1,m} = (2*n + 1)* x * P{n,m} - (n+m)*P{n-1, m} // see: http://en.wikipedia.org/wiki/Associated_Legendre_polynomials double x = Math.Cos(theta); double sin_theta = Math.Sin(theta); // the following computes (2m-1)!! * sin_theta^m // taking special care not to underflow too soon // double pmm = Math.Pow(sin_theta, m) * Math2.Factorial2(2 * m - 1) int exp = 0; double mant = Math2.Frexp(sin_theta, out exp); double pmmScaled = Math.Pow(mant, m) * Math2.Factorial2(2 * m - 1); double pmm = Math2.Ldexp(pmmScaled, m * exp); if (IsOdd(m)) { pmm = -pmm; } if (n == m) { return(pmm); } // p0 - current // p1 - next double p0 = pmm; double p1 = (2.0 * m + 1.0) * x * p0; //P{m+1,m}(x) for (uint k = (uint)m + 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - (k + (uint)m) * p0) / (k - (uint)m + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Returns the Associated Legendre Polynomial of the first kind /// </summary> /// <param name="n">Polynomial degree. Limited to |<paramref name="n"/>| ≤ 127</param> /// <param name="m">Polynomial order</param> /// <param name="x">Requires |<paramref name="x"/>| ≤ 1</param> public static double LegendreP(int n, int m, double x) { if (!(x >= -1 && x <= 1)) { Policies.ReportDomainError("LegendreP(n: {0}, m: {1}, x: {2}): Requires |x| <= 1", n, m, x); return(double.NaN); } if (n <= -128 || n >= 128) { Policies.ReportNotImplementedError("LegendreP(n: {0}, m: {1}, x: {2}): Not implemented for |n| >= 128", n, m, x); return(double.NaN); } // P{l,m}(x) == P{-l-1,m}(x); watch overflow if (n < 0) { n = -(n + 1); } if (m == 0) { return(Math2.LegendreP(n, x)); } // by definition P{n,m}(x) == 0 when |m| > l if (m < -n || m > n) { return(0); } // P{n,-m} = (-1)^m * (n-m)!/(n+m)! * P{n,m} if (m < 0) { double legP = LegendreP(n, -m, x); if (legP == 0) { return(legP); } if (IsOdd(m)) { legP = -legP; } int MaxFactorial = Math2.FactorialTable.Length - 1; if (n - m <= MaxFactorial) { double ratio = Math2.FactorialTable[n + m] / Math2.FactorialTable[n - m]; return(ratio * legP); } if (n - m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue return((legP * (Math2.FactorialTable[n + m] / Math2.FactorialTable[MaxFactorial])) * Math2.TgammaRatio(MaxFactorial + 1, n + 1 - m)); } #if false // since we are setting Nmax = 128, so Mmax = 128, and Nmax+Mmax = 256 // so this should never be called, but left here for completeness // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratioL = Math2.TgammaRatio(n + m + 1, n + 1 - m); if (ratioL >= DoubleLimits.MinNormalizedValue) { return(ratioL * legP); } double value = Math.Exp(Math2.Lgamma(n + m + 1) - Math2.Lgamma(n + 1 - m) + Math.Log(Math.Abs(legP))); return(Math.Sign(legP) * value); #else Policies.ReportNotImplementedError("LegendreP(n: {0}, m: {1}, x: {2}): Not implemented for |n| >= 128", n, m, x); return(double.NaN); #endif } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); // use the following recurrence relations: // P{n, n} = (-1)^n * (2*n - 1)!! * (1 - x^2)^(n/2) // P{n+1, n} = (2*n + 1) * x * P{n, n} // (n-m+1)*P{n+1,m} = (2*n + 1)* x * P{n,m} - (n+m)*P{n-1, m} // see: http://en.wikipedia.org/wiki/Associated_Legendre_polynomials // the following is: // pmm = Math2.Factorial2(2 * m - 1) * Math.Pow(1.0 - x*x, m/2.0); // being careful not to underflow too soon double pmm = 0; if (Math.Abs(x) > Constants.RecipSqrt2) { double inner = (1 - x) * (1 + x); int exp = 0; double mant = Math2.Frexp(inner, out exp); // careful: use integer division double pmm_scaled = Math.Pow(mant, m / 2) * Math2.Factorial2(2 * m - 1); pmm = Math2.Ldexp(pmm_scaled, (m / 2) * exp); if (IsOdd(m)) { pmm *= Math.Sqrt(inner); } } else { double p_sin_theta = Math.Exp(m / 2.0 * Math2.Log1p(-x * x)); //Math.Pow(1.0 - x*x, m/2.0); pmm = Math2.Factorial2(2 * m - 1) * p_sin_theta; } if (IsOdd(m)) { pmm = -pmm; } if (n == m) { return(pmm); } // p0 - current // p1 - next double p0 = pmm; double p1 = (2.0 * m + 1.0) * x * p0; //P{m+1,m}(x) for (uint k = (uint)m + 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - (k + (uint)m) * p0) / (k - (uint)m + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Simultaneously compute I{v}(x) and K{v}(x) /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needI"></param> /// <param name="needK"></param> /// <returns>A tuple of (I, K). If needI == false or needK == false, I = NaN or K = NaN respectively</returns> public static (double I, double K) IK(double v, double x, bool needI, bool needK) { Debug.Assert(needI || needK, "NeedI and NeedK cannot both be false"); // set initial values for parameters double I = double.NaN; double K = double.NaN; // check v first so that there are no integer throws later if (Math.Abs(v) > int.MaxValue) { Policies.ReportNotImplementedError("BesselIK(v: {0}, x: {1}): Requires |v| <= {2}", v, x, int.MaxValue); return(I, K); } if (v < 0) { v = -v; // for integer orders only, we can use the following identities: // I{-n}(x) = I{n}(v) // K{-n}(x) = K{n}(v) // for any v, use reflection rule: // I{-v}(x) = I{v}(x) + (2/PI) * sin(pi*v)*K{v}(x) // K{-v}(x) = K{v}(x) if (needI && !Math2.IsInteger(v)) { var(IPos, KPos) = IK(v, x, true, true); I = IPos + (2 / Math.PI) * Math2.SinPI(v) * KPos; if (needK) { K = KPos; } return(I, K); } } if (x < 0) { Policies.ReportDomainError("BesselIK(v: {0}, x: {1}): Complex result not supported. Requires x >= 0", v, x); return(I, K); } // both x and v are non-negative from here Debug.Assert(x >= 0 && v >= 0); if (x == 0) { if (needI) { I = (v == 0) ? 1.0 : 0.0; } if (needK) { K = double.PositiveInfinity; } return(I, K); } if (needI && (x < 2 || 3 * (v + 1) > x * x)) { I = I_SmallArg(v, x); needI = false; if (!needK) { return(I, K); } } // Hankel is fast, and reasonably accurate. // at x == 32, it will converge in about 15 iterations if (x >= HankelAsym.IKMinX(v)) { if (needK) { K = HankelAsym.K(v, x); } if (needI) { I = HankelAsym.I(v, x); } return(I, K); } // the uniform expansion is here as a last resort // to limit the number of recurrences, but it is less accurate. if (UniformAsym.IsIKAvailable(v, x)) { if (needK) { K = UniformAsym.K(v, x); } if (needI) { I = UniformAsym.I(v, x); } return(I, K); } // K{v}(x) and K{v+1}(x), binary scaled var(Kv, Kv1, binaryScale) = K_CF(v, x); if (needK) { K = Math2.Ldexp(Kv, binaryScale); } if (needI) { // use the Wronskian relationship // Since CF1 is O(x) for x > v, try to weed out obvious overflows // Note: I{v+1}(x)/I{v}(x) is in [0, 1]. // I{0}(713. ...) == MaxDouble const double I0MaxX = 713; if (x > I0MaxX && x > v) { I = Math2.Ldexp(1.0 / (x * (Kv + Kv1)), -binaryScale); if (double.IsInfinity(I)) { return(I, K); } } double W = 1 / x; double fv = I_CF1(v, x); I = Math2.Ldexp(W / (Kv * fv + Kv1), -binaryScale); } return(I, K); }
/// <summary> /// Returns J{v}(x) for integer v /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double JN(double v, double x) { Debug.Assert(Math2.IsInteger(v)); // For integer orders only, we can use two identities: // #1: J{-n}(x) = (-1)^n * J{n}(x) // #2: J{n}(-x) = (-1)^n * J{n}(x) double sign = 1; if (v < 0) { v = -v; if (Math2.IsOdd(v)) { sign = -sign; } } if (x < 0) { x = -x; if (Math2.IsOdd(v)) { sign = -sign; } } Debug.Assert(v >= 0 && x >= 0); if (v > int.MaxValue) { Policies.ReportNotImplementedError("BesselJ(v: {0}): Large integer values not yet implemented", v); return(double.NaN); } int n = (int)v; // // Special cases: // if (n == 0) { return(sign * J0(x)); } if (n == 1) { return(sign * J1(x)); } // n >= 2 Debug.Assert(n >= 2); if (x == 0) { return(0); } if (x < 5 || (v > x * x / 4)) { return(sign * J_SmallArg(v, x)); } // if v > MinAsymptoticV, use the asymptotics const int MinAsymptoticV = 7; // arbitrary constant to reduce iterations if (v > MinAsymptoticV) { double Jv; DoubleX Yv; if (JY_TryAsymptotics(v, x, out Jv, out Yv, true, false)) { return(sign * Jv); } } // v < abs(x), forward recurrence stable and usable // v >= abs(x), forward recurrence unstable, use Miller's algorithm if (v < x) { // use forward recurrence double prev = J0(x); double current = J1(x); return(sign * Recurrence.ForwardJY(1.0, x, n - 1, current, prev).JYvpn); } else { // Steed is somewhat more accurate when n gets large if (v >= 200) { var(J, Y) = JY_Steed(n, x, true, false); return(J); } // J{n+1}(x) / J{n}(x) var(fv, s) = J_CF1(n, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(n, x, n, s, fv * s); // scale the result double Jv = (J0(x) / Jvmn); Jv = Math2.Ldexp(Jv, -scale); return((s * sign) * Jv); // normalization } }
/// <summary> /// Returns K{n}(x) for integer order /// </summary> /// <param name="n"></param> /// <param name="x"></param> /// <returns></returns> public static double KN(int n, double x) { if (x < 0) { Policies.ReportDomainError("BesselK(v: {0}, x: {1}): Requires x >= 0 for real result", n, x); return(double.NaN); } if (x == 0) { return(double.PositiveInfinity); } // even function // K{-n}(z) = K{n}(z) if (n < 0) { n = -n; } if (n == 0) { return(K0(x)); } if (n == 1) { return(K1(x)); } double v = n; // Hankel is fast, and reasonably accurate, saving us from many recurrences. if (x >= HankelAsym.IKMinX(v)) { return(HankelAsym.K(v, x)); } // the uniform expansion is here as a last resort // to limit the number of recurrences, but it is less accurate. if (UniformAsym.IsIKAvailable(v, x)) { return(UniformAsym.K(v, x)); } // Since K{v}(x) has a (e^-x)/sqrt(x) multiplier // using recurrence can underflow too quickly for large x, // so, use a scaled version double result; if (x > 1) { double prev = K0(x, true); double current = K1(x, true); // for large v and x this number can get very large // maximum observed K(1000,10) = 2^6211 var(Kv, _, binaryScale) = Recurrence.ForwardK_B(1, x, n - 1, current, prev); // Compute: value * 2^(binaryScale) * e^-x if (x < -DoubleX.MinLogValue) { DoubleX exs = DoubleX.Ldexp(DoubleX.Exp(-x), binaryScale); result = Math2.Ldexp(Kv * exs.Mantissa, exs.Exponent); } else { result = Math.Exp(-x + Math.Log(Kv) + binaryScale * Constants.Ln2); } } else { double prev = K0(x); double current = K1(x); result = Recurrence.ForwardK(1, x, n - 1, current, prev).Kvpn; } return(result); }
/// <summary> /// Computes J{v}(x), Y{v}(x) using a combination of asymptotic approximation and recurrence /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needJ"></param> /// <param name="needY"></param> /// <returns></returns> static (double J, DoubleX Y) JY_AsymRecurrence(double v, double x, bool needJ, bool needY) { Debug.Assert(v >= 0 && x >= 1, "Requires positive values for v,x"); Debug.Assert(v < int.MaxValue, "v too large: v = " + v); var J = double.NaN; var Y = DoubleX.NaN; int nPos = (int)Math.Floor(v); double uPos = v - nPos; // Using Hankel, find: // J{v-Floor(v)}(x) and J{v-Floor(v)+1}(x) // Y{v-Floor(v)}(x) and Y{v-Floor(v)+1}(x) // then use recurrence to find J{v}(x), Y{v}(x) double u0, u1;; int n; if (x >= 9) { // set the start of the recurrence near sqrt(x) double maxV = Math.Floor(Math.Sqrt(x)); u1 = (maxV - 1) + uPos; u0 = u1 - 1; n = (int)Math.Floor(v - u1 + 0.5); Debug.Assert(n >= 0); } else { u0 = uPos; u1 = uPos + 1; n = nPos - 1; } Debug.Assert(x >= HankelAsym.JYMinX(u1), "x is too small for HankelAsym"); var(Ju, Yu) = HankelAsym.JY(u0, x); var(Jup1, Yup1) = HankelAsym.JY(u1, x); if (needJ) { if (v < x) { J = Recurrence.ForwardJY(u1, x, n, Jup1, Ju).JYvpn; } else { // Use fv = J{v+1}(x) / J{v}(x) // and backward recurrence to find (J{v-n+1}/J{v-n}) var(fv, s) = J_CF1(v, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(v, x, n, s, fv * s); var Jv = Math2.Ldexp(Jup1 / Jvmn, -scale); J = s * Jv; // normalization } } if (needY) { var(Yv, Yvm1, YScale) = Recurrence.ForwardJY_B(u1, x, n, Yup1, Yu); Y = DoubleX.Ldexp(Yv, YScale); } return(J, Y); }