mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-141042: fix sNaN's packing for mixed floating-point formats (#141107)
This commit is contained in:
parent
7d54374f9c
commit
23d85a2a3f
3 changed files with 59 additions and 14 deletions
|
|
@ -29,6 +29,23 @@
|
||||||
NAN = float("nan")
|
NAN = float("nan")
|
||||||
|
|
||||||
|
|
||||||
|
def make_nan(size, sign, quiet, payload=None):
|
||||||
|
if size == 8:
|
||||||
|
payload_mask = 0x7ffffffffffff
|
||||||
|
i = (sign << 63) + (0x7ff << 52) + (quiet << 51)
|
||||||
|
elif size == 4:
|
||||||
|
payload_mask = 0x3fffff
|
||||||
|
i = (sign << 31) + (0xff << 23) + (quiet << 22)
|
||||||
|
elif size == 2:
|
||||||
|
payload_mask = 0x1ff
|
||||||
|
i = (sign << 15) + (0x1f << 10) + (quiet << 9)
|
||||||
|
else:
|
||||||
|
raise ValueError("size must be either 2, 4, or 8")
|
||||||
|
if payload is None:
|
||||||
|
payload = random.randint(not quiet, payload_mask)
|
||||||
|
return i + payload
|
||||||
|
|
||||||
|
|
||||||
class CAPIFloatTest(unittest.TestCase):
|
class CAPIFloatTest(unittest.TestCase):
|
||||||
def test_check(self):
|
def test_check(self):
|
||||||
# Test PyFloat_Check()
|
# Test PyFloat_Check()
|
||||||
|
|
@ -202,16 +219,7 @@ def test_pack_unpack_roundtrip_for_nans(self):
|
||||||
# HP PA RISC uses 0 for quiet, see:
|
# HP PA RISC uses 0 for quiet, see:
|
||||||
# https://en.wikipedia.org/wiki/NaN#Encoding
|
# https://en.wikipedia.org/wiki/NaN#Encoding
|
||||||
signaling = 1
|
signaling = 1
|
||||||
quiet = int(not signaling)
|
i = make_nan(size, sign, not signaling)
|
||||||
if size == 8:
|
|
||||||
payload = random.randint(signaling, 0x7ffffffffffff)
|
|
||||||
i = (sign << 63) + (0x7ff << 52) + (quiet << 51) + payload
|
|
||||||
elif size == 4:
|
|
||||||
payload = random.randint(signaling, 0x3fffff)
|
|
||||||
i = (sign << 31) + (0xff << 23) + (quiet << 22) + payload
|
|
||||||
elif size == 2:
|
|
||||||
payload = random.randint(signaling, 0x1ff)
|
|
||||||
i = (sign << 15) + (0x1f << 10) + (quiet << 9) + payload
|
|
||||||
data = bytes.fromhex(f'{i:x}')
|
data = bytes.fromhex(f'{i:x}')
|
||||||
for endian in (BIG_ENDIAN, LITTLE_ENDIAN):
|
for endian in (BIG_ENDIAN, LITTLE_ENDIAN):
|
||||||
with self.subTest(data=data, size=size, endian=endian):
|
with self.subTest(data=data, size=size, endian=endian):
|
||||||
|
|
@ -221,6 +229,32 @@ def test_pack_unpack_roundtrip_for_nans(self):
|
||||||
self.assertTrue(math.isnan(value))
|
self.assertTrue(math.isnan(value))
|
||||||
self.assertEqual(data1, data2)
|
self.assertEqual(data1, data2)
|
||||||
|
|
||||||
|
@unittest.skipUnless(HAVE_IEEE_754, "requires IEEE 754")
|
||||||
|
@unittest.skipUnless(sys.maxsize != 2147483647, "requires 64-bit mode")
|
||||||
|
def test_pack_unpack_nans_for_different_formats(self):
|
||||||
|
pack = _testcapi.float_pack
|
||||||
|
unpack = _testcapi.float_unpack
|
||||||
|
|
||||||
|
for endian in (BIG_ENDIAN, LITTLE_ENDIAN):
|
||||||
|
with self.subTest(endian=endian):
|
||||||
|
byteorder = "big" if endian == BIG_ENDIAN else "little"
|
||||||
|
|
||||||
|
# Convert sNaN to qNaN, if payload got truncated
|
||||||
|
data = make_nan(8, 0, False, 0x80001).to_bytes(8, byteorder)
|
||||||
|
snan_low = unpack(data, endian)
|
||||||
|
qnan4 = make_nan(4, 0, True, 0).to_bytes(4, byteorder)
|
||||||
|
qnan2 = make_nan(2, 0, True, 0).to_bytes(2, byteorder)
|
||||||
|
self.assertEqual(pack(4, snan_low, endian), qnan4)
|
||||||
|
self.assertEqual(pack(2, snan_low, endian), qnan2)
|
||||||
|
|
||||||
|
# Preserve NaN type, if payload not truncated
|
||||||
|
data = make_nan(8, 0, False, 0x80000000001).to_bytes(8, byteorder)
|
||||||
|
snan_high = unpack(data, endian)
|
||||||
|
snan4 = make_nan(4, 0, False, 16384).to_bytes(4, byteorder)
|
||||||
|
snan2 = make_nan(2, 0, False, 2).to_bytes(2, byteorder)
|
||||||
|
self.assertEqual(pack(4, snan_high, endian), snan4)
|
||||||
|
self.assertEqual(pack(2, snan_high, endian), snan2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
Make qNaN in :c:func:`PyFloat_Pack2` and :c:func:`PyFloat_Pack4`, if while
|
||||||
|
conversion to a narrower precision floating-point format --- the remaining
|
||||||
|
after truncation payload will be zero. Patch by Sergey B Kirpichev.
|
||||||
|
|
@ -2030,6 +2030,10 @@ PyFloat_Pack2(double x, char *data, int le)
|
||||||
memcpy(&v, &x, sizeof(v));
|
memcpy(&v, &x, sizeof(v));
|
||||||
v &= 0xffc0000000000ULL;
|
v &= 0xffc0000000000ULL;
|
||||||
bits = (unsigned short)(v >> 42); /* NaN's type & payload */
|
bits = (unsigned short)(v >> 42); /* NaN's type & payload */
|
||||||
|
/* set qNaN if no payload */
|
||||||
|
if (!bits) {
|
||||||
|
bits |= (1<<9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sign = (x < 0.0);
|
sign = (x < 0.0);
|
||||||
|
|
@ -2202,16 +2206,16 @@ PyFloat_Pack4(double x, char *data, int le)
|
||||||
if ((v & (1ULL << 51)) == 0) {
|
if ((v & (1ULL << 51)) == 0) {
|
||||||
uint32_t u32;
|
uint32_t u32;
|
||||||
memcpy(&u32, &y, 4);
|
memcpy(&u32, &y, 4);
|
||||||
u32 &= ~(1 << 22); /* make sNaN */
|
/* if have payload, make sNaN */
|
||||||
|
if (u32 & 0x3fffff) {
|
||||||
|
u32 &= ~(1 << 22);
|
||||||
|
}
|
||||||
memcpy(&y, &u32, 4);
|
memcpy(&y, &u32, 4);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
uint32_t u32;
|
uint32_t u32;
|
||||||
|
|
||||||
memcpy(&u32, &y, 4);
|
memcpy(&u32, &y, 4);
|
||||||
if ((v & (1ULL << 51)) == 0) {
|
|
||||||
u32 &= ~(1 << 22);
|
|
||||||
}
|
|
||||||
/* Workaround RISC-V: "If a NaN value is converted to a
|
/* Workaround RISC-V: "If a NaN value is converted to a
|
||||||
* different floating-point type, the result is the
|
* different floating-point type, the result is the
|
||||||
* canonical NaN of the new type". The canonical NaN here
|
* canonical NaN of the new type". The canonical NaN here
|
||||||
|
|
@ -2222,6 +2226,10 @@ PyFloat_Pack4(double x, char *data, int le)
|
||||||
/* add payload */
|
/* add payload */
|
||||||
u32 -= (u32 & 0x3fffff);
|
u32 -= (u32 & 0x3fffff);
|
||||||
u32 += (uint32_t)((v & 0x7ffffffffffffULL) >> 29);
|
u32 += (uint32_t)((v & 0x7ffffffffffffULL) >> 29);
|
||||||
|
/* if have payload, make sNaN */
|
||||||
|
if ((v & (1ULL << 51)) == 0 && (u32 & 0x3fffff)) {
|
||||||
|
u32 &= ~(1 << 22);
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(&y, &u32, 4);
|
memcpy(&y, &u32, 4);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue