Skip to content

gh-121249: unconditionally support complex types in struct #132864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 9 additions & 11 deletions Doc/library/struct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,24 +260,17 @@ platform-dependent.
+--------+--------------------------+--------------------+----------------+------------+
| ``d`` | :c:expr:`double` | float | 8 | \(4) |
+--------+--------------------------+--------------------+----------------+------------+
| ``F`` | :c:expr:`float complex` | complex | 8 | \(10) |
+--------+--------------------------+--------------------+----------------+------------+
| ``D`` | :c:expr:`double complex` | complex | 16 | \(10) |
+--------+--------------------------+--------------------+----------------+------------+
| ``s`` | :c:expr:`char[]` | bytes | | \(9) |
+--------+--------------------------+--------------------+----------------+------------+
| ``p`` | :c:expr:`char[]` | bytes | | \(8) |
+--------+--------------------------+--------------------+----------------+------------+
| ``P`` | :c:expr:`void \*` | integer | | \(5) |
+--------+--------------------------+--------------------+----------------+------------+

Additionally, if IEC 60559 compatible complex arithmetic (Annex G of the
C11 standard) is supported, the following format characters are available:

+--------+--------------------------+--------------------+----------------+------------+
| Format | C Type | Python type | Standard size | Notes |
+========+==========================+====================+================+============+
| ``F`` | :c:expr:`float complex` | complex | 8 | \(10) |
+--------+--------------------------+--------------------+----------------+------------+
| ``D`` | :c:expr:`double complex` | complex | 16 | \(10) |
+--------+--------------------------+--------------------+----------------+------------+

.. versionchanged:: 3.3
Added support for the ``'n'`` and ``'N'`` formats.

Expand Down Expand Up @@ -367,6 +360,11 @@ Notes:
For the ``'E'`` and ``'C'`` format characters, the packed representation uses
the IEEE 754 binary32 and binary64 format for components of the complex
number, regardless of the floating-point format used by the platform.
Note that complex types (``F`` and ``D``) are available unconditionally,
despite complex types being an optional feature in C.
As specified in the C11 standard, each complex type is represented by a
two-element C array containing, respectively, the real and imaginary parts.


A format character may be preceded by an integral repeat count. For example,
the format string ``'4h'`` means exactly the same as ``'hhhh'``.
Expand Down
4 changes: 2 additions & 2 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1309,8 +1309,8 @@ struct
------

* Support the :c:expr:`float complex` and :c:expr:`double complex` C types in
the :mod:`struct` module (formatting characters ``'F'`` and ``'D'``,
respectively) if the compiler has C11 complex arithmetic.
the :mod:`struct` module (formatting characters ``'F'`` and ``'D'``
respectively).
(Contributed by Sergey B Kirpichev in :gh:`121249`.)


Expand Down
20 changes: 0 additions & 20 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@
INF = float('inf')
NAN = float('nan')

try:
struct.pack('D', 1j)
have_c_complex = True
except struct.error:
have_c_complex = False

def iter_integer_formats(byteorders=byteorders):
for code in integer_codes:
for byteorder in byteorders:
Expand Down Expand Up @@ -796,7 +790,6 @@ def test_repr(self):
s = struct.Struct('=i2H')
self.assertEqual(repr(s), f'Struct({s.format!r})')

@unittest.skipUnless(have_c_complex, "requires C11 complex type support")
def test_c_complex_round_trip(self):
values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2,
-3, INF, -INF, NAN], 2)]
Expand All @@ -806,19 +799,6 @@ def test_c_complex_round_trip(self):
round_trip = struct.unpack(f, struct.pack(f, z))[0]
self.assertComplexesAreIdentical(z, round_trip)

