https://github.com/python/cpython/commit/ad2f0884b16e6af4087ba078d2255d4c81ae8e96
commit: ad2f0884b16e6af4087ba078d2255d4c81ae8e96
branch: main
author: Sergey B Kirpichev <skirpic...@gmail.com>
committer: vstinner <vstin...@python.org>
date: 2025-05-01T16:20:36+02:00
summary:

gh-130317: Fix test_pack_unpack_roundtrip() and add docs (#133204)

* Skip sNaN's testing in 32-bit mode.
* Drop float_set_snan() helper.
* Use memcpy() workaround for sNaN's in PyFloat_Unpack4().
* Document, that sNaN's may not be preserved by PyFloat_Pack/Unpack API.

files:
M Doc/c-api/float.rst
M Lib/test/test_capi/test_float.py
M Modules/_testcapi/clinic/float.c.h
M Modules/_testcapi/float.c
M Objects/floatobject.c

diff --git a/Doc/c-api/float.rst b/Doc/c-api/float.rst
index 1da37a5bcaeef9..c5a7653efca26b 100644
--- a/Doc/c-api/float.rst
+++ b/Doc/c-api/float.rst
@@ -96,6 +96,9 @@ NaNs (if such things exist on the platform) isn't handled 
correctly, and
 attempting to unpack a bytes string containing an IEEE INF or NaN will raise an
 exception.
 
+Note that NaNs type may not be preserved on IEEE platforms (silent NaN become
+quiet), for example on x86 systems in 32-bit mode.
+
 On non-IEEE platforms with more precision, or larger dynamic range, than IEEE
 754 supports, not all values can be packed; on non-IEEE platforms with less
 precision, or smaller dynamic range, not all values can be unpacked. What
diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py
index 44570bf6379565..c832207fef0c1a 100644
--- a/Lib/test/test_capi/test_float.py
+++ b/Lib/test/test_capi/test_float.py
@@ -180,12 +180,6 @@ def test_pack_unpack_roundtrip(self):
                             self.assertEqual(value2, value)
 
     @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754")
-    # Skip on x86 (32-bit), since these tests fail. The problem is that sNaN
-    # doubles become qNaN doubles just by the C calling convention, there is no
-    # way to preserve sNaN doubles between C function calls. But tests pass
-    # on Windows x86.
-    @unittest.skipIf((sys.maxsize == 2147483647) and not(sys.platform == 
'win32'),
-                     'test fails on x86 (32-bit)')
     def test_pack_unpack_roundtrip_for_nans(self):
         pack = _testcapi.float_pack
         unpack = _testcapi.float_unpack
@@ -193,7 +187,16 @@ def test_pack_unpack_roundtrip_for_nans(self):
         for _ in range(10):
             for size in (2, 4, 8):
                 sign = random.randint(0, 1)
-                signaling = random.randint(0, 1)
+                if sys.maxsize != 2147483647:  # not it 32-bit mode
+                    signaling = random.randint(0, 1)
+                else:
+                    # Skip sNaN's on x86 (32-bit).  The problem is that sNaN
+                    # doubles become qNaN doubles just by the C calling
+                    # convention, there is no way to preserve sNaN doubles
+                    # between C function calls with the current
+                    # PyFloat_Pack/Unpack*() API.  See also gh-130317 and
+                    # e.g. https://developercommunity.visualstudio.com/t/155064
+                    signaling = 0
                 quiet = int(not signaling)
                 if size == 8:
                     payload = random.randint(signaling, 1 << 50)
@@ -209,12 +212,6 @@ def test_pack_unpack_roundtrip_for_nans(self):
                     with self.subTest(data=data, size=size, endian=endian):
                         data1 = data if endian == BIG_ENDIAN else data[::-1]
                         value = unpack(data1, endian)
-                        if signaling and sys.platform == 'win32':
-                            # On Windows x86, sNaN becomes qNaN when returned
-                            # from function.  That's a known bug, e.g.
-                            # 
https://developercommunity.visualstudio.com/t/155064
-                            # (see also gh-130317).
-                            value = _testcapi.float_set_snan(value)
                         data2 = pack(size, value, endian)
                         self.assertTrue(math.isnan(value))
                         self.assertEqual(data1, data2)
diff --git a/Modules/_testcapi/clinic/float.c.h 
b/Modules/_testcapi/clinic/float.c.h
index 0710e4df1963cb..d5a00c8072da1e 100644
--- a/Modules/_testcapi/clinic/float.c.h
+++ b/Modules/_testcapi/clinic/float.c.h
@@ -81,13 +81,4 @@ _testcapi_float_unpack(PyObject *module, PyObject *const 
*args, Py_ssize_t nargs
 exit:
     return return_value;
 }
-
-PyDoc_STRVAR(_testcapi_float_set_snan__doc__,
-"float_set_snan($module, obj, /)\n"
-"--\n"
-"\n"
-"Make a signaling NaN.");
-
-#define _TESTCAPI_FLOAT_SET_SNAN_METHODDEF    \
-    {"float_set_snan", (PyCFunction)_testcapi_float_set_snan, METH_O, 
_testcapi_float_set_snan__doc__},
-/*[clinic end generated code: output=1b0e9b05e1f50712 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b43dfd3a77fe04ba input=a9049054013a1b77]*/
diff --git a/Modules/_testcapi/float.c b/Modules/_testcapi/float.c
index 007884bc1f9da3..e3869134c84d43 100644
--- a/Modules/_testcapi/float.c
+++ b/Modules/_testcapi/float.c
@@ -157,42 +157,9 @@ test_string_to_double(PyObject *self, PyObject 
*Py_UNUSED(ignored))
 }
 
 
