From 9d14505ba38a19c1498e6cb299de209d2d0ab62c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 25 Apr 2025 16:12:10 +0300 Subject: [PATCH 1/8] gh-132908: add math.normal/subnormal() functions --- Doc/library/math.rst | 19 +++++ Doc/whatsnew/3.14.rst | 5 +- Lib/test/test_math.py | 22 ++++++ ...-04-25-16-06-53.gh-issue-132908.wV5rja.rst | 2 + Modules/clinic/mathmodule.c.h | 70 ++++++++++++++++++- Modules/mathmodule.c | 37 ++++++++++ 6 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 0749367045dfa9..fcf772a4ce820c 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -53,6 +53,8 @@ noted otherwise, all return values are floats. :func:`frexp(x) ` Mantissa and exponent of *x* :func:`isclose(a, b, rel_tol, abs_tol) ` Check if the values *a* and *b* are close to each other :func:`isfinite(x) ` Check if *x* is neither an infinity nor a NaN +:func:`isnormal(x) ` Check if *x* is a normal floating-point number +:func:`issubnormal(x) ` Check if *x* is a subnormal floating-point number :func:`isinf(x) ` Check if *x* is a positive or negative infinity :func:`isnan(x) ` Check if *x* is a NaN (not a number) :func:`ldexp(x, i) ` ``x * (2**i)``, inverse of function :func:`frexp` @@ -374,6 +376,23 @@ Floating point manipulation functions .. versionadded:: 3.2 +.. function:: isnormal(x) + + Return ``True`` if *x* is a normal floating-point number, i.e. a finite + nonzero number, that is not a subnormal. Return ``False`` otherwise. + + .. versionadded:: next + + +.. function:: issubnormal(x) + + Return ``True`` if *x* is a subnormal floating-point number, i.e. a finite + nonzero number with a magnitude smaller than the smallest positive normal + number, see :data:`sys.float_info.min`. Return ``False`` otherwise. + + .. versionadded:: next + + .. function:: isinf(x) Return ``True`` if *x* is a positive or negative infinity, and diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 3893060b153210..55bdbf14dfbe74 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -950,7 +950,10 @@ math ---- * Added more detailed error messages for domain errors in the module. - (Contributed by by Charlie Zhao and Sergey B Kirpichev in :gh:`101410`.) + (Contributed by Charlie Zhao and Sergey B Kirpichev in :gh:`101410`.) + +* Add :func:`math.isnormal` and :func:`math.issubnormal` function. + (Contributed by Sergey B Kirpichev in :gh:`132908`.) mimetypes diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 913a60bf9e04e3..fa53a455ee7ee3 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -1967,6 +1967,28 @@ def testIsfinite(self): self.assertFalse(math.isfinite(float("inf"))) self.assertFalse(math.isfinite(float("-inf"))) + def testIsnormal(self): + self.assertTrue(math.isnormal(1.25)) + self.assertTrue(math.isnormal(-1.0)) + self.assertFalse(math.isnormal(0.0)) + self.assertFalse(math.isnormal(-0.0)) + self.assertFalse(math.isnormal(INF)) + self.assertFalse(math.isnormal(NINF)) + self.assertFalse(math.isnormal(NAN)) + self.assertFalse(math.isnormal(FLOAT_MIN/2)) + self.assertFalse(math.isnormal(-FLOAT_MIN/2)) + + def testIsnormal(self): + self.assertFalse(math.issubnormal(1.25)) + self.assertFalse(math.issubnormal(-1.0)) + self.assertFalse(math.issubnormal(0.0)) + self.assertFalse(math.issubnormal(-0.0)) + self.assertFalse(math.issubnormal(INF)) + self.assertFalse(math.issubnormal(NINF)) + self.assertFalse(math.issubnormal(NAN)) + self.assertTrue(math.issubnormal(FLOAT_MIN/2)) + self.assertTrue(math.issubnormal(-FLOAT_MIN/2)) + def testIsnan(self): self.assertTrue(math.isnan(float("nan"))) self.assertTrue(math.isnan(float("-nan"))) diff --git a/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst b/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst new file mode 100644 index 00000000000000..58c13ff92244dc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst @@ -0,0 +1,2 @@ +Add :func:`math.isnormal` and :func:`math.issubnormal` function. Patch by +Sergey B Kirpichev. diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index 4c2c8acd8f69d8..1a4ccf2c3f7321 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -630,6 +630,74 @@ math_isfinite(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(math_isnormal__doc__, +"isnormal($module, x, /)\n" +"--\n" +"\n" +"Return True if x is normal, and False otherwise."); + +#define MATH_ISNORMAL_METHODDEF \ + {"isnormal", (PyCFunction)math_isnormal, METH_O, math_isnormal__doc__}, + +static PyObject * +math_isnormal_impl(PyObject *module, double x); + +static PyObject * +math_isnormal(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + double x; + + if (PyFloat_CheckExact(arg)) { + x = PyFloat_AS_DOUBLE(arg); + } + else + { + x = PyFloat_AsDouble(arg); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + return_value = math_isnormal_impl(module, x); + +exit: + return return_value; +} + +PyDoc_STRVAR(math_issubnormal__doc__, +"issubnormal($module, x, /)\n" +"--\n" +"\n" +"Return True if x is subnormal, and False otherwise."); + +#define MATH_ISSUBNORMAL_METHODDEF \ + {"issubnormal", (PyCFunction)math_issubnormal, METH_O, math_issubnormal__doc__}, + +static PyObject * +math_issubnormal_impl(PyObject *module, double x); + +static PyObject * +math_issubnormal(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + double x; + + if (PyFloat_CheckExact(arg)) { + x = PyFloat_AS_DOUBLE(arg); + } + else + { + x = PyFloat_AsDouble(arg); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + return_value = math_issubnormal_impl(module, x); + +exit: + return return_value; +} + PyDoc_STRVAR(math_isnan__doc__, "isnan($module, x, /)\n" "--\n" @@ -1112,4 +1180,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=634773bd18cd3f93 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d3f01e9bf48481e4 input=a9049054013a1b77]*/ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 11d9b7418a25a2..d8d94fcbf1a5b2 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3098,6 +3098,41 @@ math_isfinite_impl(PyObject *module, double x) return PyBool_FromLong((long)isfinite(x)); } +/*[clinic input] +math.isnormal + + x: double + / + +Return True if x is normal, and False otherwise. +[clinic start generated code]*/ + +static PyObject * +math_isnormal_impl(PyObject *module, double x) +/*[clinic end generated code: output=c7b302b5b89c3541 input=fdaa00c58aa7bc17]*/ +{ + return PyBool_FromLong((long)isnormal(x)); +} + +/*[clinic input] +math.issubnormal + + x: double + / + +Return True if x is subnormal, and False otherwise. +[clinic start generated code]*/ + +static PyObject * +math_issubnormal_impl(PyObject *module, double x) +/*[clinic end generated code: output=4e76ac98ddcae761 input=9a20aba7107d0d95]*/ +{ +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L + return PyBool_FromLong((long)issubnormal(x)); +#else + return PyBool_FromLong((long)(isfinite(x) && x && !isnormal(x))); +#endif +} /*[clinic input] math.isnan @@ -4126,6 +4161,8 @@ static PyMethodDef math_methods[] = { MATH_HYPOT_METHODDEF MATH_ISCLOSE_METHODDEF MATH_ISFINITE_METHODDEF + MATH_ISNORMAL_METHODDEF + MATH_ISSUBNORMAL_METHODDEF MATH_ISINF_METHODDEF MATH_ISNAN_METHODDEF MATH_ISQRT_METHODDEF From 7350b258a56db0c92c26201e1c7066d754b5ac84 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 25 Apr 2025 16:18:21 +0300 Subject: [PATCH 2/8] oops, test name --- Lib/test/test_math.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index fa53a455ee7ee3..248360f0d62c6f 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -1978,7 +1978,7 @@ def testIsnormal(self): self.assertFalse(math.isnormal(FLOAT_MIN/2)) self.assertFalse(math.isnormal(-FLOAT_MIN/2)) - def testIsnormal(self): + def testIssubnormal(self): self.assertFalse(math.issubnormal(1.25)) self.assertFalse(math.issubnormal(-1.0)) self.assertFalse(math.issubnormal(0.0)) From 4eb36698b3023a44f4f78bc25b7450a36cae2dd4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 25 Apr 2025 17:13:18 +0300 Subject: [PATCH 3/8] Apply suggestions from code review Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Doc/library/math.rst | 4 ++-- Doc/whatsnew/3.14.rst | 2 +- .../Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index fcf772a4ce820c..d6e65d25c980da 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -378,7 +378,7 @@ Floating point manipulation functions .. function:: isnormal(x) - Return ``True`` if *x* is a normal floating-point number, i.e. a finite + Return ``True`` if *x* is a normal floating-point number, that is a finite nonzero number, that is not a subnormal. Return ``False`` otherwise. .. versionadded:: next @@ -386,7 +386,7 @@ Floating point manipulation functions .. function:: issubnormal(x) - Return ``True`` if *x* is a subnormal floating-point number, i.e. a finite + Return ``True`` if *x* is a subnormal floating-point number, that is a finite nonzero number with a magnitude smaller than the smallest positive normal number, see :data:`sys.float_info.min`. Return ``False`` otherwise. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 55bdbf14dfbe74..2cad28c6498f98 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -952,7 +952,7 @@ math * Added more detailed error messages for domain errors in the module. (Contributed by Charlie Zhao and Sergey B Kirpichev in :gh:`101410`.) -* Add :func:`math.isnormal` and :func:`math.issubnormal` function. +* Add :func:`math.isnormal` and :func:`math.issubnormal` functions. (Contributed by Sergey B Kirpichev in :gh:`132908`.) diff --git a/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst b/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst index 58c13ff92244dc..e33b061bb9ba1f 100644 --- a/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst +++ b/Misc/NEWS.d/next/Library/2025-04-25-16-06-53.gh-issue-132908.wV5rja.rst @@ -1,2 +1,2 @@ -Add :func:`math.isnormal` and :func:`math.issubnormal` function. Patch by +Add :func:`math.isnormal` and :func:`math.issubnormal` functions. Patch by Sergey B Kirpichev. From 37b3345287396a2213f5be093a227515ad1de607 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 25 Apr 2025 17:45:06 +0300 Subject: [PATCH 4/8] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/math.rst | 3 ++- Modules/mathmodule.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index d6e65d25c980da..390887782624f2 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -379,7 +379,8 @@ Floating point manipulation functions .. function:: isnormal(x) Return ``True`` if *x* is a normal floating-point number, that is a finite - nonzero number, that is not a subnormal. Return ``False`` otherwise. + nonzero number that is not a subnormal (see :func:`issubnormal`). + Return ``False`` otherwise. .. versionadded:: next diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index d8d94fcbf1a5b2..dd58e646f291c4 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3127,7 +3127,7 @@ static PyObject * math_issubnormal_impl(PyObject *module, double x) /*[clinic end generated code: output=4e76ac98ddcae761 input=9a20aba7107d0d95]*/ { -#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L return PyBool_FromLong((long)issubnormal(x)); #else return PyBool_FromLong((long)(isfinite(x) && x && !isnormal(x))); From e318087466295ff2816d84e62becf2d0810e9f68 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 25 Apr 2025 17:54:44 +0300 Subject: [PATCH 5/8] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/mathmodule.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index dd58e646f291c4..739ddcdc242695 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3098,6 +3098,7 @@ math_isfinite_impl(PyObject *module, double x) return PyBool_FromLong((long)isfinite(x)); } + /*[clinic input] math.isnormal @@ -3114,6 +3115,7 @@ math_isnormal_impl(PyObject *module, double x) return PyBool_FromLong((long)isnormal(x)); } + /*[clinic input] math.issubnormal @@ -3134,6 +3136,7 @@ math_issubnormal_impl(PyObject *module, double x) #endif } + /*[clinic input] math.isnan From dc88e9e863675df855113c0a4e565b2a36d0902b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 8 May 2025 15:29:34 +0300 Subject: [PATCH 6/8] -> 3.15 --- Doc/whatsnew/3.14.rst | 3 --- Doc/whatsnew/3.15.rst | 7 +++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2b04b60f1d2399..f9754be20956f4 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1368,9 +1368,6 @@ math * Added more detailed error messages for domain errors in the module. (Contributed by Charlie Zhao and Sergey B Kirpichev in :gh:`101410`.) -* Add :func:`math.isnormal` and :func:`math.issubnormal` functions. - (Contributed by Sergey B Kirpichev in :gh:`132908`.) - mimetypes --------- diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 5e9922069aa42c..ee59b39ce60ebb 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -86,6 +86,13 @@ New modules Improved modules ================ +math +---- + +* Add :func:`math.isnormal` and :func:`math.issubnormal` functions. + (Contributed by Sergey B Kirpichev in :gh:`132908`.) + + module_name ----------- From 066e8841a30b2f6468d4405046806b8def6835fe Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 2 Jun 2025 11:19:39 +0300 Subject: [PATCH 7/8] Apply suggestions from code review --- Doc/library/math.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 6393b774ecb0db..c8061fb16380cd 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -53,8 +53,8 @@ noted otherwise, all return values are floats. :func:`frexp(x) ` Mantissa and exponent of *x* :func:`isclose(a, b, rel_tol, abs_tol) ` Check if the values *a* and *b* are close to each other :func:`isfinite(x) ` Check if *x* is neither an infinity nor a NaN -:func:`isnormal(x) ` Check if *x* is a normal floating-point number -:func:`issubnormal(x) ` Check if *x* is a subnormal floating-point number +:func:`isnormal(x) ` Check if *x* is a normal number +:func:`issubnormal(x) ` Check if *x* is a subnormal number :func:`isinf(x) ` Check if *x* is a positive or negative infinity :func:`isnan(x) ` Check if *x* is a NaN (not a number) :func:`ldexp(x, i) ` ``x * (2**i)``, inverse of function :func:`frexp` @@ -377,7 +377,7 @@ Floating point manipulation functions .. function:: isnormal(x) - Return ``True`` if *x* is a normal floating-point number, that is a finite + Return ``True`` if *x* is a normal number, that is a finite nonzero number that is not a subnormal (see :func:`issubnormal`). Return ``False`` otherwise. @@ -386,7 +386,7 @@ Floating point manipulation functions .. function:: issubnormal(x) - Return ``True`` if *x* is a subnormal floating-point number, that is a finite + Return ``True`` if *x* is a subnormal number, that is a finite nonzero number with a magnitude smaller than the smallest positive normal number, see :data:`sys.float_info.min`. Return ``False`` otherwise. From 08f81f5d08ed8b7c0148736bf08e49f4ca8b7615 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 2 Jun 2025 11:35:16 +0300 Subject: [PATCH 8/8] address review: remove (long) casts --- Modules/mathmodule.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 256180c3a3e178..bbbb49115681de 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -3131,7 +3131,7 @@ static PyObject * math_isnormal_impl(PyObject *module, double x) /*[clinic end generated code: output=c7b302b5b89c3541 input=fdaa00c58aa7bc17]*/ { - return PyBool_FromLong((long)isnormal(x)); + return PyBool_FromLong(isnormal(x)); } @@ -3149,9 +3149,9 @@ math_issubnormal_impl(PyObject *module, double x) /*[clinic end generated code: output=4e76ac98ddcae761 input=9a20aba7107d0d95]*/ { #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L - return PyBool_FromLong((long)issubnormal(x)); + return PyBool_FromLong(issubnormal(x)); #else - return PyBool_FromLong((long)(isfinite(x) && x && !isnormal(x))); + return PyBool_FromLong(isfinite(x) && x && !isnormal(x)); #endif }