From 2bac9b5434adce93075f677f65a3f3d03d755caa Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 21 Mar 2026 10:19:42 +0300 Subject: [PATCH] gh-146238: support half-floats in the array module --- Doc/conf.py | 1 + Doc/library/array.rst | 17 +++++- Doc/whatsnew/3.15.rst | 7 ++- Lib/test/test_array.py | 13 +++- ...-03-21-10-02-20.gh-issue-146238.2WpMOj.rst | 2 + Modules/arraymodule.c | 60 ++++++++++++++++++- 6 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-21-10-02-20.gh-issue-146238.2WpMOj.rst diff --git a/Doc/conf.py b/Doc/conf.py index 4ac6f6192a0806..07e0d113a24c10 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -177,6 +177,7 @@ ('c:type', '__int64'), ('c:type', 'unsigned __int64'), ('c:type', 'double'), + ('c:type', '_Float16'), # Standard C structures ('c:struct', 'in6_addr'), ('c:struct', 'in_addr'), diff --git a/Doc/library/array.rst b/Doc/library/array.rst index 6a69361369bdec..4468edb6efa654 100644 --- a/Doc/library/array.rst +++ b/Doc/library/array.rst @@ -42,13 +42,15 @@ defined: +-----------+--------------------+-------------------+-----------------------+-------+ | ``'Q'`` | unsigned long long | int | 8 | | +-----------+--------------------+-------------------+-----------------------+-------+ +| ``'e'`` | _Float16 | float | 2 | \(3) | ++-----------+--------------------+-------------------+-----------------------+-------+ | ``'f'`` | float | float | 4 | | +-----------+--------------------+-------------------+-----------------------+-------+ | ``'d'`` | double | float | 8 | | +-----------+--------------------+-------------------+-----------------------+-------+ -| ``'F'`` | float complex | complex | 8 | \(3) | +| ``'F'`` | float complex | complex | 8 | \(4) | +-----------+--------------------+-------------------+-----------------------+-------+ -| ``'D'`` | double complex | complex | 16 | \(3) | +| ``'D'`` | double complex | complex | 16 | \(4) | +-----------+--------------------+-------------------+-----------------------+-------+ @@ -69,6 +71,15 @@ Notes: .. versionadded:: 3.13 (3) + The IEEE 754 binary16 "half precision" type was introduced in the 2008 + revision of the `IEEE 754 standard `_. + This type is not widely supported by C compilers. It's available + as :c:expr:`_Float16` type, if the compiler supports the Annex H + of the C23 standard. + + .. versionadded:: 3.15 + +(4) Complex types (``F`` and ``D``) are available unconditionally, regardless on support for complex types (the Annex G of the C11 standard) by the C compiler. @@ -304,3 +315,5 @@ Examples:: `NumPy `_ The NumPy package defines another array type. + +.. _ieee 754 standard: https://en.wikipedia.org/wiki/IEEE_754-2008_revision diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 76e97cf4b55595..df3d6368b150ba 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -637,9 +637,10 @@ argparse array ----- -* Support the :c:expr:`float complex` and :c:expr:`double complex` C types: - formatting characters ``'F'`` and ``'D'`` respectively. - (Contributed by Sergey B Kirpichev in :gh:`146151`.) +* Support half-floats (16-bit IEEE 754 binary interchange format, formatting + character ``'e'``), the :c:expr:`float complex` and :c:expr:`double complex` + C types (formatting characters ``'F'`` and ``'D'`` respectively). + (Contributed by Sergey B Kirpichev in :gh:`146151` and :gh:`146238`.) base64 diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index b3561e7650a0ad..a2a4f94b2ae398 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -31,7 +31,7 @@ class ArraySubclassWithKwargs(array.array): def __init__(self, typecode, newarg=None): array.array.__init__(self) -typecodes = 'uwbBhHiIlLfdqQFD' +typecodes = 'uwbBhHiIlLfdqQFDe' class MiscTest(unittest.TestCase): @@ -117,8 +117,10 @@ def __index__(self): IEEE_754_FLOAT_COMPLEX_BE = 23 IEEE_754_DOUBLE_COMPLEX_LE = 24 IEEE_754_DOUBLE_COMPLEX_BE = 25 +IEEE_754_FLOAT16_LE = 26 +IEEE_754_FLOAT16_BE = 27 -MACHINE_FORMAT_CODE_MAX = 25 +MACHINE_FORMAT_CODE_MAX = 27 class ArrayReconstructorTest(unittest.TestCase): @@ -1588,6 +1590,13 @@ def test_byteswap(self): self.assertEqual(a, b) +class HalfFloatTest(FPTest, unittest.TestCase): + example = [-42.0, 0, 42, 1e3, -1e3] + smallerexample = [-42.0, 0, 42, 1e3, -2e3] + biggerexample = [-42.0, 0, 42, 1e3, 1e3] + typecode = 'e' + minitemsize = 2 + class FloatTest(FPTest, unittest.TestCase): typecode = 'f' minitemsize = 4 diff --git a/Misc/NEWS.d/next/Library/2026-03-21-10-02-20.gh-issue-146238.2WpMOj.rst b/Misc/NEWS.d/next/Library/2026-03-21-10-02-20.gh-issue-146238.2WpMOj.rst new file mode 100644 index 00000000000000..35e951e38e4152 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-21-10-02-20.gh-issue-146238.2WpMOj.rst @@ -0,0 +1,2 @@ +Support half-floats (type code ``'e'`` of the :mod:`struct` module) in the +:mod:`array` module. Patch by Sergey B Kirpichev. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 42572833c2128a..555a35b3fc92ab 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -119,10 +119,12 @@ enum machine_format_code { IEEE_754_FLOAT_COMPLEX_LE = 22, IEEE_754_FLOAT_COMPLEX_BE = 23, IEEE_754_DOUBLE_COMPLEX_LE = 24, - IEEE_754_DOUBLE_COMPLEX_BE = 25 + IEEE_754_DOUBLE_COMPLEX_BE = 25, + IEEE_754_FLOAT16_LE = 26, + IEEE_754_FLOAT16_BE = 27 }; #define MACHINE_FORMAT_CODE_MIN 0 -#define MACHINE_FORMAT_CODE_MAX 25 +#define MACHINE_FORMAT_CODE_MAX 27 /* @@ -611,6 +613,32 @@ QQ_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) return 0; } +static PyObject * +e_getitem(arrayobject *ap, Py_ssize_t i) +{ + double x = PyFloat_Unpack2(ap->ob_item + sizeof(short)*i, + PY_LITTLE_ENDIAN); + + return PyFloat_FromDouble(x); +} + +static int +e_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) +{ + float x; + if (!PyArg_Parse(v, "f;array item must be float", &x)) { + return -1; + } + + CHECK_ARRAY_BOUNDS(ap, i); + + if (i >= 0) { + return PyFloat_Pack2(x, ap->ob_item + sizeof(short)*i, + PY_LITTLE_ENDIAN); + } + return 0; +} + static PyObject * f_getitem(arrayobject *ap, Py_ssize_t i) { @@ -751,6 +779,7 @@ static const struct arraydescr descriptors[] = { {'L', sizeof(long), LL_getitem, LL_setitem, LL_compareitems, "L", 1, 0}, {'q', sizeof(long long), q_getitem, q_setitem, q_compareitems, "q", 1, 1}, {'Q', sizeof(long long), QQ_getitem, QQ_setitem, QQ_compareitems, "Q", 1, 0}, + {'e', sizeof(short), e_getitem, e_setitem, NULL, "e", 0, 0}, {'f', sizeof(float), f_getitem, f_setitem, NULL, "f", 0, 0}, {'d', sizeof(double), d_getitem, d_setitem, NULL, "d", 0, 0}, {'F', 2*sizeof(float), cf_getitem, cf_setitem, NULL, "F", 0, 0}, @@ -2090,6 +2119,8 @@ static const struct mformatdescr { {8, 0, 1}, /* 23: IEEE_754_FLOAT_COMPLEX_BE */ {16, 0, 0}, /* 24: IEEE_754_DOUBLE_COMPLEX_LE */ {16, 0, 1}, /* 25: IEEE_754_DOUBLE_COMPLEX_BE */ + {2, 0, 0}, /* 26: IEEE_754_FLOAT16_LE */ + {2, 0, 1} /* 27: IEEE_754_FLOAT16_BE */ }; @@ -2124,6 +2155,9 @@ typecode_to_mformat_code(char typecode) case 'w': return UTF32_LE + is_big_endian; + case 'e': + return _PY_FLOAT_BIG_ENDIAN ? IEEE_754_FLOAT16_BE : IEEE_754_FLOAT16_LE; + case 'f': return _PY_FLOAT_BIG_ENDIAN ? IEEE_754_FLOAT_BE : IEEE_754_FLOAT_LE; @@ -2309,6 +2343,27 @@ array__array_reconstructor_impl(PyObject *module, PyTypeObject *arraytype, return NULL; } switch (mformat_code) { + case IEEE_754_FLOAT16_LE: + case IEEE_754_FLOAT16_BE: { + Py_ssize_t i; + int le = (mformat_code == IEEE_754_FLOAT_LE) ? 1 : 0; + Py_ssize_t itemcount = Py_SIZE(items) / 2; + const char *memstr = PyBytes_AS_STRING(items); + + converted_items = PyList_New(itemcount); + if (converted_items == NULL) + return NULL; + for (i = 0; i < itemcount; i++) { + PyObject *pyfloat = PyFloat_FromDouble( + PyFloat_Unpack2(&memstr[i * 2], le)); + if (pyfloat == NULL) { + Py_DECREF(converted_items); + return NULL; + } + PyList_SET_ITEM(converted_items, i, pyfloat); + } + break; + } case IEEE_754_FLOAT_LE: case IEEE_754_FLOAT_BE: { Py_ssize_t i; @@ -3129,6 +3184,7 @@ The following type codes are defined:\n\ 'L' unsigned integer 4\n\ 'q' signed integer 8 (see note)\n\ 'Q' unsigned integer 8 (see note)\n\ + 'e' 16-bit IEEE floats 2\n\ 'f' floating-point 4\n\ 'd' floating-point 8\n\ 'F' float complex 8\n\