Revert "Add isspecial() method for mp/fp contexts, deprecate isnormal()"

This reverts commit e6aa3b46b1.

Subnormals are properly handled by fp.isnormal().  Deprecate
fp.is_special() method.

Fixes #946
This commit is contained in:
Sergey B Kirpichev
2026-02-23 03:03:10 +03:00
parent c7128a6ac5
commit 7cf16b7828
8 changed files with 95 additions and 113 deletions
+2 -2
View File
@@ -58,7 +58,6 @@ Features:
#936 (Sergey B Kirpichev)
* Use PyREPL, as fallback (no IPython), see #941 (Sergey B Kirpichev)
* Add exp2() and log2(), see #948 (Sergey B Kirpichev)
* Add isspecial() method for contexts, see #949 (Sergey B Kirpichev)
* Support rounding property for the mp context, see #963 (Sergey B Kirpichev)
* Add Fox H-function with rational A/B parameters (foxh()), see #982 (Hongren Zheng)
@@ -79,13 +78,13 @@ Compatibility:
#779, #844 and #845 (Sergey B Kirpichev, Warren Weckesser)
* Deprecate mpmath.math2, see #769 (Sergey B Kirpichev)
* Drop support for CPython 3.8, see #911 (Sergey B Kirpichev)
* Deprecate isnormal() method of contexts, see #949 (Sergey B Kirpichev)
* Importing from the mpmath.libmp submodules is deprecated, use instead ``from
mpmath.libmp import foo``, see
issue https://github.com/mpmath/mpmath/issues/704#issuecomment-2953536980
for available functions (Sergey B Kirpichev)
* Deprecate bitcount function, see #721 and #955 (Sergey B Kirpichev)
* Deprecate mpf/mpc_log, see #989 (Sergey B Kirpichev)
* Deprecate fp.is_special(), see #1042 (Sergey B Kirpichev)
Bug fixes:
@@ -168,6 +167,7 @@ Bug fixes:
* Fix erf(z) with re(z) of large magnitude, see #1039 (Sergey B Kirpichev)
* Return nan's for polylog(s, nan) or polylog(s, nan+nanj),
see #1041 (Sergey B Kirpichev)
* Fix fp.isnormal() for subnormals, see #1042 (Sergey B Kirpichev)
Maintenance:
-1
View File
@@ -75,7 +75,6 @@ multiplicity = mp.multiplicity
isinf = mp.isinf
isnan = mp.isnan
isnormal = mp.isnormal
isspecial = mp.isspecial
isint = mp.isint
isfinite = mp.isfinite
almosteq = mp.almosteq
+9 -8
View File
@@ -2,8 +2,8 @@ import cmath
import functools
import inspect
import math
import sys
import warnings
import sys
from . import function_docs, libfp, libmp
from .ctx_base import StandardBaseContext
@@ -88,8 +88,10 @@ class FPContext(StandardBaseContext):
absmin = absmax = abs
def isspecial(ctx, x):
return not x or x - x != 0.0
def is_special(ctx, x):
warnings.warn("the is_special() method is deprecated",
DeprecationWarning)
return not ctx.isnormal(x)
def isnan(ctx, x):
return x != x
@@ -103,11 +105,10 @@ class FPContext(StandardBaseContext):
return math.isfinite(x)
def isnormal(ctx, x):
warnings.warn("the isnormal() method is deprecated",
DeprecationWarning)
if x:
return x - x == 0.0
return False
if type(x) is complex:
return ctx.isnormal(abs(x))
# XXX: can use math.isnormal() on Python 3.15+
return bool(x) and math.isfinite(x) and abs(x) >= sys.float_info.min
def isnpint(ctx, x):
if type(x) is complex:
+23 -45
View File
@@ -1,7 +1,6 @@
import inspect
import numbers
import sys
import warnings
from . import function_docs
from .libmp import (MPQ, MPZ, ComplexResult, dps_to_prec, finf, fnan, fninf,
@@ -909,8 +908,29 @@ class PythonMPContext:
return ctx.isinf(x)
def isnormal(ctx, x):
warnings.warn("the isnormal() method is deprecated",
DeprecationWarning)
"""
Determine whether *x* is "normal" in the sense of floating-point
representation; that is, return *False* if *x* is zero, an
infinity or NaN; otherwise return *True*. By extension, a
complex number *x* is considered "normal" if its magnitude is
normal::
>>> from mpmath import isnormal, inf, nan, mpc
>>> isnormal(3)
True
>>> isnormal(0)
False
>>> isnormal(inf); isnormal(-inf); isnormal(nan)
False
False
False
>>> isnormal(0+0j)
False
>>> isnormal(0+3j)
True
>>> isnormal(mpc(2,nan))
False
"""
if hasattr(x, "_mpf_"):
if ctx.isfinite(x):
return bool(to_man_exp(x._mpf_, signed=True)[0])
@@ -927,48 +947,6 @@ class PythonMPContext:
x = ctx.convert(x)
return ctx.isnormal(x)
def isspecial(ctx, x):
"""
Determine whether *x* is a "special" in the sense of floating-point
representation; that is, return *True* if *x* is zero, an
infinity or NaN; otherwise return *False*. By extension, a
complex number *x* is considered "special" if its magnitude is
special::
>>> from mpmath import isspecial, inf, nan, mpc
>>> isspecial(3)
False
>>> isspecial(0)
True
>>> isspecial(inf)
True
>>> isspecial(-inf)
True
>>> isspecial(nan)
True
>>> isspecial(0+0j)
True
>>> isspecial(0+3j)
False
>>> isspecial(mpc(2,nan))
True
"""
if hasattr(x, "_mpf_"):
if ctx.isfinite(x):
return not bool(to_man_exp(x._mpf_, signed=True)[0])
return True
if hasattr(x, "_mpc_"):
re, im = x._mpc_
re_special = not bool(re[1])
im_special = not bool(im[1])
if re == fzero: return im_special
if im == fzero: return re_special
return re_special or im_special
if isinstance(x, int_types) or isinstance(x, MPQ):
return not bool(x)
x = ctx.convert(x)
return ctx.isspecial(x)
def isint(ctx, x, gaussian=False):
"""
Return *True* if *x* is integer-valued; otherwise return
+2 -3
View File
@@ -1,7 +1,6 @@
from ..libmp.backend import MPQ
from .functions import defun, defun_wrapped
@defun
def j0(ctx, x):
"""Computes the Bessel function `J_0(x)`. See :func:`~mpmath.besselj`."""
@@ -513,7 +512,7 @@ def airyai(ctx, z, derivative=0, **kwargs):
else:
n = 0
# Values at infinities
if ctx.isspecial(z) and z:
if not ctx.isnormal(z) and z:
if n and ntype == 'Z':
if n == -1:
if z == ctx.inf:
@@ -605,7 +604,7 @@ def airybi(ctx, z, derivative=0, **kwargs):
else:
n = 0
# Values at infinities
if ctx.isspecial(z) and z:
if not ctx.isnormal(z) and z:
if n and ntype == 'Z':
if z == ctx.inf:
return z
+7 -8
View File
@@ -64,7 +64,6 @@ between the various parameters (:func:`~mpmath.qfrom`, :func:`~mpmath.mfrom`,
from .functions import defun, defun_wrapped
@defun_wrapped
def eta(ctx, tau):
r"""
@@ -480,7 +479,7 @@ def RF_calc(ctx, x, y, z, r):
if y == z: return RC_calc(ctx, x, y, r)
if x == z: return RC_calc(ctx, y, x, r)
if x == y: return RC_calc(ctx, z, x, r)
if ctx.isspecial(x) or ctx.isspecial(y) and ctx.isspecial(z):
if not (ctx.isnormal(x) and ctx.isnormal(y) and ctx.isnormal(z)):
if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z):
return x*y*z
if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z):
@@ -510,7 +509,7 @@ def RF_calc(ctx, x, y, z, r):
return ctx.power(Am,-0.5) * (9240-924*E2+385*E2**2+660*E3-630*E2*E3)/9240
def RC_calc(ctx, x, y, r, pv=True):
if ctx.isspecial(x) or ctx.isspecial(y):
if not (ctx.isnormal(x) and ctx.isnormal(y)):
if ctx.isinf(x) or ctx.isinf(y):
return 1/(x*y)
if y == 0:
@@ -550,8 +549,8 @@ def RJ_calc(ctx, x, y, z, p, r, integration):
Carlson's algorithm is correct.
With integration == 2, uses only integration.
"""
if (ctx.isspecial(x) or ctx.isspecial(y)
or ctx.isspecial(z) or ctx.isspecial(p)):
if not (ctx.isnormal(x) and ctx.isnormal(y) and \
ctx.isnormal(z) and ctx.isnormal(p)):
if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z) or ctx.isnan(p):
return x*y*z*p
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):
"""
z = phi
if ctx.isspecial(z) or ctx.isspecial(m):
if not (ctx.isnormal(z) and ctx.isnormal(m)):
if m == 0:
return z + m
if z == 0:
@@ -1302,7 +1301,7 @@ def ellipe(ctx, *args):
else:
phi, m = args
z = phi
if ctx.isspecial(z) or ctx.isspecial(m):
if not (ctx.isnormal(z) and ctx.isnormal(m)):
if m == 0:
return z + m
if z == 0:
@@ -1439,7 +1438,7 @@ def ellippi(ctx, *args):
n, phi, m = args
complete = False
z = phi
if ctx.isspecial(n) or ctx.isspecial(z) or ctx.isspecial(m):
if not (ctx.isnormal(n) and ctx.isnormal(z) and ctx.isnormal(m)):
if ctx.isnan(n) or ctx.isnan(z) or ctx.isnan(m):
raise ValueError
if complete:
+2 -3
View File
@@ -395,9 +395,8 @@ def _lambertw_special(ctx, z, k):
# Some kind of nan or complex inf/nan?
return ctx.ln(z)
import cmath
import math
import cmath
def _lambertw_approx_hybrid(z, k):
imag_sign = 0
@@ -515,7 +514,7 @@ def _lambertw_series(ctx, z, k, tol):
def lambertw(ctx, z, k=0):
z = ctx.convert(z)
k = int(k)
if ctx.isspecial(z):
if not ctx.isnormal(z):
return _lambertw_special(ctx, z, k)
prec = ctx.prec
ctx.prec += 20 + ctx.mag(k or 1)
+50 -43
View File
@@ -3,6 +3,7 @@ import decimal
import math
import operator
import random
import sys
from concurrent.futures import ThreadPoolExecutor
import pytest
@@ -11,9 +12,8 @@ from hypothesis import strategies as st
import mpmath
from mpmath import (ceil, fadd, fdiv, floor, fmul, fneg, fp, frac, fsub, inf,
isinf, isint, isnan, isnormal, isspecial, iv, monitor, mp,
mpc, mpf, mpi, nan, ninf, nint, nint_distance, nstr, pi,
workprec)
isinf, isint, isnan, isnormal, iv, monitor, mp, mpc, mpf,
mpi, nan, ninf, nint, nint_distance, nstr, pi, workprec)
from mpmath.libmp import (MPQ, MPZ, finf, fnan, fninf, fnone, fone, from_float,
from_int, from_pickable, from_str, isprime, mpf_add,
mpf_mul, mpf_sub, round_down, round_nearest,
@@ -462,40 +462,50 @@ def test_isnan_etc():
assert isinf(MPQ(3, 2)) is False
assert isinf(MPQ(0, 1)) is False
pytest.raises(TypeError, lambda: isinf(object()))
assert isspecial(3) is False
assert isspecial(3.5) is False
assert isspecial(mpf(3.5)) is False
assert isspecial(0) is True
assert isspecial(mpf(0)) is True
assert isspecial(0.0) is True
assert isspecial(inf) is True
assert isspecial(-inf) is True
assert isspecial(nan) is True
assert isspecial(float(inf)) is True
assert isspecial(mpc(0, 0)) is True
assert isspecial(mpc(3, 0)) is False
assert isspecial(mpc(0, 3)) is False
assert isspecial(mpc(3, 3)) is False
assert isspecial(mpc(0, nan)) is True
assert isspecial(mpc(0, inf)) is True
assert isspecial(mpc(3, nan)) is True
assert isspecial(mpc(3, inf)) is True
assert isspecial(mpc(3, -inf)) is True
assert isspecial(mpc(nan, 0)) is True
assert isspecial(mpc(inf, 0)) is True
assert isspecial(mpc(nan, 3)) is True
assert isspecial(mpc(inf, 3)) is True
assert isspecial(mpc(inf, nan)) is True
assert isspecial(mpc(nan, inf)) is True
assert isspecial(mpc(nan, nan)) is True
assert isspecial(mpc(inf, inf)) is True
assert isspecial(MPQ(3, 2)) is False
assert isspecial(MPQ(0, 1)) is True
pytest.raises(TypeError, lambda: isspecial(object()))
assert isspecial(5e-324) is False # issue 946
assert fp.isspecial(5e-324) is False
assert fp.isspecial(0.0) is True
assert fp.isspecial(-0.0) is True
assert isnormal(3) is True
assert isnormal(3.5) is True
assert isnormal(mpf(3.5)) is True
assert isnormal(0) is False
assert isnormal(mpf(0)) is False
assert isnormal(0.0) is False
assert isnormal(inf) is False
assert isnormal(-inf) is False
assert isnormal(nan) is False
assert isnormal(float(inf)) is False
assert isnormal(mpc(0, 0)) is False
assert isnormal(mpc(3, 0)) is True
assert isnormal(mpc(0, 3)) is True
assert isnormal(mpc(3, 3)) is True
assert isnormal(mpc(0, nan)) is False
assert isnormal(mpc(0, inf)) is False
assert isnormal(mpc(3, nan)) is False
assert isnormal(mpc(3, inf)) is False
assert isnormal(mpc(3, -inf)) is False
assert isnormal(mpc(nan, 0)) is False
assert isnormal(mpc(inf, 0)) is False
assert isnormal(mpc(nan, 3)) is False
assert isnormal(mpc(inf, 3)) is False
assert isnormal(mpc(inf, nan)) is False
assert isnormal(mpc(nan, inf)) is False
assert isnormal(mpc(nan, nan)) is False
assert isnormal(mpc(inf, inf)) is False
assert isnormal(MPQ(3, 2)) is True
assert isnormal(MPQ(0, 1)) is False
pytest.raises(TypeError, lambda: isnormal(object()))
assert isnormal(math.nextafter(0, 1)) is True # issue 946
assert fp.isnormal(math.nextafter(0, 1)) is False
assert fp.isnormal(0.0) is False
assert fp.isnormal(-0.0) is False
assert fp.isnormal(fp.nan) is False
assert fp.isnormal(fp.inf) is False
assert fp.isnormal(fp.ninf) is False
assert fp.isnormal(1.0) is True
assert fp.isnormal(sys.float_info.min) is True
assert fp.isnormal(1+0j) is True
assert fp.isnormal(0j) is False
assert fp.isnormal(-0j) is False
assert fp.isnormal(1+1j) is True
assert fp.isnormal(complex('inf+1j')) is False
assert isint(3) is True
assert isint(0) is True
assert isint(int(3)) is True
@@ -545,12 +555,9 @@ def test_isnan_etc():
assert mp.isnpint(0 + 0.1j) is False
assert mp.isnpint(inf) is False
with pytest.deprecated_call():
for ctx in [mp, fp]:
assert ctx.isnormal(1) is True
assert ctx.isnormal(0.0) is False
assert ctx.isnormal(ctx.mpc(0)) is False
assert ctx.isnormal(ctx.mpc(0, 1)) is True
assert ctx.isnormal(ctx.mpc(1, inf)) is False
assert fp.is_special(1) is False
with pytest.deprecated_call():
assert fp.is_special(0.0) is True
def test_isprime():