Skip to content

Commit 7cf16b7

Browse files
committed
Revert "Add isspecial() method for mp/fp contexts, deprecate isnormal()"
This reverts commit e6aa3b4. Subnormals are properly handled by fp.isnormal(). Deprecate fp.is_special() method. Fixes #946
1 parent c7128a6 commit 7cf16b7

8 files changed

Lines changed: 95 additions & 113 deletions

File tree

CHANGES

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ Features:
5858
#936 (Sergey B Kirpichev)
5959
* Use PyREPL, as fallback (no IPython), see #941 (Sergey B Kirpichev)
6060
* Add exp2() and log2(), see #948 (Sergey B Kirpichev)
61-
* Add isspecial() method for contexts, see #949 (Sergey B Kirpichev)
6261
* Support rounding property for the mp context, see #963 (Sergey B Kirpichev)
6362
* Add Fox H-function with rational A/B parameters (foxh()), see #982 (Hongren Zheng)
6463

@@ -79,13 +78,13 @@ Compatibility:
7978
#779, #844 and #845 (Sergey B Kirpichev, Warren Weckesser)
8079
* Deprecate mpmath.math2, see #769 (Sergey B Kirpichev)
8180
* Drop support for CPython 3.8, see #911 (Sergey B Kirpichev)
82-
* Deprecate isnormal() method of contexts, see #949 (Sergey B Kirpichev)
8381
* Importing from the mpmath.libmp submodules is deprecated, use instead ``from
8482
mpmath.libmp import foo``, see
8583
issue https://github.com/mpmath/mpmath/issues/704#issuecomment-2953536980
8684
for available functions (Sergey B Kirpichev)
8785
* Deprecate bitcount function, see #721 and #955 (Sergey B Kirpichev)
8886
* Deprecate mpf/mpc_log, see #989 (Sergey B Kirpichev)
87+
* Deprecate fp.is_special(), see #1042 (Sergey B Kirpichev)
8988

9089
Bug fixes:
9190

@@ -168,6 +167,7 @@ Bug fixes:
168167
* Fix erf(z) with re(z) of large magnitude, see #1039 (Sergey B Kirpichev)
169168
* Return nan's for polylog(s, nan) or polylog(s, nan+nanj),
170169
see #1041 (Sergey B Kirpichev)
170+
* Fix fp.isnormal() for subnormals, see #1042 (Sergey B Kirpichev)
171171

172172
Maintenance:
173173

mpmath/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
isinf = mp.isinf
7676
isnan = mp.isnan
7777
isnormal = mp.isnormal
78-
isspecial = mp.isspecial
7978
isint = mp.isint
8079
isfinite = mp.isfinite
8180
almosteq = mp.almosteq

mpmath/ctx_fp.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import functools
33
import inspect
44
import math
5-
import sys
65
import warnings
6+
import sys
77

88
from . import function_docs, libfp, libmp
99
from .ctx_base import StandardBaseContext
@@ -88,8 +88,10 @@ def bernoulli(ctx, n, plus=False):
8888

8989
absmin = absmax = abs
9090

91-
def isspecial(ctx, x):
92-
return not x or x - x != 0.0
91+
def is_special(ctx, x):
92+
warnings.warn("the is_special() method is deprecated",
93+
DeprecationWarning)
94+
return not ctx.isnormal(x)
9395

9496
def isnan(ctx, x):
9597
return x != x
@@ -103,11 +105,10 @@ def isfinite(ctx, x):
103105
return math.isfinite(x)
104106

105107
def isnormal(ctx, x):
106-
warnings.warn("the isnormal() method is deprecated",
107-
DeprecationWarning)
108-
if x:
109-
return x - x == 0.0
110-
return False
108+
if type(x) is complex:
109+
return ctx.isnormal(abs(x))
110+
# XXX: can use math.isnormal() on Python 3.15+
111+
return bool(x) and math.isfinite(x) and abs(x) >= sys.float_info.min
111112

112113
def isnpint(ctx, x):
113114
if type(x) is complex:

mpmath/ctx_mp_python.py

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import inspect
22
import numbers
33
import sys
4-
import warnings
54

65
from . import function_docs
76
from .libmp import (MPQ, MPZ, ComplexResult, dps_to_prec, finf, fnan, fninf,
@@ -909,8 +908,29 @@ def isinf(ctx, x):
909908
return ctx.isinf(x)
910909

911910
def isnormal(ctx, x):
912-
warnings.warn("the isnormal() method is deprecated",
913-
DeprecationWarning)
911+
"""
912+
Determine whether *x* is "normal" in the sense of floating-point
913+
representation; that is, return *False* if *x* is zero, an
914+
infinity or NaN; otherwise return *True*. By extension, a
915+
complex number *x* is considered "normal" if its magnitude is
916+
normal::
917+
918+
>>> from mpmath import isnormal, inf, nan, mpc
919+
>>> isnormal(3)
920+
True
921+
>>> isnormal(0)
922+
False
923+
>>> isnormal(inf); isnormal(-inf); isnormal(nan)
924+
False
925+
False
926+
False
927+
>>> isnormal(0+0j)
928+
False
929+
>>> isnormal(0+3j)
930+
True
931+
>>> isnormal(mpc(2,nan))
932+
False
933+
"""
914934
if hasattr(x, "_mpf_"):
915935
if ctx.isfinite(x):
916936
return bool(to_man_exp(x._mpf_, signed=True)[0])
@@ -927,48 +947,6 @@ def isnormal(ctx, x):
927947
x = ctx.convert(x)
928948
return ctx.isnormal(x)
929949

930-
def isspecial(ctx, x):
931-
"""
932-
Determine whether *x* is a "special" in the sense of floating-point
933-
representation; that is, return *True* if *x* is zero, an
934-
infinity or NaN; otherwise return *False*. By extension, a
935-
complex number *x* is considered "special" if its magnitude is
936-
special::
937-
938-
>>> from mpmath import isspecial, inf, nan, mpc
939-
>>> isspecial(3)
940-
False
941-
>>> isspecial(0)
942-
True
943-
>>> isspecial(inf)
944-
True
945-
>>> isspecial(-inf)
946-
True
947-
>>> isspecial(nan)
948-
True
949-
>>> isspecial(0+0j)
950-
True
951-
>>> isspecial(0+3j)
952-
False
953-
>>> isspecial(mpc(2,nan))
954-
True
955-
"""
956-
if hasattr(x, "_mpf_"):
957-
if ctx.isfinite(x):
958-
return not bool(to_man_exp(x._mpf_, signed=True)[0])
959-
return True
960-
if hasattr(x, "_mpc_"):
961-
re, im = x._mpc_
962-
re_special = not bool(re[1])
963-
im_special = not bool(im[1])
964-
if re == fzero: return im_special
965-
if im == fzero: return re_special
966-
return re_special or im_special
967-
if isinstance(x, int_types) or isinstance(x, MPQ):
968-
return not bool(x)
969-
x = ctx.convert(x)
970-
return ctx.isspecial(x)
971-
972950
def isint(ctx, x, gaussian=False):
973951
"""
974952
Return *True* if *x* is integer-valued; otherwise return

mpmath/functions/bessel.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from ..libmp.backend import MPQ
22
from .functions import defun, defun_wrapped
33

4-
54
@defun
65
def j0(ctx, x):
76
"""Computes the Bessel function `J_0(x)`. See :func:`~mpmath.besselj`."""
@@ -513,7 +512,7 @@ def airyai(ctx, z, derivative=0, **kwargs):
513512
else:
514513
n = 0
515514
# Values at infinities
516-
if ctx.isspecial(z) and z:
515+
if not ctx.isnormal(z) and z:
517516
if n and ntype == 'Z':
518517
if n == -1:
519518
if z == ctx.inf:
@@ -605,7 +604,7 @@ def airybi(ctx, z, derivative=0, **kwargs):
605604
else:
606605
n = 0
607606
# Values at infinities
608-
if ctx.isspecial(z) and z:
607+
if not ctx.isnormal(z) and z:
609608
if n and ntype == 'Z':
610609
if z == ctx.inf:
611610
return z

mpmath/functions/elliptic.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464

6565
from .functions import defun, defun_wrapped
6666

67-
6867
@defun_wrapped
6968
def eta(ctx, tau):
7069
r"""
@@ -480,7 +479,7 @@ def RF_calc(ctx, x, y, z, r):
480479
if y == z: return RC_calc(ctx, x, y, r)
481480
if x == z: return RC_calc(ctx, y, x, r)
482481
if x == y: return RC_calc(ctx, z, x, r)
483-
if ctx.isspecial(x) or ctx.isspecial(y) and ctx.isspecial(z):
482+
if not (ctx.isnormal(x) and ctx.isnormal(y) and ctx.isnormal(z)):
484483
if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z):
485484
return x*y*z
486485
if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z):
@@ -510,7 +509,7 @@ def RF_calc(ctx, x, y, z, r):
510509
return ctx.power(Am,-0.5) * (9240-924*E2+385*E2**2+660*E3-630*E2*E3)/9240
511510

