mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
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.
This commit is contained in:
parent
ed039b801d
commit
ad2f0884b1
5 changed files with 18 additions and 62 deletions
|
@ -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
|
attempting to unpack a bytes string containing an IEEE INF or NaN will raise an
|
||||||
exception.
|
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
|
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
|
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
|
precision, or smaller dynamic range, not all values can be unpacked. What
|
||||||
|
|
|
@ -180,12 +180,6 @@ class CAPIFloatTest(unittest.TestCase):
|
||||||
self.assertEqual(value2, value)
|
self.assertEqual(value2, value)
|
||||||
|
|
||||||
@unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754")
|
@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):
|
def test_pack_unpack_roundtrip_for_nans(self):
|
||||||
pack = _testcapi.float_pack
|
pack = _testcapi.float_pack
|
||||||
unpack = _testcapi.float_unpack
|
unpack = _testcapi.float_unpack
|
||||||
|
@ -193,7 +187,16 @@ class CAPIFloatTest(unittest.TestCase):
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
for size in (2, 4, 8):
|
for size in (2, 4, 8):
|
||||||
sign = random.randint(0, 1)
|
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)
|
quiet = int(not signaling)
|
||||||
if size == 8:
|
if size == 8:
|
||||||
payload = random.randint(signaling, 1 << 50)
|
payload = random.randint(signaling, 1 << 50)
|
||||||
|
@ -209,12 +212,6 @@ class CAPIFloatTest(unittest.TestCase):
|
||||||
with self.subTest(data=data, size=size, endian=endian):
|
with self.subTest(data=data, size=size, endian=endian):
|
||||||
data1 = data if endian == BIG_ENDIAN else data[::-1]
|
data1 = data if endian == BIG_ENDIAN else data[::-1]
|
||||||
value = unpack(data1, endian)
|
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)
|
data2 = pack(size, value, endian)
|
||||||
self.assertTrue(math.isnan(value))
|
self.assertTrue(math.isnan(value))
|
||||||
self.assertEqual(data1, data2)
|
self.assertEqual(data1, data2)
|
||||||
|
|
11
Modules/_testcapi/clinic/float.c.h
generated
11
Modules/_testcapi/clinic/float.c.h
generated
|
@ -81,13 +81,4 @@ _testcapi_float_unpack(PyObject *module, PyObject *const *args, Py_ssize_t nargs
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
/*[clinic end generated code: output=b43dfd3a77fe04ba input=a9049054013a1b77]*/
|
||||||
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]*/
|
|
||||||
|
|
|
@ -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[] = {
|
static PyMethodDef test_methods[] = {
|
||||||
_TESTCAPI_FLOAT_PACK_METHODDEF
|
_TESTCAPI_FLOAT_PACK_METHODDEF
|
||||||
_TESTCAPI_FLOAT_UNPACK_METHODDEF
|
_TESTCAPI_FLOAT_UNPACK_METHODDEF
|
||||||
_TESTCAPI_FLOAT_SET_SNAN_METHODDEF
|
|
||||||
{"test_string_to_double", test_string_to_double, METH_NOARGS},
|
{"test_string_to_double", test_string_to_double, METH_NOARGS},
|
||||||
{NULL},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2495,12 +2495,10 @@ PyFloat_Unpack4(const char *data, int le)
|
||||||
|
|
||||||
if ((v & (1 << 22)) == 0) {
|
if ((v & (1 << 22)) == 0) {
|
||||||
double y = x; /* will make qNaN double */
|
double y = x; /* will make qNaN double */
|
||||||
union double_val {
|
uint64_t u64;
|
||||||
double d;
|
memcpy(&u64, &y, 8);
|
||||||
uint64_t u64;
|
u64 &= ~(1ULL << 51); /* make sNaN */
|
||||||
} *py = (union double_val *)&y;
|
memcpy(&y, &u64, 8);
|
||||||
|
|
||||||
py->u64 &= ~(1ULL << 51); /* make sNaN */
|
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue