gh-121249: unconditionally support complex types in struct (GH-132864)

Co-authored-by: Lisandro Dalcin <dalcinl@gmail.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
Sergey B Kirpichev 2025-05-02 19:24:52 +03:00 committed by GitHub
parent e6c518d2eb
commit f425509349
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 21 additions and 89 deletions

View file

@ -260,6 +260,10 @@ platform-dependent.
+--------+--------------------------+--------------------+----------------+------------+ +--------+--------------------------+--------------------+----------------+------------+
| ``d`` | :c:expr:`double` | float | 8 | \(4) | | ``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) | | ``s`` | :c:expr:`char[]` | bytes | | \(9) |
+--------+--------------------------+--------------------+----------------+------------+ +--------+--------------------------+--------------------+----------------+------------+
| ``p`` | :c:expr:`char[]` | bytes | | \(8) | | ``p`` | :c:expr:`char[]` | bytes | | \(8) |
@ -267,17 +271,6 @@ platform-dependent.
| ``P`` | :c:expr:`void \*` | integer | | \(5) | | ``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 .. versionchanged:: 3.3
Added support for the ``'n'`` and ``'N'`` formats. Added support for the ``'n'`` and ``'N'`` formats.
@ -367,6 +360,11 @@ Notes:
For the ``'E'`` and ``'C'`` format characters, the packed representation uses For the ``'E'`` and ``'C'`` format characters, the packed representation uses
the IEEE 754 binary32 and binary64 format for components of the complex the IEEE 754 binary32 and binary64 format for components of the complex
number, regardless of the floating-point format used by the platform. 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, A format character may be preceded by an integral repeat count. For example,
the format string ``'4h'`` means exactly the same as ``'hhhh'``. the format string ``'4h'`` means exactly the same as ``'hhhh'``.

View file

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

View file

@ -22,12 +22,6 @@
INF = float('inf') INF = float('inf')
NAN = float('nan') 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): def iter_integer_formats(byteorders=byteorders):
for code in integer_codes: for code in integer_codes:
for byteorder in byteorders: for byteorder in byteorders:
@ -796,7 +790,6 @@ def test_repr(self):
s = struct.Struct('=i2H') s = struct.Struct('=i2H')
self.assertEqual(repr(s), f'Struct({s.format!r})') 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): def test_c_complex_round_trip(self):
values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2,
-3, INF, -INF, NAN], 2)] -3, INF, -INF, NAN], 2)]
@ -806,19 +799,6 @@ def test_c_complex_round_trip(self):
round_trip = struct.unpack(f, struct.pack(f, z))[0] round_trip = struct.unpack(f, struct.pack(f, z))[0]
self.assertComplexesAreIdentical(z, round_trip) 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): class UnpackIteratorTest(unittest.TestCase):
""" """

View file

@ -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.

View file

@ -12,9 +12,6 @@
#include "pycore_long.h" // _PyLong_AsByteArray() #include "pycore_long.h" // _PyLong_AsByteArray()
#include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_moduleobject.h" // _PyModule_GetState()
#ifdef Py_HAVE_C_COMPLEX
# include "_complex.h" // complex
#endif
#include <stddef.h> // offsetof() #include <stddef.h> // offsetof()
/*[clinic input] /*[clinic input]
@ -495,25 +492,23 @@ nu_double(_structmodulestate *state, const char *p, const formatdef *f)
return PyFloat_FromDouble(x); return PyFloat_FromDouble(x);
} }
#ifdef Py_HAVE_C_COMPLEX
static PyObject * static PyObject *
nu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) nu_float_complex(_structmodulestate *state, const char *p, const formatdef *f)
{ {
float complex x; float x[2];
memcpy(&x, p, sizeof(x)); memcpy(&x, p, sizeof(x));
return PyComplex_FromDoubles(creal(x), cimag(x)); return PyComplex_FromDoubles(x[0], x[1]);
} }
static PyObject * static PyObject *
nu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) nu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
{ {
double complex x; double x[2];
memcpy(&x, p, sizeof(x)); memcpy(&x, p, sizeof(x));
return PyComplex_FromDoubles(creal(x), cimag(x)); return PyComplex_FromDoubles(x[0], x[1]);
} }
#endif
static PyObject * static PyObject *
nu_void_p(_structmodulestate *state, const char *p, const formatdef *f) nu_void_p(_structmodulestate *state, const char *p, const formatdef *f)
@ -788,13 +783,12 @@ np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
return 0; return 0;
} }
#ifdef Py_HAVE_C_COMPLEX
static int static int
np_float_complex(_structmodulestate *state, char *p, PyObject *v, np_float_complex(_structmodulestate *state, char *p, PyObject *v,
const formatdef *f) const formatdef *f)
{ {
Py_complex c = PyComplex_AsCComplex(v); 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()) { if (c.real == -1 && PyErr_Occurred()) {
PyErr_SetString(state->StructError, PyErr_SetString(state->StructError,
@ -810,7 +804,7 @@ np_double_complex(_structmodulestate *state, char *p, PyObject *v,
const formatdef *f) const formatdef *f)
{ {
Py_complex c = PyComplex_AsCComplex(v); 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()) { if (c.real == -1 && PyErr_Occurred()) {
PyErr_SetString(state->StructError, PyErr_SetString(state->StructError,
@ -820,25 +814,6 @@ np_double_complex(_structmodulestate *state, char *p, PyObject *v,
memcpy(p, &x, sizeof(x)); memcpy(p, &x, sizeof(x));
return 0; 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 static int
np_void_p(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) np_void_p(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
@ -878,13 +853,8 @@ static const formatdef native_table[] = {
{'e', sizeof(short), _Alignof(short), nu_halffloat, np_halffloat}, {'e', sizeof(short), _Alignof(short), nu_halffloat, np_halffloat},
{'f', sizeof(float), _Alignof(float), nu_float, np_float}, {'f', sizeof(float), _Alignof(float), nu_float, np_float},
{'d', sizeof(double), _Alignof(double), nu_double, np_double}, {'d', sizeof(double), _Alignof(double), nu_double, np_double},
#ifdef Py_HAVE_C_COMPLEX {'F', 2*sizeof(float), _Alignof(float[2]), nu_float_complex, np_float_complex},
{'F', sizeof(float complex), _Alignof(float complex), nu_float_complex, np_float_complex}, {'D', 2*sizeof(double), _Alignof(double[2]), nu_double_complex, np_double_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
{'P', sizeof(void *), _Alignof(void *), nu_void_p, np_void_p}, {'P', sizeof(void *), _Alignof(void *), nu_void_p, np_void_p},
{0} {0}
}; };
@ -985,7 +955,6 @@ bu_double(_structmodulestate *state, const char *p, const formatdef *f)
return unpack_double(p, 0); return unpack_double(p, 0);
} }
#ifdef Py_HAVE_C_COMPLEX
static PyObject * static PyObject *
bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f)
{ {
@ -1015,7 +984,6 @@ bu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
} }
return PyComplex_FromDoubles(x, y); return PyComplex_FromDoubles(x, y);
} }
#endif
static PyObject * static PyObject *
bu_bool(_structmodulestate *state, const char *p, const formatdef *f) bu_bool(_structmodulestate *state, const char *p, const formatdef *f)
@ -1156,7 +1124,6 @@ bp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
return PyFloat_Pack8(x, p, 0); return PyFloat_Pack8(x, p, 0);
} }
#ifdef Py_HAVE_C_COMPLEX
static int static int
bp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) bp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
{ {
@ -1186,7 +1153,6 @@ bp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatd
} }
return PyFloat_Pack8(x.imag, p + 8, 0); return PyFloat_Pack8(x.imag, p + 8, 0);
} }
#endif
static int static int
bp_bool(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) bp_bool(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
@ -1218,13 +1184,8 @@ static formatdef bigendian_table[] = {
{'e', 2, 0, bu_halffloat, bp_halffloat}, {'e', 2, 0, bu_halffloat, bp_halffloat},
{'f', 4, 0, bu_float, bp_float}, {'f', 4, 0, bu_float, bp_float},
{'d', 8, 0, bu_double, bp_double}, {'d', 8, 0, bu_double, bp_double},
#ifdef Py_HAVE_C_COMPLEX
{'F', 8, 0, bu_float_complex, bp_float_complex}, {'F', 8, 0, bu_float_complex, bp_float_complex},
{'D', 16, 0, bu_double_complex, bp_double_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} {0}
}; };
@ -1324,7 +1285,6 @@ lu_double(_structmodulestate *state, const char *p, const formatdef *f)
return unpack_double(p, 1); return unpack_double(p, 1);
} }
#ifdef Py_HAVE_C_COMPLEX
static PyObject * static PyObject *
lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f)
{ {
@ -1354,7 +1314,6 @@ lu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
} }
return PyComplex_FromDoubles(x, y); return PyComplex_FromDoubles(x, y);
} }
#endif
static int static int
lp_int(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) lp_int(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
@ -1489,7 +1448,6 @@ lp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
return PyFloat_Pack8(x, p, 1); return PyFloat_Pack8(x, p, 1);
} }
#ifdef Py_HAVE_C_COMPLEX
static int static int
lp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) lp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
{ {
@ -1520,7 +1478,6 @@ lp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatd
} }
return PyFloat_Pack8(x.imag, p + 8, 1); return PyFloat_Pack8(x.imag, p + 8, 1);
} }
#endif
static formatdef lilendian_table[] = { static formatdef lilendian_table[] = {
{'x', 1, 0, NULL}, {'x', 1, 0, NULL},
@ -1542,13 +1499,8 @@ static formatdef lilendian_table[] = {
{'e', 2, 0, lu_halffloat, lp_halffloat}, {'e', 2, 0, lu_halffloat, lp_halffloat},
{'f', 4, 0, lu_float, lp_float}, {'f', 4, 0, lu_float, lp_float},
{'d', 8, 0, lu_double, lp_double}, {'d', 8, 0, lu_double, lp_double},
#ifdef Py_HAVE_C_COMPLEX
{'F', 8, 0, lu_float_complex, lp_float_complex}, {'F', 8, 0, lu_float_complex, lp_float_complex},
{'D', 16, 0, lu_double_complex, lp_double_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} {0}
}; };