|
1 | 1 | /* |
| 2 | + * vim: syntax=c |
| 3 | + * |
2 | 4 | * Implement some C99-compatible complex math functions |
3 | 5 | * |
4 | 6 | * Most of the code is taken from the msun library in FreeBSD (HEAD @ 30th June |
|
37 | 39 | * #c = f, , l# |
38 | 40 | * #C = F, , L# |
39 | 41 | * #TMAX = FLT_MAX, DBL_MAX, LDBL_MAX# |
| 42 | + * #TMIN = FLT_MIN, DBL_MIN, LDBL_MIN# |
| 43 | + * #TMANT_DIG = FLT_MANT_DIG, DBL_MANT_DIG, LDBL_MANT_DIG# |
40 | 44 | * #precision = 1, 2, 3# |
41 | 45 | */ |
42 | 46 |
|
@@ -205,9 +209,70 @@ static NPY_INLINE @ctype@ cmuli@c@(@ctype@ a) |
205 | 209 | #endif |
206 | 210 |
|
207 | 211 | #ifndef HAVE_CLOG@C@ |
| 212 | +/* algorithm from cpython, rev. d86f5686cef9 |
| 213 | + * |
| 214 | + * The usual formula for the real part is log(hypot(z.real, z.imag)). |
| 215 | + * There are four situations where this formula is potentially |
| 216 | + * problematic: |
| 217 | + * |
| 218 | + * (1) the absolute value of z is subnormal. Then hypot is subnormal, |
| 219 | + * so has fewer than the usual number of bits of accuracy, hence may |
| 220 | + * have large relative error. This then gives a large absolute error |
| 221 | + * in the log. This can be solved by rescaling z by a suitable power |
| 222 | + * of 2. |
| 223 | + * |
| 224 | + * (2) the absolute value of z is greater than DBL_MAX (e.g. when both |
| 225 | + * z.real and z.imag are within a factor of 1/sqrt(2) of DBL_MAX) |
| 226 | + * Again, rescaling solves this. |
| 227 | + * |
| 228 | + * (3) the absolute value of z is close to 1. In this case it's |
| 229 | + * difficult to achieve good accuracy, at least in part because a |
| 230 | + * change of 1ulp in the real or imaginary part of z can result in a |
| 231 | + * change of billions of ulps in the correctly rounded answer. |
| 232 | + * |
| 233 | + * (4) z = 0. The simplest thing to do here is to call the |
| 234 | + * floating-point log with an argument of 0, and let its behaviour |
| 235 | + * (returning -infinity, signaling a floating-point exception, setting |
| 236 | + * errno, or whatever) determine that of c_log. So the usual formula |
| 237 | + * is fine here. |
| 238 | +*/ |
208 | 239 | @ctype@ npy_clog@c@(@ctype@ z) |
209 | 240 | { |
210 | | - return npy_cpack@c@(npy_log@c@ (npy_cabs@c@ (z)), npy_carg@c@ (z)); |
| 241 | + @type@ ax = npy_fabs@c@(npy_creal@c@(z)); |
| 242 | + @type@ ay = npy_fabs@c@(npy_cimag@c@(z)); |
| 243 | + @type@ rr, ri; |
| 244 | + |
| 245 | + if (ax > @TMAX@/4 || ay > @TMAX@/4) { |
| 246 | + rr = npy_log@c@(npy_hypot@c@(ax/2, ay/2)) + NPY_LOGE2@c@; |
| 247 | + } |
| 248 | + else if (ax < @TMIN@ && ay < @TMIN@) { |
| 249 | + if (ax > 0 || ay > 0) { |
| 250 | + /* catch cases where hypot(ax, ay) is subnormal */ |
| 251 | + rr = npy_log@c@(npy_hypot@c@(npy_ldexp@c@(ax, @TMANT_DIG@), |
| 252 | + npy_ldexp@c@(ay, @TMANT_DIG@))) - @TMANT_DIG@*NPY_LOGE2@c@; |
| 253 | + } |
| 254 | + else { |
| 255 | + /* log(+/-0 +/- 0i) */ |
| 256 | + /* raise divide-by-zero floating point exception */ |
| 257 | + rr = -1.0@c@ / npy_creal@c@(z); |
| 258 | + rr = npy_copysign@c@(rr, -1); |
| 259 | + ri = npy_carg@c@(z); |
| 260 | + return npy_cpack@c@(rr, ri); |
| 261 | + } |
| 262 | + } |
| 263 | + else { |
| 264 | + @type@ h = npy_hypot@c@(ax, ay); |
| 265 | + if (0.71 <= h && h <= 1.73) { |
| 266 | + @type@ am = ax > ay ? ax : ay; /* max(ax, ay) */ |
| 267 | + @type@ an = ax > ay ? ay : ax; /* min(ax, ay) */ |
| 268 | + rr = npy_log1p@c@((am-1)*(am+1)+an*an)/2; |
| 269 | + } |
| 270 | + else { |
| 271 | + rr = npy_log@c@(h); |
| 272 | + } |
| 273 | + } |
| 274 | + ri = npy_carg@c@(z); |
| 275 | + return npy_cpack@c@(rr, ri); |
211 | 276 | } |
212 | 277 | #endif |
213 | 278 |
|
|
0 commit comments