[Python-checkins] gh-104263: Rely on Py_NAN and introduce Py_INFINITY (GH-104202)

mdickinson webhook-mailer at python.org
Wed May 10 12:45:14 EDT 2023


https://github.com/python/cpython/commit/7a3b03509e5e3e72d8c47137579cccb52548a318
commit: 7a3b03509e5e3e72d8c47137579cccb52548a318
branch: main
author: Sebastian Berg <sebastianb at nvidia.com>
committer: mdickinson <dickinsm at gmail.com>
date: 2023-05-10T17:44:52+01:00
summary:

gh-104263: Rely on Py_NAN and introduce Py_INFINITY (GH-104202)

This PR removes `_Py_dg_stdnan` and `_Py_dg_infinity` in favour of
using the standard `NAN` and `INFINITY` macros provided by C99.
This change has the side-effect of fixing a bug on MIPS where the
hard-coded value used by `_Py_dg_stdnan` gave a signalling NaN
rather than a quiet NaN.
---------

Co-authored-by: Mark Dickinson <dickinsm at gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2023-05-08-10-34-55.gh-issue-104263.ctHWI8.rst
M Include/internal/pycore_dtoa.h
M Include/pymath.h
M Lib/test/test_cmath.py
M Lib/test/test_complex.py
M Lib/test/test_float.py
M Lib/test/test_math.py
M Modules/cmathmodule.c
M Modules/mathmodule.c
M Objects/floatobject.c
M Python/dtoa.c
M Python/pystrtod.c

diff --git a/Include/internal/pycore_dtoa.h b/Include/internal/pycore_dtoa.h
index fb524770efed..4d9681d59a64 100644
--- a/Include/internal/pycore_dtoa.h
+++ b/Include/internal/pycore_dtoa.h
@@ -64,8 +64,6 @@ PyAPI_FUNC(double) _Py_dg_strtod(const char *str, char **ptr);
 PyAPI_FUNC(char *) _Py_dg_dtoa(double d, int mode, int ndigits,
                         int *decpt, int *sign, char **rve);
 PyAPI_FUNC(void) _Py_dg_freedtoa(char *s);
-PyAPI_FUNC(double) _Py_dg_stdnan(int sign);
-PyAPI_FUNC(double) _Py_dg_infinity(int sign);
 
 #endif // _PY_SHORT_FLOAT_REPR == 1
 
diff --git a/Include/pymath.h b/Include/pymath.h
index 772b67e49775..4c1e3d998489 100644
--- a/Include/pymath.h
+++ b/Include/pymath.h
@@ -39,27 +39,24 @@
 // Return 1 if float or double arg is neither infinite nor NAN, else 0.
 #define Py_IS_FINITE(X) isfinite(X)
 
-/* HUGE_VAL is supposed to expand to a positive double infinity.  Python
- * uses Py_HUGE_VAL instead because some platforms are broken in this
- * respect.  We used to embed code in pyport.h to try to worm around that,
- * but different platforms are broken in conflicting ways.  If you're on
- * a platform where HUGE_VAL is defined incorrectly, fiddle your Python
- * config to #define Py_HUGE_VAL to something that works on your platform.
+// Py_INFINITY: Value that evaluates to a positive double infinity.
+#ifndef Py_INFINITY
+#  define Py_INFINITY ((double)INFINITY)
+#endif
+
+/* Py_HUGE_VAL should always be the same as Py_INFINITY.  But historically
+ * this was not reliable and Python did not require IEEE floats and C99
+ * conformity.  Prefer Py_INFINITY for new code.
  */
 #ifndef Py_HUGE_VAL
 #  define Py_HUGE_VAL HUGE_VAL
 #endif
 
-// Py_NAN: Value that evaluates to a quiet Not-a-Number (NaN).
+/* Py_NAN: Value that evaluates to a quiet Not-a-Number (NaN).  The sign is
+ * undefined and normally not relevant, but e.g. fixed for float("nan").
+ */
 #if !defined(Py_NAN)
