/// <summary> /// Returns the complete elliptic integral of the second kind D(k) = D(π/2,k) /// <para>D(k) = ∫ sin^2(θ)/sqrt(1-k^2*sin^2(θ)) dθ, θ={0,π/2}</para> /// </summary> /// <param name="k">The modulus. Requires |k| ≤ 1</param> public static double EllintD(double k) { if (!(k >= -1 && k <= 1)) { Policies.ReportDomainError("EllintD(k: {0}): Requires |k| <= 1", k); return(double.NaN); } // special values if (k == 0) { return(Math.PI / 4); } if (Math.Abs(k) == 1) { return(Double.PositiveInfinity); } // http://dlmf.nist.gov/19.5#E3 if (k * k < DoubleLimits.RootMachineEpsilon._13) { return((Math.PI / 4) * HypergeometricSeries.Sum2F1(1.5, 0.5, 2, k * k)); } double x = 0; double y = 1 - k * k; double z = 1; double value = Math2.EllintRD(x, y, z) / 3; return(value); }
/// <summary> /// Returns the Spherical Bessel function of the first kind: j<sub>n</sub>(x) /// </summary> /// <param name="n">Order. Requires n >= 0</param> /// <param name="x">Argument. Requires x >= 0</param> /// <returns></returns> public static double SphBesselJ(int n, double x) { if (!(n >= 0) || !(x >= 0)) { Policies.ReportDomainError("SphBesselJ(n: {0}, x: {1}): Requires n >= 0, x >= 0", n, x); return(double.NaN); } if (double.IsInfinity(x)) { return(0); } // // Special case, n == 0 resolves down to the sinus cardinal of x: // if (n == 0) { return(Math2.Sinc(x)); } if (x < 1) { // the straightforward evaluation below can underflow too quickly when x is small double result = (0.5 * Constants.SqrtPI); result *= (Math.Pow(x / 2, n) / Math2.Tgamma(n + 1.5)); if (result != 0) { result *= HypergeometricSeries.Sum0F1(n + 1.5, -x * x / 4); } return(result); } // Default case is just a straightforward evaluation of the definition: return(Constants.SqrtHalfPI * BesselJ(n + 0.5, x) / Math.Sqrt(x)); }
/// <summary> /// Returns Log(1 + x) with improved accuracy for |x| ≤ 0.5 /// </summary> /// <param name="x">The function argument. Requires x ≥ -1</param> public static double Log1p(double x) { if (!(x >= -1)) { Policies.ReportDomainError("Log1p(x: {0}): Requires x >= -1", x); return(double.NaN); } if (x == -1) { return(double.NegativeInfinity); } if (double.IsInfinity(x)) { return(double.PositiveInfinity); } double u = 1 + x; if (u == 1.0) { return(x); } return(Math.Log(u) * (x / (u - 1.0))); }
// In the implementation of these routines, use // parameters a1 = a1 + n instead of a1++; b1 = b1+n instead of b1++, etc // so the routines have a better chance of working when any a_n or b_n > 1/ϵ /// <summary> /// Returns the sum of a hypergeometric series 0F0. /// <para>Sum0F0 = addend + Σ( (z^n/n!)) n={0,∞} = e^z</para> /// </summary> /// <param name="z">The argument</param> /// <param name="addend">The addend to the series</param> /// <returns> /// The sum of the series, or NaN if the series does not converge /// within the policy maximum number of iterations. /// </returns> public static double Sum0F0(double z, double addend = 0) { if (double.IsNaN(z) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum0F0(z: {0}, addend: {1}): NaN not allowed", z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (z / (n + 1)); sum += term; #if CMP_FLOAT if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) return sum; #else if (sum == prevSum) return sum; #endif } Policies.ReportConvergenceError("Sum0F0(z: {0}, addend: {1}): No convergence after {2} iterations", z, addend, n); return double.NaN; }
/// <summary> /// Returns the Cylindrical Bessel J function: J<sub>v</sub>(x) /// </summary> /// <param name="v">Order</param> /// <param name="x">Argument</param> /// <returns></returns> public static double BesselJ(double v, double x) { if (double.IsNaN(x) || double.IsNaN(v)) { Policies.ReportDomainError("BesselJ(v: {0}, x: {1}): Requires finite arguments", v, x); return double.NaN; } if (x < 0 && !IsInteger(v)) { Policies.ReportDomainError("BesselJ(v: {0}, x: {1}): Requires integer v when x < 0", v, x); return double.NaN; } if (x == 0 && v < 0) { Policies.ReportDomainError("BesselJ(v: {0}, x: {1}): Complex results not supported. Requires v >= 0 when x = 0", v, x); return double.NaN; } if (double.IsInfinity(x)) return 0; if (x == 0) { Debug.Assert(v >= 0); if (v == 0) return 1; return 0; } if (IsInteger(v)) return _Bessel.JN(v, x); Debug.Assert(x > 0); return _Bessel.JY(v, x, true, false).J; }
/// <summary> /// Returns the fraction where <paramref name="x" /> == fraction * 2 ^ exponent. /// <para>Note: |fraction| is in [0.5,1.0) or is 0</para> /// <para>if x is NaN or ±∞ then x is returned and the exponent is unspecified</para> /// </summary> /// <param name="x">Argument</param> /// <param name="exponent">Output the exponent value</param> /// <returns>System.Double.</returns> public static double Frexp(double x, out int exponent) { exponent = 0; // Check for +/- Inf or NaN if (double.IsNaN(x)) { Policies.ReportDomainError("Frexp(x: {0},...): NaNs not allowed", x); return x; } if (double.IsInfinity(x) || x == 0 ) return x; // +/-0, +/-Inf IEEEDouble bits = new IEEEDouble(x); if (bits.IsSubnormal) { // Multiply by 2^52 to normalize the number const double normalMult = (1L << IEEEDouble.MantissaBits); bits = new IEEEDouble(x * normalMult); exponent = -IEEEDouble.MantissaBits; Debug.Assert(!bits.IsSubnormal); } // x is normal from here on // must return x in [0.5,1.0) = [2^-1,2^0) // so: x_exponent must = -1 while keeping the mantissa and sign the same exponent += bits.ExponentValue + 1; // 2^0 = 1 = 0.5 * 2^1; so increment exponent // replace the exponent with 2^-1 so that x is in [0.5,1.0) const Int64 ExpHalf = 0x3fe0000000000000; return BitConverter.Int64BitsToDouble( (bits & ~IEEEDouble.ExponentMask)| ExpHalf ); }
/// <summary> /// Returns the Cylindrical Bessel K function: K<sub>v</sub>(x) /// </summary> /// <param name="v">Order</param> /// <param name="x">Argument</param> /// <returns></returns> public static double BesselK(double v, double x) { if (!(x >= 0)) { Policies.ReportDomainError("BesselK(v: {0}, x: {1}): Requires x >= 0", v, x); return(double.NaN); } if (x == 0) { if (v == 0) { return(double.PositiveInfinity); } Policies.ReportDomainError("BesselK(v: {0}, x: {1}): Requires x > 0 when v != 0", v, x); return(double.NaN); } // K{-v} = K{v} // negative orders are handled in each of the following routines if (IsInteger(v) && (v > int.MinValue && v <= int.MaxValue)) { return(_Bessel.KN((int)v, x)); } double kv = _Bessel.IK(v, x, false, true).K; return(kv); }
/// <summary> /// Returns Cos(π * x) /// </summary> /// <param name="x">Argument</param> public static double CosPI(double x) { if (double.IsNaN(x) || double.IsInfinity(x)) { Policies.ReportDomainError("CosPI(x: {0}): Requires finite x", x); return(double.NaN); } // cos(-x) == cos(x) if (x < 0) { x = -x; } // Reduce angles over 2*π if (x >= 2) { x -= 2 * Math.Floor(0.5 * x); } Debug.Assert(x >= 0 && x < 2); // reflect so that x in [0,1]*π // cos(x) = cos(2π-x) if (x >= 1.0) { x = 2.0 - x; } // -cos(x) = cos(π-x) bool neg = false; if (x > 0.5) { x = 1 - x; neg = true; } if (x == 0.5) { return(0); } // using the identity: cos(x) = sin(π/2-x) // when x > 0.25 significantly improves the precision of this routine in .NET double result; if (x > 0.25) { result = Math.Sin((0.5 - x) * Math.PI); } else { result = Math.Cos(Math.PI * x); } return((neg) ? -result : result); }
/// <summary> /// Returns ∂P(a,x)/∂x = e^(-x) * x^(a-1) / Γ(a) /// <para>Note: ∂Q(a,x)/∂x = -∂P(a,x)/∂x</para> /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="x">Requires x ≥ 0</param> public static double GammaPDerivative(double a, double x) { if ((!(a > 0) || double.IsInfinity(a)) || (!(x >= 0))) { Policies.ReportDomainError("GammaPDerivative(a: {0}, x: {1}): Requires finite a > 0; x >= 0", a, x); return(double.NaN); } // // Now special cases: // if (x == 0) { if (a > 1) { return(0); } if (a == 1) { return(1); } return(double.PositiveInfinity); } // lim {x->+inf} e^(-x + (a-1)*ln(x)) / Γ(a) = 0/Γ(a) = 0 if (double.IsInfinity(x)) { return(0); } // when x is small, we can underflow too quickly // For x < eps, e^-x ~= 1, so use // x^(a-1)/Γ(a) // and for eps < x < 1, use: // IgammaPrefixRegularized(a, x) = (x/(a-1)) * IgammaPrefixRegularized(a-1, x) if (x <= DoubleLimits.MachineEpsilon) { if (a < DoubleLimits.MachineEpsilon / Constants.EulerMascheroni) { return((a / x) * Math.Pow(x, a)); } if (a < 1) { return(Math.Pow(x, a) / (x * Math2.Tgamma(a))); } return(Math.Pow(x, a - 1) / Math2.Tgamma(a)); } if (x < 1 && a >= 2) { return(_Igamma.PrefixRegularized(a - 1, x) / (a - 1)); } // Normal case double result = _Igamma.PrefixRegularized(a, x) / x; return(result); }
/// <summary> /// Returns the value "a" such that q == GammaQ(a, x); /// </summary> /// <param name="x">Requires x ≥ 0</param> /// <param name="q">Requires 0 ≤ q ≤ 1</param> public static double GammaQInvA(double x, double q) { if ((!(x >= 0) || double.IsInfinity(x)) || (!(q >= 0 && q <= 1))) { Policies.ReportDomainError("GammaPInvA(x: {0}, q: {1}): Requires finite x >= 0; q in [0,1]", x, q); return(double.NaN); } if (q == 0) { return(DoubleLimits.MinNormalValue); } if (q == 1) { return(double.MaxValue); } var p = 1 - q; if (q <= 0.5) { return(_GammaInvA.ImpQ(x, p, q)); } return(_GammaInvA.ImpP(x, p, q)); }
/// <summary> /// Returns the value "a" such that: p == GammaP(a, x); /// </summary> /// <param name="x">Requires x ≥ 0</param> /// <param name="p">Requires 0 ≤ p ≤ 1</param> public static double GammaPInvA(double x, double p) { if ((!(x >= 0) || double.IsInfinity(x)) || (!(p >= 0 && p <= 1))) { Policies.ReportDomainError("GammaPInvA(x: {0}, p: {1}): Requires finite x >= 0; p in [0,1]", x, p); return(double.NaN); } if (p == 0) { return(double.MaxValue); } if (p == 1) { return(DoubleLimits.MinNormalValue); } if (p < 0.5) { return(_GammaInvA.ImpP(x, p, 1 - p)); } return(_GammaInvA.ImpQ(x, p, 1 - p)); }
/// <summary> /// Returns Floor(Log(2,|<paramref name="x"/>|)). /// Note: this is the exponent field of a double precision number. /// <para>If x is NaN then x is returned</para> /// <para>If x == ±∞ then +∞ is returned</para> /// <para>If x == ±0 then -∞ is returned</para> /// </summary> /// <param name="x">Argument</param> public static double Logb(double x) { IEEEDouble rep = new IEEEDouble(x); // Check for +/- Inf or NaN if (!rep.IsFinite) { if ( rep.IsInfinity ) return double.PositiveInfinity; Policies.ReportDomainError("Logb(x: {0}): NaNs not allowed", x); return x; } if ( x == 0 ) { Policies.ReportPoleError("Logb(x: {0}): Logb(0) == -∞", x); return double.NegativeInfinity; } int exponent = 0; if (rep.IsSubnormal) { // Multiply by 2^53 to normalize the number const double normalMult = (1L << IEEEDouble.MantissaBits); rep = new IEEEDouble(x * normalMult); exponent = -IEEEDouble.MantissaBits; Debug.Assert(!rep.IsSubnormal); } exponent += rep.ExponentValue; return exponent; }
/// <summary> /// Returns the sum of a hypergeometric series 3F0 with one numerator = 1. /// <para>Sum3F0_A1 = addend + Σ(( (a1)_n * (a2)_n ) * (z^n)) n={0,∞}</para> /// </summary> /// <param name="a1">First numerator</param> /// <param name="a2">Second numerator</param> /// <param name="z">Argument</param> /// <param name="addend">The addend to the series</param> /// <returns> /// The sum of the series, or NaN if the series does not converge /// within the policy maximum number of iterations /// </returns> private static double Sum3F0_A1(double a1, double a2, double z, double addend) { if ((double.IsNaN(a1) || double.IsInfinity(a1)) || (double.IsNaN(a2) || double.IsInfinity(a2)) || (double.IsNaN(z) || double.IsInfinity(z)) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum3F1(a1: {0}, a2: {1}, z: {2}, addend: {3}): Requires finite arguments", a1, a2, z, addend); return double.NaN; } double sum = 1.0 + addend; double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= z * (a1 + n) * (a2 + n); sum += term; #if CMP_FLOAT if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) return sum; #else if (sum == prevSum) return sum; #endif } Policies.ReportConvergenceError("Sum3F1(a1: {0}, a2: {1}, z: {2}, addend: {3}): No convergence after {4} iterations", a1, a2, z, addend, n); return double.NaN; }
/// <summary> /// Returns the value of the Riemann Zeta function at <paramref name="x"/> /// <para>ζ(x) = Σ n^-x, n={1, ∞}</para> /// </summary> /// <param name="x">The function argument</param> public static double Zeta(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Zeta(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x > 0) { return(1); } Policies.ReportDomainError("Zeta(x: {0}): -Infinity not allowed", x); return(double.NaN); } if (x == 1) { Policies.ReportPoleError("Zeta(x: {0}): Evaluation at pole", x); return(double.NaN); } return(_Zeta.Imp(x, 1 - x)); }
/// <summary> /// Returns the Spherical Bessel function of the second kind: y<sub>n</sub>(x) /// </summary> /// <param name="v">Order. Requires n >= 0</param> /// <param name="x">Argument. Requires x >= 0</param> /// <returns></returns> public static double SphBesselY(int v, double x) { const double LowerLimit = 2 * DoubleLimits.MinNormalValue; if (!(v >= 0) || !(x >= 0)) { Policies.ReportDomainError("SphBesselY(v: {0}, x: {1}): Requires v >= 0, x >= 0", v, x); return(double.NaN); } if (x < LowerLimit) { return(double.NegativeInfinity); } if (double.IsInfinity(x)) { return(0); } // The code below is the following // double result = (Constants.SqrtHalfPI/Math.Sqrt(x)) * Math2.CylBesselY(v+0.5, x); // but for x << v, spherical Bessel y can overflow too soon. var(J, Y) = _Bessel.JY(v + 0.5, x, false, true); double prefix = (Constants.SqrtHalfPI / Math.Sqrt(x)); double result = (double)(prefix * Y); return(result); }
/// <summary> /// Returns "a" such that: I<sub>x</sub>(a, b) == p /// </summary> /// <param name="b">Requires b > 0</param> /// <param name="x">Requires 0 ≤ x ≤ 1</param> /// <param name="p">Requires 0 ≤ p ≤ 1</param> public static double IbetaInvA(double b, double x, double p) { if ((!(b > 0) || double.IsInfinity(b)) || (!(x >= 0 && x <= 1)) || (!(p >= 0 && p <= 1))) { Policies.ReportDomainError("IbetaInvA(b: {0}, x: {1}, p: {2}): Requires finite b > 0; x,p in [0,1]", b, x, p); return(double.NaN); } if (p == 1) { return(0); } if (p == 0) { return(double.PositiveInfinity); } // use I_x(a,b) = 1 - I_{1-x}(b, a) if (p > 0.5 && x > 0.5) { return(_IbetaInvAB.IbetaInvBImp(b, 1 - x, x, 1 - p, p)); } return(_IbetaInvAB.IbetaInvAImp(b, x, 1 - x, p, 1 - p)); }
/// <summary> /// Returns "b" such that: 1 - I<sub>x</sub>(a, b) == q /// </summary> /// <param name="a">Requires a ≥ 0</param> /// <param name="x">Requires 0 ≤ x ≤ 1</param> /// <param name="q">Requires 0 ≤ q ≤ 1</param> public static double IbetacInvB(double a, double x, double q) { if ((!(a > 0) || double.IsInfinity(a)) || (!(x >= 0 && x <= 1)) || (!(q >= 0 && q <= 1))) { Policies.ReportDomainError("IbetaInvB(a: {0}, x: {1}, q: {2}): Requires finite a > 0; x,q in [0,1]", a, x, q); return(double.NaN); } if (q == 0) { return(double.PositiveInfinity); } if (q == 1) { return(0); } // use I_x(a,b) = 1 - I_{1-x}(b, a) if (q > 0.5 && x > 0.5) { return(_IbetaInvAB.IbetaInvAImp(a, 1 - x, x, q, 1 - q)); } return(_IbetaInvAB.IbetaInvBImp(a, x, 1 - x, 1 - q, q)); }
/// <summary> /// Evaluates the first n terms of polynomial with a given argument /// <para>Σ( c[k] * x^k ) k={0, nTerms-1}</para> /// </summary> /// <param name="c">The coefficient array</param> /// <param name="x">The polynomial argument</param> /// <param name="nTerms">The maximum number of terms to evaluate</param> public static double Eval(double[] c, double x, int nTerms) { // Uses Horner's Method // see http://en.wikipedia.org/wiki/Horner_scheme if (c == null) { Policies.ReportDomainError("Requires Coefficients (c) != null"); return(double.NaN); } if (nTerms < 0 || nTerms > c.Length) { Policies.ReportDomainError("Requires nTerms in [0, {0}]; nTerms = {1}", c.Length - 1, nTerms); return(double.NaN); } if (nTerms == 0) { return(0); } int n = nTerms - 1; double result = c[n]; for (int i = n - 1; i >= 0; i--) { result = result * x + c[i]; } return(result); }
/// <summary> /// Returns <paramref name="x"/> * 2^n /// </summary> /// <param name="x">Argument</param> /// <param name="n">The binary exponent (2^n)</param> /// <returns> /// Returns <paramref name="x"/> * 2^n /// <para>If <paramref name="x"/> is NaN, returns NaN.</para> /// <para>If <paramref name="x"/> is ±0 or ±∞, returns x.</para> /// <para>If n is 0, returns x.</para> /// </returns> public static double Ldexp(double x, int n) { const double toNormal = (1L << IEEEDouble.MantissaBits); const double fromNormal = 1.0/(1L << IEEEDouble.MantissaBits); // handle special cases if (double.IsNaN(x)) { Policies.ReportDomainError("Ldexp(x: {0}, n: {1}): Requires x not NaN", x, n); return x; } if (double.IsInfinity(x)) return x; // +/-Inf if (x == 0 || n == 0) return x; // +/-0, x IEEEDouble bits = new IEEEDouble(x); int xExp = 0; if ( bits.IsSubnormal ) { bits = new IEEEDouble(x * toNormal); xExp -= IEEEDouble.MantissaBits; } xExp += bits.ExponentValue + n; // there will be an overflow if (xExp > IEEEDouble.MaxBinaryExponent) return (x > 0) ? double.PositiveInfinity : double.NegativeInfinity; // cannot represent with a denorm -- underflow if (xExp < IEEEDouble.MinBinaryDenormExponent) return (x > 0) ? 0.0 : -0.0; if (xExp >= IEEEDouble.MinBinaryExponent) { // in the normal range - just set the exponent to the sum bits.ExponentValue = xExp; return bits; } // We have a denormalized number #if true // Use C99 definition x*2^exp xExp += IEEEDouble.MantissaBits; Debug.Assert(xExp >= IEEEDouble.MinBinaryExponent); bits.ExponentValue = xExp; return ((double)bits) * fromNormal; #else // Make it compatible with MS C library - no rounding for denorms // kept for future reference int shift = IEEEDouble.MinBinaryExponent - xExp; Debug.Assert(shift > 0); // Recover the hidden mantissa bit and shift const Int64 impliedbit = 1L << IEEEDouble.MantissaBits; // 1.MANTISSA = 1 << 52 Int64 mantissa = (bits.Significand | impliedbit) >> shift; return BitConverter.Int64BitsToDouble(bits.Sign | mantissa); #endif }
/// <summary> /// Returns the sum of a series with each term given by the function f(). /// </summary> /// <param name="f">Delegate returning the next term</param> /// <param name="initialValue">initial value</param> /// <param name="tolerance">Stops when the relative tolerance is reached</param> /// <param name="maxIterations">The maximum number of terms</param> /// <returns> /// The sum of the series or NaN if the series does not converge within the maximum /// number of iterations specified in the policy. /// </returns> public static double Eval(Func <double> f, double initialValue, double tolerance, uint maxIterations) { if (f == null) { Policies.ReportDomainError("Requires f != null"); return(double.NaN); } if (!(tolerance >= 0)) { Policies.ReportDomainError("Requires relative tolerance >= 0"); return(double.NaN); } double sum = initialValue; for (UInt32 i = 0; i < maxIterations; i++) { double prevSum = sum; double delta = f(); sum += delta; if (Math.Abs(delta) <= Math.Abs(prevSum) * tolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge in {0} iterations", maxIterations); return(double.NaN); }
/// <summary> /// Returns the value of the Airy Ai function at <paramref name="x"/> /// </summary> /// <param name="x">The function argument</param> /// <returns></returns> public static double AiryAi(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("AiryAi(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { return(0); } double absX = Math.Abs(x); if (x >= -3 && x <= 2) { // Use small series. See: // http://functions.wolfram.com/Bessel-TypeFunctions/AiryAi/06/01/02/01/0004/ // http://dlmf.nist.gov/9.4 double z = x * x * x / 9; double s1 = Constants.AiryAi0 * HypergeometricSeries.Sum0F1(2 / 3.0, z); double s2 = x * Constants.AiryAiPrime0 * HypergeometricSeries.Sum0F1(4 / 3.0, z); return(s1 + s2); } const double v = 1.0 / 3; double zeta = 2 * absX * Math.Sqrt(absX) / 3; if (x < 0) { if (x < -32) { return(AiryAsym.AiBiNeg(x).Ai); } // The following is //double j1 = Math2.BesselJ(v, zeta); //double j2 = Math2.BesselJ(-v, zeta); //double ai = Math.Sqrt(-x) * (j1 + j2) / 3; var(J, Y) = _Bessel.JY(v, zeta, true, true); double s = 0.5 * (J - ((double)Y) / Constants.Sqrt3); double ai = Math.Sqrt(-x) * s; return(ai); } else { // Use the K relationship: http://dlmf.nist.gov/9.6 if (x >= 16) { return(AiryAsym.Ai(x)); } double ai = Math2.BesselK(v, zeta) * Math.Sqrt(x / 3) / Math.PI; return(ai); } }
/// <summary> /// Returns the next representable value which is greater than x. /// </summary> /// <param name="x">FloatNext argument</param> /// <returns>The next floating point value /// <para>If x == NaN || x == Infinity, returns NaN</para> /// <para>If x == MaxValue, returns PositiveInfinity</para></returns> static public double FloatNext(double x) { if (double.IsNaN(x) || double.IsInfinity(x)) { Policies.ReportDomainError("FloatNext(x: {0}): Requires finite x", x); return(double.NaN); } if (x >= double.MaxValue) { return(double.PositiveInfinity); } if (x == 0) { return(double.Epsilon); } Int64 bits = BitConverter.DoubleToInt64Bits(x); // check if we should increase or decrease the mantissa if (x >= 0) { bits++; } else { bits--; } return(BitConverter.Int64BitsToDouble(bits)); }
/// <summary> /// Evaluates a finite continued fraction /// <para>Return = initialValue + a[0]/(b[0]+(a[1]/b[1]+...(b[n-1]+a[n]/b[n])))</para> /// </summary> /// <param name="initialValue">The initial value is added to the finite fraction</param> /// <param name="a">The array of numerators</param> /// <param name="b">The array of denominators</param> /// <returns></returns> public static double Eval(double initialValue, double[] a, double[] b) { if (double.IsNaN(initialValue)) { Policies.ReportDomainError("Requires finite initialValue: initialValue = {0}", initialValue); return(double.NaN); } if (a == null) { Policies.ReportDomainError("Requires a != null"); return(double.NaN); } if (b == null) { Policies.ReportDomainError("Requires b != null"); return(double.NaN); } if (a.Length != b.Length) { Policies.ReportDomainError("Requires equal sized arrays: a.Length = {0}; b.Length = {1}", a.Length, b.Length); return(double.NaN); } IEnumerable <(double, double)> series = a.Zip(b, (x, y) => (x, y)); return(Eval(initialValue, series, _DefaultTolerance, a.Length)); }
/// <summary> /// Returns the partial derivative of IBeta(a,b,x) with respect to <paramref name="x"/> /// <para>IbetaDerivative(a, b, x) = ∂( I<sub>x</sub>(a,b) )/∂x = (1-x)^(b-1)*x^(a-1)/B(a,b)</para> /// </summary> /// <param name="a">Requires a ≥ 0 and not both a and b are zero</param> /// <param name="b">Requires b ≥ 0 and not both a and b are zero</param> /// <param name="x">Defined for 0 ≤ x ≤ 1</param> public static double IbetaDerivative(double a, double b, double x) { if (!(a > 0) || !(b > 0) || !(x >= 0 && x <= 1)) { Policies.ReportDomainError("IbetaDerivative(a: {0}, b: {1}, x: {2}): Requires a,b >= 0; x in [0,1]", a, b, x); return(double.NaN); } // // Now the corner cases: // if (x == 0) { if (a > 1) { return(0); } if (a == 1) { return(1 / Math2.Beta(a, b)); } return(double.PositiveInfinity); } if (x == 1) { if (b > 1) { return(0); } if (b == 1) { return(1 / Math2.Beta(a, b)); } return(double.PositiveInfinity); } // handle denorm x if (x < DoubleLimits.MinNormalValue) { double logValue = a * Math.Log(x) + b * Math2.Log1p(-x) - Math.Log(x * (1 - x)); logValue -= Math2.LogBeta(a, b); double result = Math.Exp(logValue); #if EXTRA_DEBUG Debug.WriteLine("IbetaDerivative(a: {0}, b: {1}, x: {2}): Denorm = {3}", a, b, x, result); #endif return(result); } // // Now the regular cases: // double mult = (1 - x) * x; double f1 = _Ibeta.PowerTerms(a, b, x, 1 - x, true, 1 / mult); return(f1); }
// Carlson's degenerate elliptic integral // Carlson, Numerische Mathematik, vol 33, 1 (1979) /// <summary> /// Carlson's symmetric form of elliptical integrals /// <para>R<sub>C</sub>(x,y) = R<sub>F</sub>(x,y,y) = (1/2) * ∫ dt/((t+y)*Sqrt(t+x)), t={0,∞}</para> /// </summary> public static double EllintRC(double x, double y) { if ((!(x >= 0) || double.IsInfinity(x)) || (y == 0 || double.IsInfinity(y) || double.IsNaN(y))) { Policies.ReportDomainError("EllintRC(x: {0}, y: {1}): Requires finite x >= 0; finite y != 0", x, y); return(double.NaN); } // Taylor series: Max Value const double tolerance = 0.003100392679625389599124425076703727071077135406054400409781; Debug.Assert(Math2.AreNearUlps(tolerance, Math.Pow(4 * DoubleLimits.MachineEpsilon, 1.0 / 6), 5), "Incorrect constant"); // for y < 0, the integral is singular, return Cauchy principal value double prefix = 1; if (y < 0) { prefix = Math.Sqrt(x / (x - y)); x -= y; y = -y; } if (x == 0) { return(prefix * ((Math.PI / 2) / Math.Sqrt(y))); } if (x == y) { return(prefix / Math.Sqrt(x)); } int k = 1; for (; k < Policies.MaxSeriesIterations; k++) { double u = (x + y + y) / 3; double S = y / u - 1; // 1 - x / u = 2 * S if (2 * Math.Abs(S) < tolerance) { // Taylor series expansion to the 5th order double value = (1 + S * S * (3.0 / 10 + S * (1.0 / 7 + S * (3.0 / 8 + S * (9.0 / 22))))) / Math.Sqrt(u); return(value * prefix); } double sx = Math.Sqrt(x); double sy = Math.Sqrt(y); double lambda = 2 * sx * sy + y; x = (x + lambda) / 4; y = (y + lambda) / 4; } Policies.ReportDomainError("EllintRC(x: {0}, y: {1}): No convergence after {2} iterations", x, y, k); return(double.NaN); }
/// <summary> /// Returns the Jacobi Zeta function /// Z(k, φ) = E(k, φ) - E(k)/K(k)*F(k, φ) /// </summary> /// <param name="k">The modulus. Requires |k| ≤ 1</param> /// <param name="phi">The amplitude</param> /// <returns></returns> public static double JacobiZeta(double k, double phi) { if ((!(k >= -1 && k <= 1)) || double.IsNaN(phi) || double.IsInfinity(phi)) { Policies.ReportDomainError("JacobiZeta(k: {0}, phi: {1}): Requires |k| <= 1; finite phi", k, phi); return(double.NaN); } if (phi == 0) { return(0); } double sign = 1; if (phi < 0) { phi = Math.Abs(phi); sign = -1; } double k2 = k * k; if (k2 < 8 * DoubleLimits.MachineEpsilon) { // See: http://functions.wolfram.com/EllipticIntegrals/JacobiZeta/06/01/08/0001/ if (k2 == 0) { return(0); } return(sign * Math.Sin(2 * phi) * k2 / 4); } double sinp = Math.Sin(phi); double cosp = Math.Cos(phi); if (k == 1) { // We get here by simplifying JacobiZeta[w, 1] in Mathematica, and the fact that 0 <= phi. return(sign * sinp * Math.Sign(cosp)); } double x = 0; double y = 1 - k2; double z = 1; double p = 1 - k2 * sinp * sinp; if (p < 0.125) // second form is more accurate for small p { p = (1 - k2) + k2 * cosp * cosp; } double result = sign * k2 * sinp * cosp * Math.Sqrt(p) * EllintRJ(x, y, z, p) / (3 * EllintK(k)); return(result); }
/// <summary> /// Returns the value "x" such that q == GammaQ(a, x); /// </summary> /// <param name="a">Requires a > 0</param> /// <param name="q">Requires 0 ≤ q ≤ 1</param> public static double GammaQInv(double a, double q) { if ((!(a > 0) || double.IsInfinity(a)) || (!(q >= 0 && q <= 1))) { Policies.ReportDomainError("GammaQInv(a: {0}, q: {1}): Requires finite a > 0; q in [0,1]", a, q); return(double.NaN); } if (q == 0) { return(double.PositiveInfinity); } if (q == 1) { return(0); } // Q(1,x) = e^-x if (a == 1) { return(-Math.Log(q)); } // Q(1/2, x) = erfc(sqrt(x)) if (a == 0.5) { return(Squared(Math2.ErfcInv(q))); } double guess = _IgammaInv.GammaPInvGuess(a, 1 - q, q, out bool has10Digits); var(lower, upper) = _IgammaInv.GammaQInvLimits(a, q); // there can be some numerical uncertainties in the limits, // particularly for denormalized values, so adjust the limits if (upper == 0 && lower == 0) { return(0.0); } if (guess <= DoubleLimits.MinNormalValue) { return(guess); } if (guess <= lower) { lower = DoubleLimits.MinNormalValue; } if (guess > upper) { upper = guess; } return(_IgammaInv.SolveGivenAQ(a, q, guess, lower, upper)); }
/// <summary> /// Returns Sin(π * x) /// </summary> /// <param name="x">Argument</param> public static double SinPI(double x) { if (double.IsNaN(x) || double.IsInfinity(x)) { Policies.ReportDomainError("SinPI(x: {0}): Requires finite x", x); return(double.NaN); } // sin(-x) == -sin(x) bool neg = false; if (x < 0) { x = -x; neg = !neg; } // Reduce angles over 2*PI if (x >= 2) { x -= 2 * Math.Floor(x / 2); } Debug.Assert(x >= 0 && x < 2); // reflect in the x-axis // and negate so that x in [0,1]*π if (x > 1) { x = 2.0 - x; neg = !neg; } // reflect in the y-axis so that x in [0,0.5]*π if (x > 0.5) { x = 1.0 - x; } double result; if (x == 0.5) { result = 1.0; } else if (x > 0.25) { result = Math.Cos((0.5 - x) * Math.PI); } else { result = Math.Sin(Math.PI * x); } return((neg) ? -result : result); }
/// <summary> /// Returns the sum of a hypergeometric series 3F1. /// <para>Sum3F1 = addend + Σ(( (a1)_n * (a2)_n * (a3)_n)/(b1)_n * (z^n/n!)) n={0,∞}</para> /// </summary> /// <param name="a1">First numerator</param> /// <param name="a2">Second numerator</param> /// <param name="a3">Third numerator</param> /// <param name="b1">First denominator. Requires b1 > 0</param> /// <param name="z">Argument</param> /// <param name="addend">The addend to the series</param> /// <returns> /// The sum of the series, or NaN if the series does not converge /// within the policy maximum number of iterations /// </returns> public static double Sum3F1(double a1, double a2, double a3, double b1, double z, double addend = 0) { if ((double.IsNaN(a1) || double.IsInfinity(a1)) || (double.IsNaN(a2) || double.IsInfinity(a2)) || (double.IsNaN(a3) || double.IsInfinity(a3)) || (double.IsNaN(z) || double.IsInfinity(z)) || (double.IsNaN(b1) || double.IsInfinity(b1)) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum3F1(a1: {0}, a2: {1}, a3: {2}, b1: {3}, z: {4}, addend: {5}): Requires finite arguments", a1, a2, a3, b1, z, addend); return double.NaN; } if (b1 <= 0 && Math2.IsInteger(b1)) { Policies.ReportDomainError("Sum3F1(a1: {0}, a2: {1}, a3: {2}, b1: {3}, z: {4}, addend: {5}): Requires b1 is not zero or a negative integer", a1, a2, a3, b1, z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; if (a1 == b1) return Sum2F0(a2, a3, z, addend); if (a2 == b1) return Sum2F0(a1, a3, z, addend); if (a3 == b1) return Sum2F0(a1, a2, z, addend); #if false // TODO: if (a1 == 1) return Sum3F1_A1(a2, a3, b1, z, addend); if (a2 == 1) return Sum3F1_A1(a1, a3, b1, z, addend); if (a3 == 1) return Sum3F1_A1(a1, a2, b1, z, addend); #endif double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (z / (n + 1)) * ((a1 + n) / (b1 + n)) * (a2 + n) * (a3 + n); sum += term; #if CMP_FLOAT if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) return sum; #else if (sum == prevSum) return sum; #endif } Policies.ReportConvergenceError("Sum3F1(a1: {0}, a2: {1}, a3: {2}, b1: {3}, z: {4}, addend: {5}): No convergence after {6} iterations", a1, a2, a3, b1, z, addend, n); return double.NaN; }
/// <summary> /// Returns the Scaled Complementary Error Function /// <para>Erfcx(x) = e^(x^2)*Erfc(x)</para> /// </summary> /// <param name="x">The Argument</param> /// <returns></returns> public static double Erfcx(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Erfcx(x: {0}): NaN not allowed", x); return(double.NaN); } return(Erf_Imp(x, true, true)); }