@unittest.skipIf(have_c_complex, "requires no C11 complex type support")
def test_c_complex_error(self):
msg1 = "'F' format not supported on this system"
msg2 = "'D' format not supported on this system"
with self.assertRaisesRegex(struct.error, msg1):
struct.pack('F', 1j)
with self.assertRaisesRegex(struct.error, msg1):
struct.unpack('F', b'1')
with self.assertRaisesRegex(struct.error, msg2):
struct.pack('D', 1j)
with self.assertRaisesRegex(struct.error, msg2):
struct.unpack('D', b'1')


class UnpackIteratorTest(unittest.TestCase):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Always support the :c:expr:`float complex` and :c:expr:`double complex` C types in
the :mod:`struct` module. Patch by Sergey B Kirpichev.
64 changes: 8 additions & 56 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
#include "pycore_long.h" // _PyLong_AsByteArray()
#include "pycore_moduleobject.h" // _PyModule_GetState()

#ifdef Py_HAVE_C_COMPLEX
# include "_complex.h" // complex
#endif
#include <stddef.h> // offsetof()

/*[clinic input]
Expand Down Expand Up @@ -495,25 +492,23 @@ nu_double(_structmodulestate *state, const char *p, const formatdef *f)
return PyFloat_FromDouble(x);
}

#ifdef Py_HAVE_C_COMPLEX
static PyObject *
nu_float_complex(_structmodulestate *state, const char *p, const formatdef *f)
{
float complex x;
float x[2];

memcpy(&x, p, sizeof(x));
return PyComplex_FromDoubles(creal(x), cimag(x));
return PyComplex_FromDoubles(x[0], x[1]);
}

static PyObject *
nu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
{
double complex x;
double x[2];

memcpy(&x, p, sizeof(x));
return PyComplex_FromDoubles(creal(x), cimag(x));
return PyComplex_FromDoubles(x[0], x[1]);
}
#endif

static PyObject *
nu_void_p(_structmodulestate *state, const char *p, const formatdef *f)
Expand Down Expand Up @@ -788,13 +783,12 @@ np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
return 0;
}

#ifdef Py_HAVE_C_COMPLEX
static int
np_float_complex(_structmodulestate *state, char *p, PyObject *v,
const formatdef *f)
{
Py_complex c = PyComplex_AsCComplex(v);
float complex x = CMPLXF((float)c.real, (float)c.imag);
float x[2] = {(float)c.real, (float)c.imag};

if (c.real == -1 && PyErr_Occurred()) {
PyErr_SetString(state->StructError,
Expand All @@ -810,7 +804,7 @@ np_double_complex(_structmodulestate *state, char *p, PyObject *v,
const formatdef *f)
{
Py_complex c = PyComplex_AsCComplex(v);
double complex x = CMPLX(c.real, c.imag);
double x[2] = {c.real, c.imag};

if (c.real == -1 && PyErr_Occurred()) {
PyErr_SetString(state->StructError,
Expand All @@ -820,25 +814,6 @@ np_double_complex(_structmodulestate *state, char *p, PyObject *v,
memcpy(p, &x, sizeof(x));
return 0;
}
#else
static int
np_complex_stub(_structmodulestate *state, char *p, PyObject *v,
const formatdef *f)
{
PyErr_Format(state->StructError,
"'%c' format not supported on this system",
f->format);
return -1;
}
static PyObject *
nu_complex_stub(_structmodulestate *state, const char *p, const formatdef *f)
{
PyErr_Format(state->StructError,
"'%c' format not supported on this system",
f->format);
return NULL;
}
#endif

static int
np_void_p(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
Expand Down Expand Up @@ -878,13 +853,8 @@ static const formatdef native_table[] = {
{'e', sizeof(short), _Alignof(short), nu_halffloat, np_halffloat},
{'f', sizeof(float), _Alignof(float), nu_float, np_float},
{'d', sizeof(double), _Alignof(double), nu_double, np_double},
#ifdef Py_HAVE_C_COMPLEX
{'F', sizeof(float complex), _Alignof(float complex), nu_float_complex, np_float_complex},
{'D', sizeof(double complex), _Alignof(double complex), nu_double_complex, np_double_complex},
#else
{'F', 1, 0, nu_complex_stub, np_complex_stub},
{'D', 1, 0, nu_complex_stub, np_complex_stub},
#endif
{'F', 2*sizeof(float), _Alignof(float[2]), nu_float_complex, np_float_complex},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something we should actually care in the future but _Alignof is deprecated since C23 and it's renamed as alignof. It's unlikely it will be removed but for the future, we should likely use a macro Py_ALIGNOF or something like that. It's a follow-up work though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, C23 calls it an "Alternative Spelling". I wouldn't worry about it.

{'D', 2*sizeof(double), _Alignof(double[2]), nu_double_complex, np_double_complex},
{'P', sizeof(void *), _Alignof(void *), nu_void_p, np_void_p},
{0}
};
Expand Down Expand Up @@ -985,7 +955,6 @@ bu_double(_structmodulestate *state, const char *p, const formatdef *f)
return unpack_double(p, 0);
}

#ifdef Py_HAVE_C_COMPLEX
static PyObject *
bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f)
{
Expand Down Expand Up @@ -1015,7 +984,6 @@ bu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
}
return PyComplex_FromDoubles(x, y);
}
#endif

static PyObject *
bu_bool(_structmodulestate *state, const char *p, const formatdef *f)
Expand Down Expand Up @@ -1156,7 +1124,6 @@ bp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
return PyFloat_Pack8(x, p, 0);
}

#ifdef Py_HAVE_C_COMPLEX
static int
bp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
{
Expand Down Expand Up @@ -1186,7 +1153,6 @@ bp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatd
}
return PyFloat_Pack8(x.imag, p + 8, 0);
}
#endif

static int
bp_bool(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
Expand Down Expand Up @@ -1218,13 +1184,8 @@ static formatdef bigendian_table[] = {
{'e', 2, 0, bu_halffloat, bp_halffloat},
{'f', 4, 0, bu_float, bp_float},
{'d', 8, 0, bu_double, bp_double},
#ifdef Py_HAVE_C_COMPLEX
{'F', 8, 0, bu_float_complex, bp_float_complex},
{'D', 16, 0, bu_double_complex, bp_double_complex},
#else
{'F', 1, 0, nu_complex_stub, np_complex_stub},
{'D', 1, 0, nu_complex_stub, np_complex_stub},
#endif
{0}
};

Expand Down Expand Up @@ -1324,7 +1285,6 @@ lu_double(_structmodulestate *state, const char *p, const formatdef *f)
return unpack_double(p, 1);
}

#ifdef Py_HAVE_C_COMPLEX
static PyObject *
lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f)
{
Expand Down Expand Up @@ -1354,7 +1314,6 @@ lu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
}
return PyComplex_FromDoubles(x, y);
}
#endif

static int
lp_int(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
Expand Down Expand Up @@ -1489,7 +1448,6 @@ lp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
return PyFloat_Pack8(x, p, 1);
}

#ifdef Py_HAVE_C_COMPLEX
static int
lp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
{
Expand Down Expand Up @@ -1520,7 +1478,6 @@ lp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatd
}
return PyFloat_Pack8(x.imag, p + 8, 1);
}
#endif

static formatdef lilendian_table[] = {
{'x', 1, 0, NULL},
Expand All @@ -1542,13 +1499,8 @@ static formatdef lilendian_table[] = {
{'e', 2, 0, lu_halffloat, lp_halffloat},
{'f', 4, 0, lu_float, lp_float},
{'d', 8, 0, lu_double, lp_double},
#ifdef Py_HAVE_C_COMPLEX
{'F', 8, 0, lu_float_complex, lp_float_complex},
{'D', 16, 0, lu_double_complex, lp_double_complex},
#else
{'F', 1, 0, nu_complex_stub, np_complex_stub},
{'D', 1, 0, nu_complex_stub, np_complex_stub},
#endif
{0}
};

Expand Down
Loading