-#  if _Py__has_builtin(__builtin_nan)
-     // Built-in implementation of the ISO C99 function nan(): quiet NaN.
-#    define Py_NAN (__builtin_nan(""))
-#else
-     // Use C99 NAN constant: quiet Not-A-Number.
-     // NAN is a float, Py_NAN is a double: cast to double.
 #    define Py_NAN ((double)NAN)
-#  endif
 #endif
 
 #endif /* Py_PYMATH_H */
diff --git a/Lib/test/test_cmath.py b/Lib/test/test_cmath.py
index cd2c6939105d..57f80d5d8cd0 100644
--- a/Lib/test/test_cmath.py
+++ b/Lib/test/test_cmath.py
@@ -166,6 +166,11 @@ def test_infinity_and_nan_constants(self):
         self.assertEqual(cmath.nan.imag, 0.0)
         self.assertEqual(cmath.nanj.real, 0.0)
         self.assertTrue(math.isnan(cmath.nanj.imag))
+        # Also check that the sign of all of these is positive:
+        self.assertEqual(math.copysign(1., cmath.nan.real), 1.)
+        self.assertEqual(math.copysign(1., cmath.nan.imag), 1.)
+        self.assertEqual(math.copysign(1., cmath.nanj.real), 1.)
+        self.assertEqual(math.copysign(1., cmath.nanj.imag), 1.)
 
         # Check consistency with reprs.
         self.assertEqual(repr(cmath.inf), "inf")
diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py
index 51ba151505fb..9180cca62b28 100644
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -529,6 +529,12 @@ class complex2(complex):
                     self.assertFloatsAreIdentical(z.real, x)
                     self.assertFloatsAreIdentical(z.imag, y)
 
+    def test_constructor_negative_nans_from_string(self):
+        self.assertEqual(copysign(1., complex("-nan").real), -1.)
+        self.assertEqual(copysign(1., complex("-nanj").imag), -1.)
+        self.assertEqual(copysign(1., complex("-nan-nanj").real), -1.)
+        self.assertEqual(copysign(1., complex("-nan-nanj").imag), -1.)
+
     def test_underscores(self):
         # check underscores
         for lit in VALID_UNDERSCORE_LITERALS:
@@ -569,6 +575,7 @@ def test(v, expected, test_fn=self.assertEqual):
         test(complex(NAN, 1), "(nan+1j)")
         test(complex(1, NAN), "(1+nanj)")
         test(complex(NAN, NAN), "(nan+nanj)")
+        test(complex(-NAN, -NAN), "(nan+nanj)")
 
         test(complex(0, INF), "infj")
         test(complex(0, -INF), "-infj")
diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index f8350c1e4caa..c4ee1e08251d 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -1040,11 +1040,8 @@ def test_inf_signs(self):
         self.assertEqual(copysign(1.0, float('inf')), 1.0)
         self.assertEqual(copysign(1.0, float('-inf')), -1.0)
 
-    @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
-                         "applies only when using short float repr style")
     def test_nan_signs(self):
-        # When using the dtoa.c code, the sign of float('nan') should
-        # be predictable.
+        # The sign of float('nan') should be predictable.
         self.assertEqual(copysign(1.0, float('nan')), 1.0)
         self.assertEqual(copysign(1.0, float('-nan')), -1.0)
 
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index 433161c2dd41..f282434c9a33 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -1881,11 +1881,11 @@ def testIsinf(self):
         self.assertFalse(math.isinf(0.))
         self.assertFalse(math.isinf(1.))
 
-    @requires_IEEE_754
     def test_nan_constant(self):
+        # `math.nan` must be a quiet NaN with positive sign bit
         self.assertTrue(math.isnan(math.nan))
+        self.assertEqual(math.copysign(1., math.nan), 1.)
 
