Skip to content

gh-121249: Support _Complex types in the struct module #121613

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 12 commits into from
Oct 7, 2024
Merged
19 changes: 19 additions & 0 deletions Doc/library/struct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,26 @@ platform-dependent.
| ``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 |
+========+==========================+====================+================+============+
| ``E`` | :c:expr:`float complex` | complex | 8 | \(10) |
+--------+--------------------------+--------------------+----------------+------------+
| ``C`` | :c:expr:`double complex` | complex | 16 | \(10) |
+--------+--------------------------+--------------------+----------------+------------+

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

.. versionchanged:: 3.6
Added support for the ``'e'`` format.

.. versionchanged:: 3.14
Added support for the ``'E'`` and ``'C'`` formats.


Notes:

Expand Down Expand Up @@ -349,6 +363,11 @@ Notes:
of bytes. As a special case, ``'0s'`` means a single, empty string (while
``'0c'`` means 0 characters).

(10)
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.

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
2 changes: 2 additions & 0 deletions Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,10 @@ class c_longdouble(_SimpleCData):
try:
class c_double_complex(_SimpleCData):
_type_ = "C"
_check_size(c_double_complex)
class c_float_complex(_SimpleCData):
_type_ = "E"
_check_size(c_float_complex)
class c_longdouble_complex(_SimpleCData):
_type_ = "F"
except AttributeError:
Expand Down
37 changes: 36 additions & 1 deletion Lib/test/test_struct.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import abc
from itertools import combinations
import array
import gc
import math
Expand All @@ -11,12 +12,22 @@
from test import support
from test.support import import_helper, suppress_immortalization
from test.support.script_helper import assert_python_ok
from test.support.testcase import ComplexesAreIdenticalMixin

ISBIGENDIAN = sys.byteorder == "big"

integer_codes = 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'n', 'N'
byteorders = '', '@', '=', '<', '>', '!'

INF = float('inf')
NAN = float('nan')

try:
struct.pack('C', 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 All @@ -33,7 +44,7 @@ def bigendian_to_native(value):
else:
return string_reverse(value)

class StructTest(unittest.TestCase):
class StructTest(ComplexesAreIdenticalMixin, unittest.TestCase):
def test_isbigendian(self):
self.assertEqual((struct.pack('=i', 1)[0] == 0), ISBIGENDIAN)

Expand Down Expand Up @@ -783,6 +794,30 @@ 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)]
for z in values:
for f in ['E', 'C', '>E', '>C', '<E', '<C']:
with self.subTest(z=z, format=f):
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 = "'E' format not supported on this system"
msg2 = "'C' format not supported on this system"
with self.assertRaisesRegex(struct.error, msg1):
struct.pack('E', 1j)
with self.assertRaisesRegex(struct.error, msg1):
struct.unpack('E', b'1')
with self.assertRaisesRegex(struct.error, msg2):
struct.pack('C', 1j)
with self.assertRaisesRegex(struct.error, msg2):
struct.unpack('C', b'1')


class UnpackIteratorTest(unittest.TestCase):
"""
Tests for iterative unpacking (struct.Struct.iter_unpack).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Support the :c:expr:`float complex` and :c:expr:`double complex`
C types in the :mod:`struct` module if the compiler has C11 complex
arithmetic. Patch by Sergey B Kirpichev.
Loading
Loading