-/*[clinic input]
-_testcapi.float_set_snan
-
-    obj: object
-    /
-
-Make a signaling NaN.
-[clinic start generated code]*/
-
-static PyObject *
-_testcapi_float_set_snan(PyObject *module, PyObject *obj)
-/*[clinic end generated code: output=f43778a70f60aa4b input=c1269b0f88ef27ac]*/
-{
-    if (!PyFloat_Check(obj)) {
-        PyErr_SetString(PyExc_ValueError, "float-point number expected");
-        return NULL;
-    }
-    double d = ((PyFloatObject *)obj)->ob_fval;
-    if (!isnan(d)) {
-        PyErr_SetString(PyExc_ValueError, "nan expected");
-        return NULL;
-    }
-    uint64_t v;
-    memcpy(&v, &d, 8);
-    v &= ~(1ULL << 51); /* make sNaN */
-
-    // gh-130317: memcpy() is needed to preserve the sNaN flag on x86 (32-bit)
-    PyObject *res = PyFloat_FromDouble(0.0);
-    memcpy(&((PyFloatObject *)res)->ob_fval, &v, 8);
-    return res;
-}
-
 static PyMethodDef test_methods[] = {
     _TESTCAPI_FLOAT_PACK_METHODDEF
     _TESTCAPI_FLOAT_UNPACK_METHODDEF
-    _TESTCAPI_FLOAT_SET_SNAN_METHODDEF
     {"test_string_to_double", test_string_to_double, METH_NOARGS},
     {NULL},
 };
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 76ed24d29fd25c..692b6aa2cabd5e 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -2495,12 +2495,10 @@ PyFloat_Unpack4(const char *data, int le)
 
             if ((v & (1 << 22)) == 0) {
                 double y = x; /* will make qNaN double */
-                union double_val {
-                    double d;
-                    uint64_t u64;
-                } *py = (union double_val *)&y;
-
-                py->u64 &= ~(1ULL << 51); /* make sNaN */
+                uint64_t u64;
+                memcpy(&u64, &y, 8);
+                u64 &= ~(1ULL << 51); /* make sNaN */
+                memcpy(&y, &u64, 8);
                 return y;
             }
         }

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to