From 971fd98eb1816eb6c3ee4f5644b5dbb1d6be0119 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 22 Feb 2025 13:07:16 +0300 Subject: [PATCH 01/23] gh-130317: fix PyFloat_Pack2/Unpack2 for NaN's with payload --- Lib/test/test_struct.py | 8 +++++++ ...-02-22-13-07-06.gh-issue-130317.tnxd0I.rst | 4 ++++ Objects/floatobject.c | 24 +++++++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-02-22-13-07-06.gh-issue-130317.tnxd0I.rst diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index b99391e482ff70..c796def3efb060 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -935,6 +935,14 @@ def test_half_float(self): self.assertTrue(math.isnan(struct.unpack('e', bits[::-1])[0])) + # Check round-trip for NaN's: + if sys.platform != 'win32': + for formatcode, bits in format_bits__nan_list: + nan = struct.unpack('e', bits[::-1])[0] + self.assertEqual(struct.pack('>e', nan), bits[::-1]) + # Check that packing produces a bit pattern representing a quiet NaN: # all exponent bits and the msb of the fraction should all be 1. packed = struct.pack(' Date: Tue, 25 Feb 2025 08:04:58 +0300 Subject: [PATCH 02/23] more tests in test_capi, fix also Pack/Unpack4 --- Lib/test/test_capi/test_float.py | 28 ++++++++++++++++++++++++++++ Objects/floatobject.c | 24 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 92c987794142c9..52ace3f4637954 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -1,4 +1,5 @@ import math +import random import sys import unittest import warnings @@ -178,6 +179,33 @@ def test_pack_unpack_roundtrip(self): else: self.assertEqual(value2, value) + @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") + def test_pack_unpack_roundtrip_nans(self): + pack = _testcapi.float_pack + unpack = _testcapi.float_unpack + + for _ in range(100): + for size in (2, 4, 8): + sign = random.randint(0, 1) + quiet = random.randint(0, 1) + if size == 8: + payload = random.randint(0 if quiet else 1, 1<<50) + i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload + elif size == 4: + payload = random.randint(0 if quiet else 1, 1<<21) + i = (sign<<31) + (0xff<<23) + (quiet<<22) + payload + elif size == 2: + payload = random.randint(0 if quiet else 1, 1<<8) + i = (sign<<15) + (0x1f<<10) + (quiet<<9) + payload + data = bytes.fromhex(f'{i:x}') + for endian in (BIG_ENDIAN, LITTLE_ENDIAN): + with self.subTest(data=data, size=size, endian=endian): + data1 = data if endian == BIG_ENDIAN else data[::-1] + value = unpack(data1, endian) + data2 = pack(size, value, endian) + self.assertTrue(math.isnan(value)) + self.assertEqual(data1, data2) + if __name__ == "__main__": unittest.main() diff --git a/Objects/floatobject.c b/Objects/floatobject.c index d2fe9719221283..ac1e8442aac561 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2221,6 +2221,17 @@ PyFloat_Pack4(double x, char *data, int le) if (isinf(y) && !isinf(x)) goto Overflow; + if (isnan(x)) { + uint64_t v; + + memcpy(&v, &x, 8); + if ((v & (1ULL<<51)) == 0) { + uint32_t *py = (uint32_t *)&y; + + *py -= (1<<22); + } + } + unsigned char s[sizeof(float)]; memcpy(s, &y, sizeof(float)); @@ -2507,6 +2518,19 @@ PyFloat_Unpack4(const char *data, int le) memcpy(&x, p, 4); } + if (isnan(x)) { + uint32_t v; + + memcpy(&v, &x, 4); + if ((v & (1<<22)) == 0) { + double y = x; + uint64_t *py = (uint64_t *)&y; + + *py -= (1ULL<<51); + return y; + } + } + return x; } } From 218f6b317b6ae1c208be1d74b71c12f187ee6456 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 09:26:10 +0300 Subject: [PATCH 03/23] + different fix for Pack/Unpack2 (works as for floats) --- Lib/test/test_struct.py | 5 ++--- Objects/floatobject.c | 11 ++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index c796def3efb060..3fcaf60b5f9763 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -935,9 +935,8 @@ def test_half_float(self): self.assertTrue(math.isnan(struct.unpack('e', bits[::-1])[0])) - # Check round-trip for NaN's: - if sys.platform != 'win32': - for formatcode, bits in format_bits__nan_list: + if sys.platform != 'win32': + # Check round-trip for NaN's: nan = struct.unpack('e', bits[::-1])[0] diff --git a/Objects/floatobject.c b/Objects/floatobject.c index ac1e8442aac561..7a394fbf75bffb 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2054,10 +2054,7 @@ PyFloat_Pack2(double x, char *data, int le) uint64_t v; memcpy(&v, &x, sizeof(v)); - bits = v & 0x1ffULL; /* NaN's payload */ - if (v & 0x8000000000000ULL) { /* is a quiet NaN? */ - bits += 0x200; - } + bits = ((v & 0xffc0000000000ULL)>>42); /* NaN's payload */ } else { sign = (x < 0.0); @@ -2416,11 +2413,7 @@ PyFloat_Unpack2(const char *data, int le) /* NaN */ uint64_t v = sign ? 0xfff0000000000000ULL : 0x7ff0000000000000ULL; - if (f & 0x200) { /* is a quiet NaN? */ - v += 0x8000000000000ULL; - f -= 0x200; - } - v += f; /* add NaN's payload */ + v += ((uint64_t)f << 42); /* add NaN's payload */ memcpy(&x, &v, sizeof(v)); return x; } From a7f5ed928a10a3305d15f955e34e0d65e7a52ea5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 09:27:54 +0300 Subject: [PATCH 04/23] blacklist on win32 --- Lib/test/test_capi/test_float.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 52ace3f4637954..c2ac9db9fc23b6 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -179,7 +179,8 @@ def test_pack_unpack_roundtrip(self): else: self.assertEqual(value2, value) - @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") + @unittest.skipUnless(HAVE_IEEE_754 and sys.platform != 'win32', + "requires IEEE 754") def test_pack_unpack_roundtrip_nans(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack From c7c08ff62fdcc53d2b9e0325b4b49dedf33b51b0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 09:56:22 +0300 Subject: [PATCH 05/23] cleanup and comments --- Objects/floatobject.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 7a394fbf75bffb..5e10625f40bd4d 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2054,7 +2054,7 @@ PyFloat_Pack2(double x, char *data, int le) uint64_t v; memcpy(&v, &x, sizeof(v)); - bits = ((v & 0xffc0000000000ULL)>>42); /* NaN's payload */ + bits = (v & 0xffc0000000000ULL) >> 42; /* NaN's payload */ } else { sign = (x < 0.0); @@ -2218,14 +2218,15 @@ PyFloat_Pack4(double x, char *data, int le) if (isinf(y) && !isinf(x)) goto Overflow; + /* correct y if x was a sNaN, transformed to qNaN by assignment */ if (isnan(x)) { uint64_t v; memcpy(&v, &x, 8); - if ((v & (1ULL<<51)) == 0) { + if ((v & (1ULL << 51)) == 0) { uint32_t *py = (uint32_t *)&y; - *py -= (1<<22); + *py -= 1 << 22; /* make sNaN */ } } @@ -2413,7 +2414,7 @@ PyFloat_Unpack2(const char *data, int le) /* NaN */ uint64_t v = sign ? 0xfff0000000000000ULL : 0x7ff0000000000000ULL; - v += ((uint64_t)f << 42); /* add NaN's payload */ + v += (uint64_t)f << 42; /* add NaN's payload */ memcpy(&x, &v, sizeof(v)); return x; } @@ -2511,15 +2512,16 @@ PyFloat_Unpack4(const char *data, int le) memcpy(&x, p, 4); } + /* return sNaN double if x was sNaN float */ if (isnan(x)) { uint32_t v; memcpy(&v, &x, 4); if ((v & (1<<22)) == 0) { - double y = x; + double y = x; /* will make qNaN double */ uint64_t *py = (uint64_t *)&y; - *py -= (1ULL<<51); + *py -= (1ULL<<51); /* make sNaN */ return y; } } From de7b6aa631788d2be5bdf2f866e8e702c5dd5488 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 10:29:16 +0300 Subject: [PATCH 06/23] + enable test_pack_unpack_roundtrip_nans on win32 for qNaN's --- Lib/test/test_capi/test_float.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index c2ac9db9fc23b6..3fcbf7c6708d61 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -179,8 +179,7 @@ def test_pack_unpack_roundtrip(self): else: self.assertEqual(value2, value) - @unittest.skipUnless(HAVE_IEEE_754 and sys.platform != 'win32', - "requires IEEE 754") + @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") def test_pack_unpack_roundtrip_nans(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack @@ -188,7 +187,10 @@ def test_pack_unpack_roundtrip_nans(self): for _ in range(100): for size in (2, 4, 8): sign = random.randint(0, 1) - quiet = random.randint(0, 1) + if sys.platform != 'win32': + quiet = random.randint(0, 1) + else: + quiet = 1 # doesn't work for sNaN's here if size == 8: payload = random.randint(0 if quiet else 1, 1<<50) i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload From e6c1c12b3effc0e34e0d059c9cc5273a62535609 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 10:55:55 +0300 Subject: [PATCH 07/23] +1 --- Lib/test/test_struct.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 3fcaf60b5f9763..693f60738ec5d6 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -935,12 +935,14 @@ def test_half_float(self): self.assertTrue(math.isnan(struct.unpack('e', bits[::-1])[0])) - if sys.platform != 'win32': - # Check round-trip for NaN's: - nan = struct.unpack('e', bits[::-1])[0] - self.assertEqual(struct.pack('>e', nan), bits[::-1]) + # Check round-trip for NaN's: + if (sys.platform == 'win32' + and not (int.from_bytes(bits[::-1]) & (1<<9))): + continue # doesn't work for sNaN's here + nan = struct.unpack('e', bits[::-1])[0] + self.assertEqual(struct.pack('>e', nan), bits[::-1]) # Check that packing produces a bit pattern representing a quiet NaN: # all exponent bits and the msb of the fraction should all be 1. From ac7bdcb9320a51bf5e34490564150b7f8f158964 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 16:16:27 +0300 Subject: [PATCH 08/23] address review: warning on Windows --- Objects/floatobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 5e10625f40bd4d..ebc8dcda03311d 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2054,7 +2054,8 @@ PyFloat_Pack2(double x, char *data, int le) uint64_t v; memcpy(&v, &x, sizeof(v)); - bits = (v & 0xffc0000000000ULL) >> 42; /* NaN's payload */ + v &= 0xffc0000000000ULL; + bits = (unsigned short)(v >> 42); /* NaN's payload */ } else { sign = (x < 0.0); From b461b81982556dfc73e411bc26b3f9e3be9accaa Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 25 Feb 2025 19:03:53 +0300 Subject: [PATCH 09/23] XXX try to revert win32 WA --- Lib/test/test_capi/test_float.py | 5 +---- Lib/test/test_struct.py | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 3fcbf7c6708d61..52ace3f4637954 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -187,10 +187,7 @@ def test_pack_unpack_roundtrip_nans(self): for _ in range(100): for size in (2, 4, 8): sign = random.randint(0, 1) - if sys.platform != 'win32': - quiet = random.randint(0, 1) - else: - quiet = 1 # doesn't work for sNaN's here + quiet = random.randint(0, 1) if size == 8: payload = random.randint(0 if quiet else 1, 1<<50) i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 693f60738ec5d6..be1f0ef20d7b60 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -936,9 +936,6 @@ def test_half_float(self): self.assertTrue(math.isnan(struct.unpack('>e', bits[::-1])[0])) # Check round-trip for NaN's: - if (sys.platform == 'win32' - and not (int.from_bytes(bits[::-1]) & (1<<9))): - continue # doesn't work for sNaN's here nan = struct.unpack('e', bits[::-1])[0] From cbfbf05a6bc1da2be26da720b9245ff2d8d45271 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 26 Feb 2025 04:30:37 +0300 Subject: [PATCH 10/23] Revert "XXX try to revert win32 WA" This reverts commit b461b81982556dfc73e411bc26b3f9e3be9accaa. --- Lib/test/test_capi/test_float.py | 5 ++++- Lib/test/test_struct.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 52ace3f4637954..3fcbf7c6708d61 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -187,7 +187,10 @@ def test_pack_unpack_roundtrip_nans(self): for _ in range(100): for size in (2, 4, 8): sign = random.randint(0, 1) - quiet = random.randint(0, 1) + if sys.platform != 'win32': + quiet = random.randint(0, 1) + else: + quiet = 1 # doesn't work for sNaN's here if size == 8: payload = random.randint(0 if quiet else 1, 1<<50) i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index be1f0ef20d7b60..693f60738ec5d6 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -936,6 +936,9 @@ def test_half_float(self): self.assertTrue(math.isnan(struct.unpack('>e', bits[::-1])[0])) # Check round-trip for NaN's: + if (sys.platform == 'win32' + and not (int.from_bytes(bits[::-1]) & (1<<9))): + continue # doesn't work for sNaN's here nan = struct.unpack('e', bits[::-1])[0] From 32bdeed1d5dca65d726c187938fcef85aab58447 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 06:24:11 +0300 Subject: [PATCH 11/23] revert redundant test --- Lib/test/test_struct.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index e41ed53f1dc540..a410fd5a1940d1 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -935,15 +935,6 @@ def test_half_float(self): self.assertTrue(math.isnan(struct.unpack('e', bits[::-1])[0])) - # Check round-trip for NaN's: - if (sys.platform == 'win32' - and not (int.from_bytes(bits[::-1]) & (1<<9))): - continue # doesn't work for sNaN's here - nan = struct.unpack('e', bits[::-1])[0] - self.assertEqual(struct.pack('>e', nan), bits[::-1]) - # Check that packing produces a bit pattern representing a quiet NaN: # all exponent bits and the msb of the fraction should all be 1. packed = struct.pack(' Date: Sun, 27 Apr 2025 06:48:04 +0300 Subject: [PATCH 12/23] rename test --- Lib/test/test_capi/test_float.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 3fcbf7c6708d61..ab7b0f7c922cea 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -180,7 +180,7 @@ def test_pack_unpack_roundtrip(self): self.assertEqual(value2, value) @unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754") - def test_pack_unpack_roundtrip_nans(self): + def test_pack_unpack_roundtrip_for_nans(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack From b27c91686c31bfaa26e26aeaa36537b7cfa0cca3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 07:01:23 +0300 Subject: [PATCH 13/23] XXX revert win32 wa --- Lib/test/test_capi/test_float.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index ab7b0f7c922cea..7569be2069f78d 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -187,10 +187,7 @@ def test_pack_unpack_roundtrip_for_nans(self): for _ in range(100): for size in (2, 4, 8): sign = random.randint(0, 1) - if sys.platform != 'win32': - quiet = random.randint(0, 1) - else: - quiet = 1 # doesn't work for sNaN's here + quiet = random.randint(0, 1) if size == 8: payload = random.randint(0 if quiet else 1, 1<<50) i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload From 4dc5697ea3443c780e5640f0179e807035d635e2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 07:53:03 +0300 Subject: [PATCH 14/23] +1 --- Modules/_struct.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index ee757ed8a9d29d..0e41433a0de2dc 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -265,7 +265,8 @@ static PyObject * unpack_halffloat(const char *p, /* start of 2-byte string */ int le) /* true for little-endian, false for big-endian */ { - double x = PyFloat_Unpack2(p, le); + volatile double x = PyFloat_Unpack2(p, le); + if (x == -1.0 && PyErr_Occurred()) { return NULL; } @@ -291,9 +292,8 @@ static PyObject * unpack_float(const char *p, /* start of 4-byte string */ int le) /* true for little-endian, false for big-endian */ { - double x; + volatile double x = PyFloat_Unpack4(p, le); - x = PyFloat_Unpack4(p, le); if (x == -1.0 && PyErr_Occurred()) return NULL; return PyFloat_FromDouble(x); @@ -303,9 +303,8 @@ static PyObject * unpack_double(const char *p, /* start of 8-byte string */ int le) /* true for little-endian, false for big-endian */ { - double x; + volatile double x = PyFloat_Unpack8(p, le); - x = PyFloat_Unpack8(p, le); if (x == -1.0 && PyErr_Occurred()) return NULL; return PyFloat_FromDouble(x); @@ -989,11 +988,14 @@ bu_double(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) { - double x = PyFloat_Unpack4(p, 0); + volatile double x = PyFloat_Unpack4(p, 0); + if (x == -1.0 && PyErr_Occurred()) { return NULL; } - double y = PyFloat_Unpack4(p + 4, 0); + + volatile double y = PyFloat_Unpack4(p + 4, 0); + if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1003,13 +1005,14 @@ bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * bu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) { - double x, y; + volatile double x = PyFloat_Unpack8(p, 0); - x = PyFloat_Unpack8(p, 0); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - y = PyFloat_Unpack8(p + 8, 0); + + volatile double y = PyFloat_Unpack8(p + 8, 0); + if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1328,11 +1331,14 @@ lu_double(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) { - double x = PyFloat_Unpack4(p, 1); + volatile double x = PyFloat_Unpack4(p, 1); + if (x == -1.0 && PyErr_Occurred()) { return NULL; } - double y = PyFloat_Unpack4(p + 4, 1); + + volatile double y = PyFloat_Unpack4(p + 4, 1); + if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1342,13 +1348,14 @@ lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * lu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) { - double x, y; + volatile double x = PyFloat_Unpack8(p, 1); - x = PyFloat_Unpack8(p, 1); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - y = PyFloat_Unpack8(p + 8, 1); + + volatile double y = PyFloat_Unpack8(p + 8, 1); + if (y == -1.0 && PyErr_Occurred()) { return NULL; } From 6f71e3460166fbebc2355d4d9b710b7a8eeb5eb9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 08:00:03 +0300 Subject: [PATCH 15/23] +1 --- Modules/_struct.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index 0e41433a0de2dc..0cbe3790b5b909 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -279,7 +279,8 @@ pack_halffloat(_structmodulestate *state, PyObject *v, /* value to pack */ int le) /* true for little-endian, false for big-endian */ { - double x = PyFloat_AsDouble(v); + volatile double x = PyFloat_AsDouble(v); + if (x == -1.0 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -764,26 +765,28 @@ np_halffloat(_structmodulestate *state, char *p, PyObject *v, const formatdef *f static int np_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - float x = (float)PyFloat_AsDouble(v); + volatile float x = (float)PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); return -1; } - memcpy(p, &x, sizeof x); + memcpy(p, (void *)&x, sizeof x); return 0; } static int np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - double x = PyFloat_AsDouble(v); + volatile double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); return -1; } - memcpy(p, &x, sizeof(double)); + memcpy(p, (void *)&x, sizeof(double)); return 0; } @@ -1138,7 +1141,8 @@ bp_halffloat(_structmodulestate *state, char *p, PyObject *v, const formatdef *f static int bp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - double x = PyFloat_AsDouble(v); + volatile double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -1150,7 +1154,8 @@ bp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) static int bp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - double x = PyFloat_AsDouble(v); + volatile double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -1475,7 +1480,8 @@ lp_halffloat(_structmodulestate *state, char *p, PyObject *v, const formatdef *f static int lp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - double x = PyFloat_AsDouble(v); + volatile double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -1487,7 +1493,8 @@ lp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) static int lp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - double x = PyFloat_AsDouble(v); + volatile double x = PyFloat_AsDouble(v); + if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); From 3e5a211916a2bb93b9dc1964ed19797ffc2acb88 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 08:40:08 +0300 Subject: [PATCH 16/23] revert --- Modules/_struct.c | 62 ++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index 0cbe3790b5b909..ee757ed8a9d29d 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -265,8 +265,7 @@ static PyObject * unpack_halffloat(const char *p, /* start of 2-byte string */ int le) /* true for little-endian, false for big-endian */ { - volatile double x = PyFloat_Unpack2(p, le); - + double x = PyFloat_Unpack2(p, le); if (x == -1.0 && PyErr_Occurred()) { return NULL; } @@ -279,8 +278,7 @@ pack_halffloat(_structmodulestate *state, PyObject *v, /* value to pack */ int le) /* true for little-endian, false for big-endian */ { - volatile double x = PyFloat_AsDouble(v); - + double x = PyFloat_AsDouble(v); if (x == -1.0 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -293,8 +291,9 @@ static PyObject * unpack_float(const char *p, /* start of 4-byte string */ int le) /* true for little-endian, false for big-endian */ { - volatile double x = PyFloat_Unpack4(p, le); + double x; + x = PyFloat_Unpack4(p, le); if (x == -1.0 && PyErr_Occurred()) return NULL; return PyFloat_FromDouble(x); @@ -304,8 +303,9 @@ static PyObject * unpack_double(const char *p, /* start of 8-byte string */ int le) /* true for little-endian, false for big-endian */ { - volatile double x = PyFloat_Unpack8(p, le); + double x; + x = PyFloat_Unpack8(p, le); if (x == -1.0 && PyErr_Occurred()) return NULL; return PyFloat_FromDouble(x); @@ -765,28 +765,26 @@ np_halffloat(_structmodulestate *state, char *p, PyObject *v, const formatdef *f static int np_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - volatile float x = (float)PyFloat_AsDouble(v); - + float x = (float)PyFloat_AsDouble(v); if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); return -1; } - memcpy(p, (void *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } static int np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - volatile double x = PyFloat_AsDouble(v); - + double x = PyFloat_AsDouble(v); if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); return -1; } - memcpy(p, (void *)&x, sizeof(double)); + memcpy(p, &x, sizeof(double)); return 0; } @@ -991,14 +989,11 @@ bu_double(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) { - volatile double x = PyFloat_Unpack4(p, 0); - + double x = PyFloat_Unpack4(p, 0); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - - volatile double y = PyFloat_Unpack4(p + 4, 0); - + double y = PyFloat_Unpack4(p + 4, 0); if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1008,14 +1003,13 @@ bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * bu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) { - volatile double x = PyFloat_Unpack8(p, 0); + double x, y; + x = PyFloat_Unpack8(p, 0); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - - volatile double y = PyFloat_Unpack8(p + 8, 0); - + y = PyFloat_Unpack8(p + 8, 0); if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1141,8 +1135,7 @@ bp_halffloat(_structmodulestate *state, char *p, PyObject *v, const formatdef *f static int bp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - volatile double x = PyFloat_AsDouble(v); - + double x = PyFloat_AsDouble(v); if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -1154,8 +1147,7 @@ bp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) static int bp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - volatile double x = PyFloat_AsDouble(v); - + double x = PyFloat_AsDouble(v); if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -1336,14 +1328,11 @@ lu_double(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) { - volatile double x = PyFloat_Unpack4(p, 1); - + double x = PyFloat_Unpack4(p, 1); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - - volatile double y = PyFloat_Unpack4(p + 4, 1); - + double y = PyFloat_Unpack4(p + 4, 1); if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1353,14 +1342,13 @@ lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * lu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) { - volatile double x = PyFloat_Unpack8(p, 1); + double x, y; + x = PyFloat_Unpack8(p, 1); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - - volatile double y = PyFloat_Unpack8(p + 8, 1); - + y = PyFloat_Unpack8(p + 8, 1); if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1480,8 +1468,7 @@ lp_halffloat(_structmodulestate *state, char *p, PyObject *v, const formatdef *f static int lp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - volatile double x = PyFloat_AsDouble(v); - + double x = PyFloat_AsDouble(v); if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); @@ -1493,8 +1480,7 @@ lp_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) static int lp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { - volatile double x = PyFloat_AsDouble(v); - + double x = PyFloat_AsDouble(v); if (x == -1 && PyErr_Occurred()) { PyErr_SetString(state->StructError, "required argument is not a float"); From 092d729d15fd6420010627005ebfeacc0d733ea2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 09:34:38 +0300 Subject: [PATCH 17/23] fixup value on win32 --- Lib/test/test_capi/test_float.py | 14 +++++++------ Modules/_testcapi/clinic/float.c.h | 11 +++++++++- Modules/_testcapi/float.c | 33 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 7569be2069f78d..9384b3152fc323 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -183,25 +183,27 @@ def test_pack_unpack_roundtrip(self): def test_pack_unpack_roundtrip_for_nans(self): pack = _testcapi.float_pack unpack = _testcapi.float_unpack - - for _ in range(100): + for _ in range(1000): for size in (2, 4, 8): sign = random.randint(0, 1) - quiet = random.randint(0, 1) + signaling = random.randint(0, 1) + quiet = int(not signaling) if size == 8: - payload = random.randint(0 if quiet else 1, 1<<50) + payload = random.randint(signaling, 1<<50) i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload elif size == 4: - payload = random.randint(0 if quiet else 1, 1<<21) + payload = random.randint(signaling, 1<<21) i = (sign<<31) + (0xff<<23) + (quiet<<22) + payload elif size == 2: - payload = random.randint(0 if quiet else 1, 1<<8) + payload = random.randint(signaling, 1<<8) i = (sign<<15) + (0x1f<<10) + (quiet<<9) + payload data = bytes.fromhex(f'{i:x}') for endian in (BIG_ENDIAN, LITTLE_ENDIAN): 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': + 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 d5a00c8072da1e..0710e4df1963cb 100644 --- a/Modules/_testcapi/clinic/float.c.h +++ b/Modules/_testcapi/clinic/float.c.h @@ -81,4 +81,13 @@ _testcapi_float_unpack(PyObject *module, PyObject *const *args, Py_ssize_t nargs exit: 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]*/ diff --git a/Modules/_testcapi/float.c b/Modules/_testcapi/float.c index e3869134c84d43..a7bd5df1a1b8a3 100644 --- a/Modules/_testcapi/float.c +++ b/Modules/_testcapi/float.c @@ -157,13 +157,46 @@ 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; + } + PyObject *ret = PyNumber_Positive(obj); + if (!ret) { + return NULL; + } + double *d = &((PyFloatObject *)ret)->ob_fval; + if (!isnan(*d)) { + PyErr_SetString(PyExc_ValueError, "nan expected"); + return NULL; + } + uint64_t *v = (uint64_t *)d; + *v &= ~(1ULL<<51); /* make sNaN */ + return ret; +} + 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}, }; + int _PyTestCapi_Init_Float(PyObject *mod) { From 6137c7525cbd3fff4bcdc098e9c73529d53eb2d0 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 13:32:56 +0300 Subject: [PATCH 18/23] cleanup & comments --- Modules/_testcapi/float.c | 1 - Objects/floatobject.c | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Modules/_testcapi/float.c b/Modules/_testcapi/float.c index a7bd5df1a1b8a3..e0c73774596061 100644 --- a/Modules/_testcapi/float.c +++ b/Modules/_testcapi/float.c @@ -196,7 +196,6 @@ static PyMethodDef test_methods[] = { {NULL}, }; - int _PyTestCapi_Init_Float(PyObject *mod) { diff --git a/Objects/floatobject.c b/Objects/floatobject.c index cfcdd84ac60a99..9267a0c2cc312c 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2028,7 +2028,7 @@ PyFloat_Pack2(double x, char *data, int le) memcpy(&v, &x, sizeof(v)); v &= 0xffc0000000000ULL; - bits = (unsigned short)(v >> 42); /* NaN's payload */ + bits = (unsigned short)(v >> 42); /* NaN's type & payload */ } else { sign = (x < 0.0); @@ -2192,7 +2192,7 @@ PyFloat_Pack4(double x, char *data, int le) if (isinf(y) && !isinf(x)) goto Overflow; - /* correct y if x was a sNaN, transformed to qNaN by assignment */ + /* correct y if x was a sNaN, transformed to qNaN by conversion */ if (isnan(x)) { uint64_t v; @@ -2200,7 +2200,7 @@ PyFloat_Pack4(double x, char *data, int le) if ((v & (1ULL << 51)) == 0) { uint32_t *py = (uint32_t *)&y; - *py -= 1 << 22; /* make sNaN */ + *py &= ~(1 << 22); /* make sNaN */ } } @@ -2388,7 +2388,7 @@ PyFloat_Unpack2(const char *data, int le) /* NaN */ uint64_t v = sign ? 0xfff0000000000000ULL : 0x7ff0000000000000ULL; - v += (uint64_t)f << 42; /* add NaN's payload */ + v += (uint64_t)f << 42; /* add NaN's type & payload */ memcpy(&x, &v, sizeof(v)); return x; } @@ -2491,11 +2491,11 @@ PyFloat_Unpack4(const char *data, int le) uint32_t v; memcpy(&v, &x, 4); - if ((v & (1<<22)) == 0) { + if ((v & (1 << 22)) == 0) { double y = x; /* will make qNaN double */ uint64_t *py = (uint64_t *)&y; - *py -= (1ULL<<51); /* make sNaN */ + *py &= ~(1ULL << 51); /* make sNaN */ return y; } } From 00dfd66bde5603597fb7bd233ece7dd82d96d3e4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 27 Apr 2025 17:43:08 +0300 Subject: [PATCH 19/23] address review: redo helper --- Modules/_testcapi/float.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Modules/_testcapi/float.c b/Modules/_testcapi/float.c index e0c73774596061..a520cebef1e8eb 100644 --- a/Modules/_testcapi/float.c +++ b/Modules/_testcapi/float.c @@ -174,18 +174,16 @@ _testcapi_float_set_snan(PyObject *module, PyObject *obj) PyErr_SetString(PyExc_ValueError, "float-point number expected"); return NULL; } - PyObject *ret = PyNumber_Positive(obj); - if (!ret) { - return NULL; - } - double *d = &((PyFloatObject *)ret)->ob_fval; - if (!isnan(*d)) { + double d = ((PyFloatObject *)obj)->ob_fval; + if (!isnan(d)) { PyErr_SetString(PyExc_ValueError, "nan expected"); return NULL; } - uint64_t *v = (uint64_t *)d; - *v &= ~(1ULL<<51); /* make sNaN */ - return ret; + uint64_t v; + memcpy(&v, &d, 8); + v &= ~(1ULL<<51); /* make sNaN */ + memcpy(&d, &v, 8); + return PyFloat_FromDouble(d); } static PyMethodDef test_methods[] = { From b7f727cda2708ead8f2ddbd15923bc8dfb1f1e3e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 28 Apr 2025 13:42:58 +0300 Subject: [PATCH 20/23] Apply suggestions from code review Co-authored-by: Victor Stinner --- Modules/_testcapi/float.c | 2 +- Objects/floatobject.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/_testcapi/float.c b/Modules/_testcapi/float.c index a520cebef1e8eb..2feeb205d8a376 100644 --- a/Modules/_testcapi/float.c +++ b/Modules/_testcapi/float.c @@ -181,7 +181,7 @@ _testcapi_float_set_snan(PyObject *module, PyObject *obj) } uint64_t v; memcpy(&v, &d, 8); - v &= ~(1ULL<<51); /* make sNaN */ + v &= ~(1ULL << 51); /* make sNaN */ memcpy(&d, &v, 8); return PyFloat_FromDouble(d); } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 9267a0c2cc312c..10ff02d3688161 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2025,7 +2025,6 @@ PyFloat_Pack2(double x, char *data, int le) e = 0x1f; uint64_t v; - memcpy(&v, &x, sizeof(v)); v &= 0xffc0000000000ULL; bits = (unsigned short)(v >> 42); /* NaN's type & payload */ @@ -2489,8 +2488,8 @@ PyFloat_Unpack4(const char *data, int le) /* return sNaN double if x was sNaN float */ if (isnan(x)) { uint32_t v; - memcpy(&v, &x, 4); + if ((v & (1 << 22)) == 0) { double y = x; /* will make qNaN double */ uint64_t *py = (uint64_t *)&y; From 13e83108a5cfbaeb581fe43badf05b71aa375054 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 28 Apr 2025 13:46:43 +0300 Subject: [PATCH 21/23] restore comment --- Lib/test/test_capi/test_float.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 9384b3152fc323..0c5436944ad4be 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -203,6 +203,9 @@ def test_pack_unpack_roundtrip_for_nans(self): data1 = data if endian == BIG_ENDIAN else data[::-1] value = unpack(data1, endian) if signaling and sys.platform == 'win32': + # On this platform sNaN becomes qNaN when returned + # from function. That's a known bug, e.g. + # https://developercommunity.visualstudio.com/t/155064 value = _testcapi.float_set_snan(value) data2 = pack(size, value, endian) self.assertTrue(math.isnan(value)) From bcf54f63c4ad16d5afcdd291f9db3152c7de1fd7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 28 Apr 2025 14:30:28 +0300 Subject: [PATCH 22/23] address review: ensure code works with strict aliasing --- Objects/floatobject.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 10ff02d3688161..e0a8f0c62d4951 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2197,9 +2197,12 @@ PyFloat_Pack4(double x, char *data, int le) memcpy(&v, &x, 8); if ((v & (1ULL << 51)) == 0) { - uint32_t *py = (uint32_t *)&y; + union float_val { + float f; + uint32_t u32; + } *py = (union float_val *)&y; - *py &= ~(1 << 22); /* make sNaN */ + py->u32 &= ~(1 << 22); /* make sNaN */ } } @@ -2492,9 +2495,12 @@ PyFloat_Unpack4(const char *data, int le) if ((v & (1 << 22)) == 0) { double y = x; /* will make qNaN double */ - uint64_t *py = (uint64_t *)&y; + union double_val { + double d; + uint64_t u64; + } *py = (union double_val *)&y; - *py &= ~(1ULL << 51); /* make sNaN */ + py->u64 &= ~(1ULL << 51); /* make sNaN */ return y; } } From 9d206331372dabdd27ca38e1899c550146774d90 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 28 Apr 2025 15:08:51 +0300 Subject: [PATCH 23/23] Apply suggestions from code review Co-authored-by: Victor Stinner --- Lib/test/test_capi/test_float.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index 0c5436944ad4be..c857959d569529 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -189,14 +189,14 @@ def test_pack_unpack_roundtrip_for_nans(self): signaling = random.randint(0, 1) quiet = int(not signaling) if size == 8: - payload = random.randint(signaling, 1<<50) - i = (sign<<63) + (0x7ff<<52) + (quiet<<51) + payload + payload = random.randint(signaling, 1 << 50) + i = (sign << 63) + (0x7ff << 52) + (quiet << 51) + payload elif size == 4: - payload = random.randint(signaling, 1<<21) - i = (sign<<31) + (0xff<<23) + (quiet<<22) + payload + payload = random.randint(signaling, 1 << 21) + i = (sign << 31) + (0xff << 23) + (quiet << 22) + payload elif size == 2: - payload = random.randint(signaling, 1<<8) - i = (sign<<15) + (0x1f<<10) + (quiet<<9) + payload + payload = random.randint(signaling, 1 << 8) + i = (sign << 15) + (0x1f << 10) + (quiet << 9) + payload data = bytes.fromhex(f'{i:x}') for endian in (BIG_ENDIAN, LITTLE_ENDIAN): with self.subTest(data=data, size=size, endian=endian): @@ -206,6 +206,7 @@ def test_pack_unpack_roundtrip_for_nans(self): # On this platform 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))