23
23
24
24
#include < ql/errors.hpp>
25
25
#include < ql/mathconstants.hpp>
26
+ #include < ql/math/comparison.hpp>
26
27
#include < ql/math/integrals/exponentialintegrals.hpp>
27
28
29
+ #include < boost/math/special_functions/sign.hpp>
28
30
#include < cmath>
29
31
30
- #ifndef M_EULER_MASCHERONI
31
- #define M_EULER_MASCHERONI 0.5772156649015328606065121
32
- #endif
33
-
34
32
namespace QuantLib {
35
33
namespace exponential_integrals_helper {
36
34
@@ -122,28 +120,61 @@ namespace QuantLib {
122
120
}
123
121
}
124
122
123
+ std::complex <Real> _Ei (
124
+ const std::complex <Real>& z, const std::complex <Real>& acc) {
125
125
126
- std::complex <Real> E1 (std::complex <Real> z) {
127
- QL_REQUIRE (std::abs (z) <= 25.0 , " Insufficient precision for |z| > 25.0" );
126
+ if (z.real () == 0.0 && z.imag () == 0.0
127
+ && std::numeric_limits<Real>::has_infinity) {
128
+ return std::complex <Real>(-std::numeric_limits<Real>::infinity ());
129
+ }
128
130
129
- std::complex <Real> s (0.0 ), sn (-z);
130
131
131
- Size n;
132
- for (n=2 ; n < 1000 && s + sn/Real (n-1 ) != s; ++n) {
133
- s+=sn/Real (n-1 );
134
- sn *= -z/Real (n);
135
- }
132
+ constexpr Real DIST = 4.5 ;
133
+ constexpr Real MAX_ERROR = 5 *QL_EPSILON;
136
134
137
- QL_REQUIRE (n < 1000 , " series conversion issue" );
135
+ const Real z_inf = std::log (0.01 *QL_MAX_REAL) + std::log (100.0 );
136
+ QL_REQUIRE (z.real () < z_inf, " argument error " << z);
138
137
139
- return -M_EULER_MASCHERONI - std::log (z) -s;
140
- }
138
+ const Real z_asym = 2.0 - 1.035 *std::log (MAX_ERROR);
141
139
142
- std:: complex <Real> Ei (std:: complex <Real> z) {
143
- QL_REQUIRE ( std::abs (z) <= 25.0 , " Insufficient precision for |z| > 25.0 " );
140
+ using boost::math::sign;
141
+ const Real abs_z = std::abs (z);
144
142
145
- std::complex <Real> s (0.0 ), sn=z;
143
+ const auto match = [=](
144
+ const std::complex <Real>& z1, const std::complex <Real>& z2)
145
+ -> bool {
146
+ const std::complex <Real> d = z1 - z2;
147
+ return std::abs (d.real ()) <= MAX_ERROR*std::abs (z1.real ())
148
+ && std::abs (d.imag ()) <= MAX_ERROR*std::abs (z1.imag ());
149
+ };
150
+
151
+ if (z.real () > z_inf)
152
+ return std::complex <Real>(std::exp (z)/z) + acc;
146
153
154
+ if (abs_z > 1.1 *z_asym) {
155
+ std::complex <Real> ei = acc + std::complex <Real>(Real (0.0 ), sign (z.imag ())*M_PI);
156
+ std::complex <Real> s (std::exp (z)/z);
157
+ for (Size i=1 ; i <= std::floor (abs_z)+1 ; ++i) {
158
+ if (match (ei+s, ei))
159
+ return ei+s;
160
+ ei += s;
161
+ s *= Real (i)/z;
162
+ }
163
+ QL_FAIL (" series conversion issue for Ei(" << z << " )" );
164
+ }
165
+
166
+ if (abs_z > DIST && (z.real () < 0 || std::abs (z.imag ()) > DIST)) {
167
+ std::complex <Real> ei (0.0 );
168
+ for (Size k = 47 ; k >=1 ; --k) {
169
+ ei = - Real (k*k)/(2.0 *k + 1.0 - z + ei);
170
+ }
171
+ return (acc + std::complex <Real>(0.0 , sign (z.imag ())*M_PI))
172
+ - std::exp (z)/ (1.0 - z + ei);
173
+
174
+ QL_FAIL (" series conversion issue for Ei(" << z << " )" );
175
+ }
176
+
177
+ std::complex <Real> s (0.0 ), sn=z;
147
178
Real nn=1.0 ;
148
179
149
180
Size n;
@@ -156,27 +187,65 @@ namespace QuantLib {
156
187
sn *= -z / Real (2 *n);
157
188
}
158
189
159
- QL_REQUIRE (n < 1000 , " series conversion issue" );
190
+ QL_REQUIRE (n < 1000 , " series conversion issue for Ei(" << z << " )" );
191
+
192
+ const std::complex <Real> r
193
+ = (M_EULER_MASCHERONI + acc) + std::log (z) + std::exp (0.5 *z)*s;
160
194
161
- return M_EULER_MASCHERONI + std::log (z) + std::exp (0.5 *z)*s;
195
+ if (z.imag () != Real (0.0 ))
196
+ return r;
197
+ else
198
+ return std::complex <Real>(r.real (), acc.imag ());
199
+ }
200
+
201
+ std::complex <Real> Ei (const std::complex <Real>&z) {
202
+ return _Ei (z, std::complex <Real>(0.0 ));
203
+ }
204
+
205
+ std::complex <Real> E1 (const std::complex <Real>& z) {
206
+ if (z.imag () < 0.0 ) {
207
+ return -_Ei (-z, std::complex <Real>(0.0 , -M_PI));
208
+ }
209
+ else if (z.imag () > 0.0 || z.real () < 0.0 ) {
210
+ return -_Ei (-z, std::complex <Real>(0.0 , M_PI));
211
+ }
212
+ else {
213
+ return -Ei (-z);
214
+ }
162
215
}
163
216
164
217
// Reference:
165
218
// https://functions.wolfram.com/GammaBetaErf/ExpIntegralEi/introductions/ExpIntegrals/ShowAll.html
166
- std::complex <Real> Si (std::complex <Real> z) {
167
- const std::complex <Real> i (0.0 , 1.0 );
168
-
169
- return 0.25 *i*(2.0 *(Ei (-i*z) - Ei (i*z))
170
- + std::log (i/z) - std::log (-i/z) - std::log (-i*z)
171
- + std::log (i*z));
219
+ std::complex <Real> Si (const std::complex <Real>& z) {
220
+ if (std::abs (z) <= 0.2 ) {
221
+ std::complex <Real> s (0 ), nn (z);
222
+ Size k;
223
+ for (k=2 ; k < 100 && s != s+nn; ++k) {
224
+ s += nn;
225
+ nn *= -z*z/((2.0 *k-2 )*(2 *k-1 )*(2 *k-1 ))*(2.0 *k-3 );
226
+ }
227
+ QL_REQUIRE (k < 100 , " series conversion issue for Si(" << z << " )" );
228
+
229
+ return s;
230
+ }
231
+ else {
232
+ const std::complex <Real> i (0.0 , 1.0 );
233
+ return 0.5 *i*(E1 (-i*z) - E1 (i*z)
234
+ - std::complex <Real>(0.0 , ((z.real () >= 0 && z.imag () >= 0 )
235
+ || (z.real () > 0 && z.imag () < 0 ) )? M_PI : -M_PI));
236
+ }
172
237
}
173
238
174
- std::complex <Real> Ci (std::complex <Real> z) {
239
+ std::complex <Real> Ci (const std::complex <Real>& z) {
175
240
const std::complex <Real> i (0.0 , 1.0 );
176
241
177
- return 0.25 *(2.0 *(Ei (-i*z) + Ei (i*z))
178
- + std::log (i/z) + std::log (-i/z) - std::log (-i*z)
179
- - std::log (i*z)) + std::log (z);
242
+ std::complex <Real> acc (0.0 );
243
+ if (z.real () < 0.0 && z.imag () >= 0.0 )
244
+ acc.imag (M_PI);
245
+ else if (z.real () <= 0.0 && z.imag () <= 0.0 )
246
+ acc.imag (-M_PI);
247
+
248
+ return -0.5 *(E1 (-i*z) + E1 (i*z)) + acc;
180
249
}
181
250
}
182
251
}
0 commit comments