-    @requires_IEEE_754
     def test_inf_constant(self):
         self.assertTrue(math.isinf(math.inf))
         self.assertGreater(math.inf, 0.0)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-08-10-34-55.gh-issue-104263.ctHWI8.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-08-10-34-55.gh-issue-104263.ctHWI8.rst
new file mode 100644
index 000000000000..342467cfcd4e
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-08-10-34-55.gh-issue-104263.ctHWI8.rst	
@@ -0,0 +1,6 @@
+Fix ``float("nan")`` to produce a quiet NaN on platforms (like MIPS) where
+the meaning of the signalling / quiet bit is inverted from its usual
+meaning. Also introduce a new macro ``Py_INFINITY`` matching C99's
+``INFINITY``, and refactor internals to rely on C99's ``NAN`` and
+``INFINITY`` macros instead of hard-coding bit patterns for infinities and
+NaNs. Thanks Sebastian Berg.
diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c
index 914a697f8e17..1a31bdc824bb 100644
--- a/Modules/cmathmodule.c
+++ b/Modules/cmathmodule.c
@@ -8,7 +8,6 @@
 
 #include "Python.h"
 #include "pycore_pymath.h"        // _PY_SHORT_FLOAT_REPR
-#include "pycore_dtoa.h"          // _Py_dg_stdnan()
 /* we need DBL_MAX, DBL_MIN, DBL_EPSILON, DBL_MANT_DIG and FLT_RADIX from
    float.h.  We assume that FLT_RADIX is either 2 or 16. */
 #include <float.h>
