Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9e7be8c

Browse files
committed
BUG: Overflow in tan and tanh for large complex values
numpygh-2321 (trac 1726). np.tanh(1000+0j) gave nan+nan*j, should be 1.0+0j. The same bug was present in np.tan(0+1000j). Bug fixed by replacing our complex tan and tanh implementation with one from FreeBSD.
1 parent 2943c43 commit 9e7be8c

4 files changed

Lines changed: 133 additions & 35 deletions

File tree

numpy/core/include/numpy/npy_math.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ npy_cdouble npy_csqrt(npy_cdouble z);
420420

421421
npy_cdouble npy_ccos(npy_cdouble z);
422422
npy_cdouble npy_csin(npy_cdouble z);
423+
npy_cdouble npy_ctan(npy_cdouble z);
424+
425+
npy_cdouble npy_ctanh(npy_cdouble z);
423426

424427
/*
425428
* Single precision complex functions
@@ -435,6 +438,9 @@ npy_cfloat npy_csqrtf(npy_cfloat z);
435438

436439
npy_cfloat npy_ccosf(npy_cfloat z);
437440
npy_cfloat npy_csinf(npy_cfloat z);
441+
npy_cfloat npy_ctanf(npy_cfloat z);
442+
443+
npy_cfloat npy_ctanhf(npy_cfloat z);
438444

439445
/*
440446
* Extended precision complex functions
@@ -450,6 +456,9 @@ npy_clongdouble npy_csqrtl(npy_clongdouble z);
450456

451457
npy_clongdouble npy_ccosl(npy_clongdouble z);
452458
npy_clongdouble npy_csinl(npy_clongdouble z);
459+
npy_clongdouble npy_ctanl(npy_clongdouble z);
460+
461+
npy_clongdouble npy_ctanhl(npy_clongdouble z);
453462

454463
/*
455464
* Functions that set the floating point error

numpy/core/setup_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def check_api_version(apiversion, codegen_dir):
144144
C99_COMPLEX_TYPES = ['complex double', 'complex float', 'complex long double']
145145

146146
C99_COMPLEX_FUNCS = ['creal', 'cimag', 'cabs', 'carg', 'cexp', 'csqrt', 'clog',
147-
'ccos', 'csin', 'cpow']
147+
'ccos', 'csin', 'cpow', 'ctan', 'ctanh']
148148

149149
def fname2def(name):
150150
return "HAVE_%s" % name.upper()

numpy/core/src/npymath/npy_math_complex.c.src

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Most of the code is taken from the msun library in FreeBSD (HEAD @ 30th June
55
* 2009), under the following license:
66
*
7-
* Copyright (c) 2007 David Schultz <[email protected]>
7+
* Copyright (c) 2007, 2011 David Schultz <[email protected]>
88
* All rights reserved.
99
*
1010
* Redistribution and use in source and binary forms, with or without
@@ -227,6 +227,123 @@
227227
return npy_cpack@c@(npy_sin@c@(x) * npy_cosh@c@(y), npy_cos@c@(x) * npy_sinh@c@(y));
228228
}
229229
#endif
230+
231+
#ifndef HAVE_CTAN@C@
232+
@ctype@ npy_ctan@c@(@ctype@ z)
233+
{
234+
/* ctan(z) = -I * ctanh(I * z) */
235+
z = npy_ctanh@c@(npy_cpack@c@(-npy_cimag@c@(z), npy_creal@c@(z)));
236+
return (npy_cpack@c@(npy_cimag@c@(z), -npy_creal@c@(z)));
237+
}
238+
#endif
239+
240+
#ifndef HAVE_CTANH@C@
241+
/*
242+
* Taken from the msun library in FreeBSD, rev 226600.
243+
*
244+
* Hyperbolic tangent of a complex argument z = x + i y.
245+
*
246+
* The algorithm is from:
247+
*
248+
* W. Kahan. Branch Cuts for Complex Elementary Functions or Much
249+
* Ado About Nothing's Sign Bit. In The State of the Art in
250+
* Numerical Analysis, pp. 165 ff. Iserles and Powell, eds., 1987.
251+
*
252+
* Method:
253+
*
254+
* Let t = tan(x)
255+
* beta = 1/cos^2(y)
256+
* s = sinh(x)
257+
* rho = cosh(x)
258+
*
259+
* We have:
260+
*
261+
* tanh(z) = sinh(z) / cosh(z)
262+
*
263+
* sinh(x) cos(y) + i cosh(x) sin(y)
264+
* = ---------------------------------
265+
* cosh(x) cos(y) + i sinh(x) sin(y)
266+
*
267+
* cosh(x) sinh(x) / cos^2(y) + i tan(y)
268+
* = -------------------------------------
269+
* 1 + sinh^2(x) / cos^2(y)
270+
*
271+
* beta rho s + i t
272+
* = ----------------
273+
* 1 + beta s^2
274+
*
275+
* Modifications:
276+
*
277+
* I omitted the original algorithm's handling of overflow in tan(x) after
278+
* verifying with nearpi.c that this can't happen in IEEE single or double
279+
* precision. I also handle large x differently.
280+
*/
281+
282+
#define TANH_HUGE 22.0
283+
#define TANHF_HUGE 11.0F
284+
#define TANHL_HUGE 42.0L
285+
286+
@ctype@ npy_ctanh@c@(@ctype@ z)
287+
{
288+
@type@ x, y;
289+
@type@ t, beta, s, rho, denom;
290+
291+
x = npy_creal@c@(z);
292+
y = npy_cimag@c@(z);
293+
294+
/*
295+
* ctanh(NaN + i 0) = NaN + i 0
296+
*
297+
* ctanh(NaN + i y) = NaN + i NaN for y != 0
298+
*
299+
* The imaginary part has the sign of x*sin(2*y), but there's no
300+
* special effort to get this right.
301+
*
302+
* ctanh(+-Inf +- i Inf) = +-1 +- 0
303+
*
304+
* ctanh(+-Inf + i y) = +-1 + 0 sin(2y) for y finite
305+
*
306+
* The imaginary part of the sign is unspecified. This special
307+
* case is only needed to avoid a spurious invalid exception when
308+
* y is infinite.
309+
*/
310+
if (!npy_isfinite(x)) {
311+
if (npy_isnan(x))
312+
return npy_cpack@c@(x, (y == 0 ? y : x * y));
313+
return npy_cpack@c@(npy_copysign@c@(1,x),
314+
npy_copysign@c@(0, npy_isinf(y) ? y : npy_sin@c@(y) * npy_cos@c@(y)));
315+
}
316+
317+
/*
318+
* ctanh(x + i NAN) = NaN + i NaN
319+
* ctanh(x +- i Inf) = NaN + i NaN
320+
*/
321+
if (!npy_isfinite(y))
322+
return (npy_cpack@c@(y - y, y - y));
323+
324+
/*
325+
* ctanh(+-huge + i +-y) ~= +-1 +- i 2sin(2y)/exp(2x), using the
326+
* approximation sinh^2(huge) ~= exp(2*huge) / 4.
327+
* We use a modified formula to avoid spurious overflow.
328+
*/
329+
if (x >= TANH@C@_HUGE) {
330+
@type@ exp_mx = npy_exp@c@(-npy_fabs@c@(x));
331+
return (npy_cpack@c@(npy_copysign@c@(1, x),
332+
4 * npy_sin@c@(y) * npy_cos@c@(y) * exp_mx * exp_mx));
333+
}
334+
335+
/* Kahan's algorithm */
336+
t = npy_tan@c@(y);
337+
beta = 1.0 + t * t; /* = 1 / cos^2(y) */
338+
s = npy_sinh@c@(x);
339+
rho = npy_sqrt@c@(1 + s * s); /* = cosh(x) */
340+
denom = 1 + beta * s * s;
341+
return (npy_cpack@c@((beta * rho * s) / denom, t / denom));
342+
}
343+
#undef TANH_HUGE
344+
#undef TANHF_HUGE
345+
#undef TANHL_HUGE
346+
#endif
230347
/**end repeat**/
231348

