Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ def test_p_code(self):
(got,) = struct.unpack(code, got)
self.assertEqual(got, expectedback)

def test_705836(self):
def check_705836(self, format, reverse_format):
# SF bug 705836. "<f" and ">f" had a severe rounding bug, where a carry
# from the low-order discarded bits could propagate into the exponent
# field, causing the result to be wrong by a factor of 2.
Expand All @@ -376,27 +376,33 @@ def test_705836(self):
delta /= 2.0
smaller = base - delta
# Packing this rounds away a solid string of trailing 1 bits.
packed = struct.pack("<f", smaller)
unpacked = struct.unpack("<f", packed)[0]
packed = struct.pack(format, smaller)
unpacked = struct.unpack(format, packed)[0]
# This failed at base = 2, 4, and 32, with unpacked = 1, 2, and
# 16, respectively.
self.assertEqual(base, unpacked)
bigpacked = struct.pack(">f", smaller)

bigpacked = struct.pack(reverse_format, smaller)
self.assertEqual(bigpacked, string_reverse(packed))
unpacked = struct.unpack(">f", bigpacked)[0]
unpacked = struct.unpack(reverse_format, bigpacked)[0]
self.assertEqual(base, unpacked)

# Largest finite IEEE single.
big = (1 << 24) - 1
big = math.ldexp(big, 127 - 23)
packed = struct.pack(">f", big)
unpacked = struct.unpack(">f", packed)[0]
packed = struct.pack(format, big)
unpacked = struct.unpack(format, packed)[0]
self.assertEqual(big, unpacked)

# The same, but tack on a 1 bit so it rounds up to infinity.
big = (1 << 25) - 1
big = math.ldexp(big, 127 - 24)
self.assertRaises(OverflowError, struct.pack, ">f", big)
self.assertRaises(OverflowError, struct.pack, format, big)
Comment thread
vstinner marked this conversation as resolved.
Outdated

def test_705836(self):
self.check_705836("<f", ">f")
self.check_705836(">f", "<f")
self.check_705836("f", "<f" if sys.byteorder == "big" else ">f")

def test_1530559(self):
for code, byteorder in iter_integer_formats():
Expand Down Expand Up @@ -1201,6 +1207,20 @@ def test_half_float(self):
for formatcode, bits, f in format_bits_float__doubleRoundingError_list:
self.assertEqual(bits, struct.pack(formatcode, f))

def test_float_round_trip(self):
for format in ("f", "<f", ">f", "d", "<d", ">d"):
with self.subTest(format=format):
f = struct.unpack(format, struct.pack(format, 1.5))[0]
self.assertEqual(f, 1.5)
f = struct.unpack(format, struct.pack(format, NAN))[0]
self.assertTrue(math.isnan(f), f)
f = struct.unpack(format, struct.pack(format, INF))[0]
self.assertTrue(math.isinf(f), f)
self.assertEqual(math.copysign(1.0, f), 1.0)
f = struct.unpack(format, struct.pack(format, -INF))[0]
self.assertTrue(math.isinf(f), f)
self.assertEqual(math.copysign(1.0, f), -1.0)


if __name__ == '__main__':
unittest.main()
5 changes: 2 additions & 3 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -763,14 +763,13 @@ 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);
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 x);
return 0;
return PyFloat_Pack4(x, p, PY_LITTLE_ENDIAN);
}

static int
Expand Down
Loading