512511
def RC_calc(ctx, x, y, r, pv=True):
513-
if ctx.isspecial(x) or ctx.isspecial(y):
512+
if not (ctx.isnormal(x) and ctx.isnormal(y)):
514513
if ctx.isinf(x) or ctx.isinf(y):
515514
return 1/(x*y)
516515
if y == 0:
@@ -550,8 +549,8 @@ def RJ_calc(ctx, x, y, z, p, r, integration):
550549
Carlson's algorithm is correct.
551550
With integration == 2, uses only integration.
552551
"""
553-
if (ctx.isspecial(x) or ctx.isspecial(y)
554-
or ctx.isspecial(z) or ctx.isspecial(p)):
552+
if not (ctx.isnormal(x) and ctx.isnormal(y) and \
553+
ctx.isnormal(z) and ctx.isnormal(p)):
555554
if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z) or ctx.isnan(p):
556555
return x*y*z*p
557556
if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z) or ctx.isinf(p):
@@ -1132,7 +1131,7 @@ def ellipf(ctx, phi, m):
11321131
11331132
"""
11341133
z = phi
1135-
if ctx.isspecial(z) or ctx.isspecial(m):
1134+
if not (ctx.isnormal(z) and ctx.isnormal(m)):
11361135
if m == 0:
11371136
return z + m
11381137
if z == 0:
@@ -1302,7 +1301,7 @@ def ellipe(ctx, *args):
13021301
else:
13031302
phi, m = args
13041303
z = phi
1305-
if ctx.isspecial(z) or ctx.isspecial(m):
1304+
if not (ctx.isnormal(z) and ctx.isnormal(m)):
13061305
if m == 0:
13071306
return z + m
13081307
if z == 0:
@@ -1439,7 +1438,7 @@ def ellippi(ctx, *args):
14391438
n, phi, m = args
14401439
complete = False
14411440
z = phi
1442-
if ctx.isspecial(n) or ctx.isspecial(z) or ctx.isspecial(m):
1441+
if not (ctx.isnormal(n) and ctx.isnormal(z) and ctx.isnormal(m)):
14431442
if ctx.isnan(n) or ctx.isnan(z) or ctx.isnan(m):
14441443
raise ValueError
14451444
if complete:

mpmath/functions/functions.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,8 @@ def _lambertw_special(ctx, z, k):
395395
# Some kind of nan or complex inf/nan?
396396
return ctx.ln(z)
397397

398-
import cmath
399398
import math
400-
399+
import cmath
401400

402401
def _lambertw_approx_hybrid(z, k):
403402
imag_sign = 0
@@ -515,7 +514,7 @@ def _lambertw_series(ctx, z, k, tol):
515514
def lambertw(ctx, z, k=0):
516515
z = ctx.convert(z)
517516
k = int(k)
518-
if ctx.isspecial(z):
517+
if not ctx.isnormal(z):
519518
return _lambertw_special(ctx, z, k)
520519
prec = ctx.prec
521520
ctx.prec += 20 + ctx.mag(k or 1)

mpmath/tests/test_basic_ops.py

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import math
44
import operator
55
import random
6+
import sys
67
from concurrent.futures import ThreadPoolExecutor
78

89
import pytest
@@ -11,9 +12,8 @@
1112

