Skip to content

Commit 30d9822

Browse files
committed
gh-154568: Fix array._array_reconstructor() ignoring requested byte order for float16
The slow-path decoder for IEEE_754_FLOAT16_LE/BE computed the byte-order flag by comparing mformat_code against IEEE_754_FLOAT_LE (the 32-bit float constant) instead of IEEE_754_FLOAT16_LE. Since the float16 mformat codes are never equal to that constant, the comparison was always false, so the decoder always treated input as big-endian regardless of what was requested. Adds a regression test. A real 'e'-typecode array only exercises the slow/converting path when the requested mformat disagrees with the machine's native float16 format, so which direction (LE/BE) exercises the buggy branch depends on the test machine's endianness, and the other direction happens to come out "correct" by coincidence since the bug unconditionally decodes as big-endian. The test uses a typecode ('d') whose native format can never match the float16 codes, forcing the slow path deterministically on any machine.
1 parent 66e313f commit 30d9822

3 files changed

Lines changed: 31 additions & 1 deletion

File tree

Lib/test/test_array.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,28 @@ def test_numbers(self):
239239
self.assertEqual(a, b,
240240
msg="{0!r} != {1!r}; testcase={2!r}".format(a, b, testcase))
241241

242+
def test_float16_endianness(self):
243+
# gh-issue: the slow-path decoder for IEEE_754_FLOAT16_LE/BE
244+
# compared mformat_code against the 32-bit float constant
245+
# (IEEE_754_FLOAT_LE) instead of the float16 one, so it always
246+
# decoded as big-endian regardless of what was requested.
247+
#
248+
# A real 'e'-typecode array only exercises the slow (converting)
249+
# path when the requested mformat disagrees with the *native*
250+
# float16 format, so which of LE/BE actually exercises the buggy
251+
# branch depends on the test machine's endianness, and the other
252+
# direction happens to come out "correct by coincidence" because
253+
# the bug unconditionally decodes as big-endian. Using a
254+
# typecode ('d') whose native mformat can never match
255+
# IEEE_754_FLOAT16_LE/BE forces the slow path deterministically
256+
# on any machine, so both directions are actually exercised.
257+
le_bytes = struct.pack('<e', 1.0)
258+
be_bytes = struct.pack('>e', 1.0)
259+
b_le = array_reconstructor(array.array, 'd', IEEE_754_FLOAT16_LE, le_bytes)
260+
b_be = array_reconstructor(array.array, 'd', IEEE_754_FLOAT16_BE, be_bytes)
261+
self.assertEqual(b_le.tolist(), [1.0])
262+
self.assertEqual(b_be.tolist(), [1.0])
263+
242264
def test_unicode(self):
243265
teststr = "Bonne Journ\xe9e \U0002030a\U00020347"
244266
testcases = (
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Fix :func:`array._array_reconstructor` (used internally for unpickling
2+
:class:`array.array`) ignoring the requested byte order for the
3+
``IEEE_754_FLOAT16_LE``/``IEEE_754_FLOAT16_BE`` machine format codes: it
4+
compared against the 32-bit float constant instead of the float16 one, so
5+
it always decoded as big-endian regardless of what was requested. This
6+
could silently produce wrong values when unpickling an ``'e'``-typecode
7+
(half precision float) array on a machine with different native
8+
endianness than the one that pickled it.

Modules/arraymodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2281,7 +2281,7 @@ array__array_reconstructor_impl(PyObject *module, PyTypeObject *arraytype,
22812281
case IEEE_754_FLOAT16_LE:
22822282
case IEEE_754_FLOAT16_BE: {
22832283
Py_ssize_t i;
2284-
int le = (mformat_code == IEEE_754_FLOAT_LE) ? 1 : 0;
2284+
int le = (mformat_code == IEEE_754_FLOAT16_LE) ? 1 : 0;
22852285
Py_ssize_t itemcount = Py_SIZE(items) / 2;
22862286
const char *memstr = PyBytes_AS_STRING(items);
22872287

0 commit comments

Comments
 (0)