/// <summary> /// Calculation of /// b = Φ(h+t)·exp(h·t) - Φ(h-t)·exp(-h·t) /// exp(-(h²+t²)/2) /// = --------------- · [ Y(h+t) - Y(h-t) ] /// √(2π) /// with /// Y(z) := Φ(z)/φ(z) /// using an expansion of Y(h+t)-Y(h-t) for small t to twelvth order in t. /// Theoretically accurate to (better than) precision ε = 2.23E-16 when h<=0 and t < τ with τ := /// 2·ε^(1/16) ≈ 0.21. /// The main bottleneck for precision is the coefficient a:=1+h·Y(h) when |h|>1 . /// Smalltexpansions the of normalised black call. /// Y(h) := Φ(h)/φ(h) = √(π/2)·erfcx(-h/√2) /// a := 1+h·Y(h) --- Note that due to h<0, and h·Y(h) -> -1 (from above) as h -> -∞, we also have that a> /// 0 and a -> 0 as h -> -∞ /// w := t² , h2 := h² /// </summary> /// <param name="h">The h.</param> /// <param name="t">The t.</param> /// <returns></returns> private double SmalltexpansionOfNormalisedBlackCall(double h, double t) { var a = 1 + h * (0.5 * SqrtTwoPi) * Erfc.ErfcxCody(-OneOverSqrtTwo * h); var w = t * t; var h2 = h * h; var expansion = 2 * t * (a + w * ((-1 + 3 * a + a * h2) / 6 + w * ((-7 + 15 * a + h2 * (-1 + 10 * a + a * h2)) / 120 + w * ((-57 + 105 * a + h2 * (-18 + 105 * a + h2 * (-1 + 21 * a + a * h2))) / 5040 + w * ((-561 + 945 * a + h2 * (-285 + 1260 * a + h2 * (-33 + 378 * a + h2 * (-1 + 36 * a + a * h2))) ) / 362880 + w * ((-6555 + 10395 * a + h2 * (-4680 + 17325 * a + h2 * (-840 + 6930 * a + h2 * (-52 + 990 * a + h2 * (-1 + 55 * a + a * h2))))) / 39916800 + (-89055 + 135135 * a + h2 * (-82845 + 270270 * a + h2 * (-20370 + 135135 * a + h2 * (-1926 + 25740 * a + h2 * (-75 + 2145 * a + h2 * (-1 + 78 * a + a * h2)))))) * w / 6227020800.0)))))); var b = OneOverSqrtTwoPi * Math.Exp(-0.5 * (h * h + t * t)) * expansion; return(Math.Max(b, 0.0)); }
// // Introduced on 2017-02-18 // // b(x,s) = Φ(x/s+s/2)·exp(x/2) - Φ(x/s-s/2)·exp(-x/2) // = Φ(h+t)·exp(x/2) - Φ(h-t)·exp(-x/2) // = ½ · exp(-u²-v²) · [ erfcx(u-v) - erfcx(u+v) ] // = ½ · [ exp(x/2)·erfc(u-v) - exp(-x/2)·erfc(u+v) ] // = ½ · [ exp(x/2)·erfc(u-v) - exp(-u²-v²)·erfcx(u+v) ] // = ½ · [ exp(-u²-v²)·erfcx(u-v) - exp(-x/2)·erfc(u+v) ] // with // h = x/s , t = s/2 , // and // u = -h/√2 and v = t/√2 . // // Cody's erfc() and erfcx() functions each, for some values of their argument, involve the evaluation // of the exponential function exp(). The normalised Black function requires additional evaluation(s) // of the exponential function irrespective of which of the above formulations is used. However, the total // number of exponential function evaluations can be minimised by a judicious choice of one of the above // formulations depending on the input values and the branch logic in Cody's erfc() and erfcx(). // private double NormalisedBlackCallWithOptimalUseOfCodysFunctions(double x, double s) { const double codysThreshold = 0.46875; var h = x / s; var t = 0.5 * s; var q1 = -OneOverSqrtTwo * (h + t); var q2 = -OneOverSqrtTwo * (h - t); double twoB; if (q1 < codysThreshold) { if (q2 < codysThreshold) { twoB = Math.Exp(0.5 * x) * Erfc.ErfcCody(q1) - Math.Exp(-0.5 * x) * Erfc.ErfcCody(q2); } else { twoB = Math.Exp(0.5 * x) * Erfc.ErfcCody(q1) - Math.Exp(-0.5 * (h * h + t * t)) * Erfc.ErfcCody(q2); } } else { if (q2 < codysThreshold) { twoB = Math.Exp(-0.5 * (h * h + t * t)) * Erfc.ErfcxCody(q1) - Math.Exp(-0.5 * x) * Erfc.ErfcCody(q2); } else { twoB = Math.Exp(-0.5 * (h * h + t * t)) * (Erfc.ErfcxCody(q1) - Erfc.ErfcxCody(q2)); } } return(Math.Max(0.5 * twoB, 0.0)); }