1213
import mpmath
1314
from mpmath import (ceil, fadd, fdiv, floor, fmul, fneg, fp, frac, fsub, inf,
14-
isinf, isint, isnan, isnormal, isspecial, iv, monitor, mp,
15-
mpc, mpf, mpi, nan, ninf, nint, nint_distance, nstr, pi,
16-
workprec)
15+
isinf, isint, isnan, isnormal, iv, monitor, mp, mpc, mpf,
16+
mpi, nan, ninf, nint, nint_distance, nstr, pi, workprec)
1717
from mpmath.libmp import (MPQ, MPZ, finf, fnan, fninf, fnone, fone, from_float,
1818
from_int, from_pickable, from_str, isprime, mpf_add,
1919
mpf_mul, mpf_sub, round_down, round_nearest,
@@ -462,40 +462,50 @@ def test_isnan_etc():
462462
assert isinf(MPQ(3, 2)) is False
463463
assert isinf(MPQ(0, 1)) is False
464464
pytest.raises(TypeError, lambda: isinf(object()))
465-
assert isspecial(3) is False
466-
assert isspecial(3.5) is False
467-
assert isspecial(mpf(3.5)) is False
468-
assert isspecial(0) is True
469-
assert isspecial(mpf(0)) is True
470-
assert isspecial(0.0) is True
471-
assert isspecial(inf) is True
472-
assert isspecial(-inf) is True
473-
assert isspecial(nan) is True
474-
assert isspecial(float(inf)) is True
475-
assert isspecial(mpc(0, 0)) is True
476-
assert isspecial(mpc(3, 0)) is False
477-
assert isspecial(mpc(0, 3)) is False
478-
assert isspecial(mpc(3, 3)) is False
479-
assert isspecial(mpc(0, nan)) is True
480-
assert isspecial(mpc(0, inf)) is True
481-
assert isspecial(mpc(3, nan)) is True
482-
assert isspecial(mpc(3, inf)) is True
483-
assert isspecial(mpc(3, -inf)) is True
484-
assert isspecial(mpc(nan, 0)) is True
485-
assert isspecial(mpc(inf, 0)) is True
486-
assert isspecial(mpc(nan, 3)) is True
487-
assert isspecial(mpc(inf, 3)) is True
488-
assert isspecial(mpc(inf, nan)) is True
489-
assert isspecial(mpc(nan, inf)) is True
490-
assert isspecial(mpc(nan, nan)) is True
491-
assert isspecial(mpc(inf, inf)) is True
492-
assert isspecial(MPQ(3, 2)) is False
493-
assert isspecial(MPQ(0, 1)) is True
494-
pytest.raises(TypeError, lambda: isspecial(object()))
495-
assert isspecial(5e-324) is False # issue 946
496-
assert fp.isspecial(5e-324) is False
497-
assert fp.isspecial(0.0) is True
498-
assert fp.isspecial(-0.0) is True
465+
assert isnormal(3) is True
466+
assert isnormal(3.5) is True
467+
assert isnormal(mpf(3.5)) is True
468+
assert isnormal(0) is False
469+
assert isnormal(mpf(0)) is False
470+
assert isnormal(0.0) is False
471+
assert isnormal(inf) is False
472+
assert isnormal(-inf) is False
473+
assert isnormal(nan) is False
474+
assert isnormal(float(inf)) is False
475+
assert isnormal(mpc(0, 0)) is False
476+
assert isnormal(mpc(3, 0)) is True
477+
assert isnormal(mpc(0, 3)) is True
478+
assert isnormal(mpc(3, 3)) is True
479+
assert isnormal(mpc(0, nan)) is False
480+
assert isnormal(mpc(0, inf)) is False
481+
assert isnormal(mpc(3, nan)) is False
482+
assert isnormal(mpc(3, inf)) is False
483+
assert isnormal(mpc(3, -inf)) is False
484+
assert isnormal(mpc(nan, 0)) is False
485+
assert isnormal(mpc(inf, 0)) is False
486+
assert isnormal(mpc(nan, 3)) is False
487+
assert isnormal(mpc(inf, 3)) is False
488+
assert isnormal(mpc(inf, nan)) is False
489+
assert isnormal(mpc(nan, inf)) is False
490+
assert isnormal(mpc(nan, nan)) is False
491+
assert isnormal(mpc(inf, inf)) is False
492+
assert isnormal(MPQ(3, 2)) is True
493+
assert isnormal(MPQ(0, 1)) is False
494+
pytest.raises(TypeError, lambda: isnormal(object()))
495+
assert isnormal(math.nextafter(0, 1)) is True # issue 946
496+
assert fp.isnormal(math.nextafter(0, 1)) is False
497+
assert fp.isnormal(0.0) is False
498+
assert fp.isnormal(-0.0) is False
499+
assert fp.isnormal(fp.nan) is False
500+
assert fp.isnormal(fp.inf) is False
501+
assert fp.isnormal(fp.ninf) is False
502+
assert fp.isnormal(1.0) is True
503+
assert fp.isnormal(sys.float_info.min) is True
504+
assert fp.isnormal(1+0j) is True
505+
assert fp.isnormal(0j) is False
506+
assert fp.isnormal(-0j) is False
507+
assert fp.isnormal(1+1j) is True
508+
assert fp.isnormal(complex('inf+1j')) is False
499509
assert isint(3) is True
500510
assert isint(0) is True
501511
assert isint(int(3)) is True
@@ -545,12 +555,9 @@ def test_isnan_etc():
545555
assert mp.isnpint(0 + 0.1j) is False
546556
assert mp.isnpint(inf) is False
547557
with pytest.deprecated_call():
548-
for ctx in [mp, fp]:
549-
assert ctx.isnormal(1) is True
550-
assert ctx.isnormal(0.0) is False
551-
assert ctx.isnormal(ctx.mpc(0)) is False
552-
assert ctx.isnormal(ctx.mpc(0, 1)) is True
553-
assert ctx.isnormal(ctx.mpc(1, inf)) is False
558+
assert fp.is_special(1) is False
559+
with pytest.deprecated_call():
560+
assert fp.is_special(0.0) is True
554561

555562

556563
def test_isprime():

0 commit comments

Comments
 (0)