@@ -88,53 +87,6 @@ else {
 #endif
 #define CM_SCALE_DOWN (-(CM_SCALE_UP+1)/2)
 
-/* Constants cmath.inf, cmath.infj, cmath.nan, cmath.nanj.
-   cmath.nan and cmath.nanj are defined only when either
-   _PY_SHORT_FLOAT_REPR is 1 (which should be
-   the most common situation on machines using an IEEE 754
-   representation), or Py_NAN is defined. */
-
-static double
-m_inf(void)
-{
-#if _PY_SHORT_FLOAT_REPR == 1
-    return _Py_dg_infinity(0);
-#else
-    return Py_HUGE_VAL;
-#endif
-}
-
-static Py_complex
-c_infj(void)
-{
-    Py_complex r;
-    r.real = 0.0;
-    r.imag = m_inf();
-    return r;
-}
-
-#if _PY_SHORT_FLOAT_REPR == 1
-
-static double
-m_nan(void)
-{
-#if _PY_SHORT_FLOAT_REPR == 1
-    return _Py_dg_stdnan(0);
-#else
-    return Py_NAN;
-#endif
-}
-
-static Py_complex
-c_nanj(void)
-{
-    Py_complex r;
-    r.real = 0.0;
-    r.imag = m_nan();
-    return r;
-}
-
-#endif
 
 /* forward declarations */
 static Py_complex cmath_asinh_impl(PyObject *, Py_complex);
@@ -1274,23 +1226,22 @@ cmath_exec(PyObject *mod)
     if (PyModule_AddObject(mod, "tau", PyFloat_FromDouble(Py_MATH_TAU)) < 0) {
         return -1;
     }
-    if (PyModule_AddObject(mod, "inf", PyFloat_FromDouble(m_inf())) < 0) {
+    if (PyModule_AddObject(mod, "inf", PyFloat_FromDouble(Py_INFINITY)) < 0) {
         return -1;
     }
 
+    Py_complex infj = {0.0, Py_INFINITY};
     if (PyModule_AddObject(mod, "infj",
-                           PyComplex_FromCComplex(c_infj())) < 0) {
+                           PyComplex_FromCComplex(infj)) < 0) {
         return -1;
     }
-#if _PY_SHORT_FLOAT_REPR == 1
-    if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(m_nan())) < 0) {
+    if (PyModule_AddObject(mod, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) {
         return -1;
     }
-    if (PyModule_AddObject(mod, "nanj",
-                           PyComplex_FromCComplex(c_nanj())) < 0) {
+    Py_complex nanj = {0.0, fabs(Py_NAN)};
+    if (PyModule_AddObject(mod, "nanj", PyComplex_FromCComplex(nanj)) < 0) {
         return -1;
     }
-#endif
 
     /* initialize special value tables */
 
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 3737a9654575..f369b2c45ce3 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -59,7 +59,6 @@ raised for division by zero and mod by zero.
 #include "Python.h"
 #include "pycore_bitutils.h"      // _Py_bit_length()
 #include "pycore_call.h"          // _PyObject_CallNoArgs()
-#include "pycore_dtoa.h"          // _Py_dg_infinity()
 #include "pycore_long.h"          // _PyLong_GetZero()
 #include "pycore_moduleobject.h"  // _PyModule_GetState()
 #include "pycore_object.h"        // _PyObject_LookupSpecial()
@@ -389,34 +388,6 @@ lanczos_sum(double x)
     return num/den;
 }
 
-/* Constant for +infinity, generated in the same way as float('inf'). */
-
-static double
-m_inf(void)
-{
-#if _PY_SHORT_FLOAT_REPR == 1
-    return _Py_dg_infinity(0);
-#else
-    return Py_HUGE_VAL;
-#endif
-}
-
-/* Constant nan value, generated in the same way as float('nan'). */
-/* We don't currently assume that Py_NAN is defined everywhere. */
-
-#if _PY_SHORT_FLOAT_REPR == 1
-
-static double
-m_nan(void)
-{
-#if _PY_SHORT_FLOAT_REPR == 1
-    return _Py_dg_stdnan(0);
-#else
-    return Py_NAN;
-#endif
-}
-
-#endif
 
 static double
 m_tgamma(double x)
@@ -435,7 +406,7 @@ m_tgamma(double x)
     if (x == 0.0) {
         errno = EDOM;
         /* tgamma(+-0.0) = +-inf, divide-by-zero */
-        return copysign(Py_HUGE_VAL, x);
+        return copysign(Py_INFINITY, x);
     }
 
     /* integer arguments */
@@ -3938,7 +3909,7 @@ math_ulp_impl(PyObject *module, double x)
     if (Py_IS_INFINITY(x)) {
         return x;
     }
-    double inf = m_inf();
+    double inf = Py_INFINITY;
     double x2 = nextafter(x, inf);
     if (Py_IS_INFINITY(x2)) {
         /* special case: x is the largest positive representable float */
@@ -3975,14 +3946,12 @@ math_exec(PyObject *module)
     if (PyModule_AddObject(module, "tau", PyFloat_FromDouble(Py_MATH_TAU)) < 0) {
         return -1;
     }
-    if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(m_inf())) < 0) {
+    if (PyModule_AddObject(module, "inf", PyFloat_FromDouble(Py_INFINITY)) < 0) {
         return -1;
     }
-#if _PY_SHORT_FLOAT_REPR == 1
-    if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(m_nan())) < 0) {
+    if (PyModule_AddObject(module, "nan", PyFloat_FromDouble(fabs(Py_NAN))) < 0) {
         return -1;
     }
-#endif
     return 0;
 }
 
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index d257857d9c61..83a263c0d9c6 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -2424,25 +2424,14 @@ PyFloat_Unpack2(const char *data, int le)
     f |= *p;
 
     if (e == 0x1f) {
-#if _PY_SHORT_FLOAT_REPR == 0
         if (f == 0) {
             /* Infinity */
             return sign ? -Py_HUGE_VAL : Py_HUGE_VAL;
         }
         else {
             /* NaN */
-            return sign ? -Py_NAN : Py_NAN;
+            return sign ? -fabs(Py_NAN) : fabs(Py_NAN);
         }
-#else  // _PY_SHORT_FLOAT_REPR == 1
-        if (f == 0) {
-            /* Infinity */
-            return _Py_dg_infinity(sign);
-        }
-        else {
-            /* NaN */
-            return _Py_dg_stdnan(sign);
-        }
-#endif  // _PY_SHORT_FLOAT_REPR == 1
     }
 
     x = (double)f / 1024.0;