232349
/*==========================================================
@@ -254,8 +371,8 @@
254371
/**end repeat1**/
255372

256373
/**begin repeat1
257-
* #kind = cexp,clog,csqrt,ccos,csin#
258-
* #KIND = CEXP,CLOG,CSQRT,CCOS,CSIN#
374+
* #kind = cexp,clog,csqrt,ccos,csin,ctan,ctanh#
375+
* #KIND = CEXP,CLOG,CSQRT,CCOS,CSIN,CTAN,CTANH#
259376
*/
260377
#ifdef HAVE_@KIND@@C@
261378
@ctype@ npy_@kind@@c@(@ctype@ z)

numpy/core/src/umath/funcs.inc.src

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -671,42 +671,14 @@ nc_sinh@c@(@ctype@ *x, @ctype@ *r)
671671
static void
672672
nc_tan@c@(@ctype@ *x, @ctype@ *r)
673673
{
674-
@ftype@ sr,cr,shi,chi;
675-
@ftype@ rs,is,rc,ic;
676-
@ftype@ d;
677-
@ftype@ xr=x->real, xi=x->imag;
678-
sr = npy_sin@c@(xr);
679-
cr = npy_cos@c@(xr);
680-
shi = npy_sinh@c@(xi);
681-
chi = npy_cosh@c@(xi);
682-
rs = sr*chi;
683-
is = cr*shi;
684-
rc = cr*chi;
685-
ic = -sr*shi;
686-
d = rc*rc + ic*ic;
687-
r->real = (rs*rc+is*ic)/d;
688-
r->imag = (is*rc-rs*ic)/d;
689-
return;
674+
*r = npy_ctan@c@(*x);
675+
return;
690676
}
691677

692678
static void
693679
nc_tanh@c@(@ctype@ *x, @ctype@ *r)
694680
{
695-
@ftype@ si,ci,shr,chr;
696-
@ftype@ rs,is,rc,ic;
697-
@ftype@ d;
698-
@ftype@ xr=x->real, xi=x->imag;
699-
si = npy_sin@c@(xi);
700-
ci = npy_cos@c@(xi);
701-
shr = npy_sinh@c@(xr);
702-
chr = npy_cosh@c@(xr);
703-
rs = ci*shr;
704-
is = si*chr;
705-
rc = ci*chr;
706-
ic = si*shr;
707-
d = rc*rc + ic*ic;
708-
r->real = (rs*rc+is*ic)/d;
709-
r->imag = (is*rc-rs*ic)/d;
681+
*r = npy_ctanh@c@(*x);
710682
return;
711683
}
712684

0 commit comments

Comments
 (0)