https://github.com/python/cpython/commit/ad2f0884b16e6af4087ba078d2255d4c81ae8e96
commit: ad2f0884b16e6af4087ba078d2255d4c81ae8e96
branch: main
author: Sergey B Kirpichev <[email protected]>
committer: vstinner <[email protected]>
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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]