diff --git a/Python/dtoa.c b/Python/dtoa.c
index 6ea60ac9946e..c5e343b82f74 100644
--- a/Python/dtoa.c
+++ b/Python/dtoa.c
@@ -273,11 +273,6 @@ typedef union { double d; ULong L[2]; } U;
 #define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1))
 #define Big1 0xffffffff
 
-/* Standard NaN used by _Py_dg_stdnan. */
-
-#define NAN_WORD0 0x7ff80000
-#define NAN_WORD1 0
-
 /* Bits of the representation of positive infinity. */
 
 #define POSINF_WORD0 0x7ff00000
@@ -1399,35 +1394,6 @@ bigcomp(U *rv, const char *s0, BCinfo *bc)
     return 0;
 }
 
-/* Return a 'standard' NaN value.
-
-   There are exactly two quiet NaNs that don't arise by 'quieting' signaling
-   NaNs (see IEEE 754-2008, section 6.2.1).  If sign == 0, return the one whose
-   sign bit is cleared.  Otherwise, return the one whose sign bit is set.
-*/
-
-double
-_Py_dg_stdnan(int sign)
-{
-    U rv;
-    word0(&rv) = NAN_WORD0;
-    word1(&rv) = NAN_WORD1;
-    if (sign)
-        word0(&rv) |= Sign_bit;
-    return dval(&rv);
-}
-
-/* Return positive or negative infinity, according to the given sign (0 for
- * positive infinity, 1 for negative infinity). */
-
-double
-_Py_dg_infinity(int sign)
-{
-    U rv;
-    word0(&rv) = POSINF_WORD0;
-    word1(&rv) = POSINF_WORD1;
-    return sign ? -dval(&rv) : dval(&rv);
-}
 
 double
 _Py_dg_strtod(const char *s00, char **se)
diff --git a/Python/pystrtod.c b/Python/pystrtod.c
index d77b846f0403..9bb060e3d119 100644
--- a/Python/pystrtod.c
+++ b/Python/pystrtod.c
@@ -23,44 +23,6 @@ case_insensitive_match(const char *s, const char *t)
    return the NaN or Infinity as a double and set *endptr to point just beyond
    the successfully parsed portion of the string.  On failure, return -1.0 and
    set *endptr to point to the start of the string. */
-
-#if _PY_SHORT_FLOAT_REPR == 1
-
-double
-_Py_parse_inf_or_nan(const char *p, char **endptr)
-{
-    double retval;
-    const char *s;
-    int negate = 0;
-
-    s = p;
-    if (*s == '-') {
-        negate = 1;
-        s++;
-    }
-    else if (*s == '+') {
-        s++;
-    }
-    if (case_insensitive_match(s, "inf")) {
-        s += 3;
-        if (case_insensitive_match(s, "inity"))
-            s += 5;
-        retval = _Py_dg_infinity(negate);
-    }
-    else if (case_insensitive_match(s, "nan")) {
-        s += 3;
-        retval = _Py_dg_stdnan(negate);
-    }
-    else {
-        s = p;
-        retval = -1.0;
-    }
-    *endptr = (char *)s;
-    return retval;
-}
-
-#else
-
 double
 _Py_parse_inf_or_nan(const char *p, char **endptr)
 {
@@ -84,7 +46,7 @@ _Py_parse_inf_or_nan(const char *p, char **endptr)
     }
     else if (case_insensitive_match(s, "nan")) {
         s += 3;
-        retval = negate ? -Py_NAN : Py_NAN;
+        retval = negate ? -fabs(Py_NAN) : fabs(Py_NAN);
     }
     else {
         s = p;
@@ -94,7 +56,6 @@ _Py_parse_inf_or_nan(const char *p, char **endptr)
     return retval;
 }
 
-#endif
 
 /**
  * _PyOS_ascii_strtod:



More information about the Python-checkins mailing list