Skip to content

Add support for C99 complex type (_Complex) to the struct module #121249

Description

@skirpichev

Feature or enhancement

Proposal:

The struct module has support for float and double types, so at least there should be also float _Complex and double _Complex. I'll work on a patch.

Initial version
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index d2e6a8bfc8..d941036719 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -208,6 +208,7 @@ class c_longdouble(_SimpleCData):
 try:
     class c_double_complex(_SimpleCData):
         _type_ = "C"
+    _check_size(c_double_complex)
 except AttributeError:
     pass
 
diff --git a/Modules/_struct.c b/Modules/_struct.c
index 6a68478dd4..caf4975413 100644
--- a/Modules/_struct.c
+++ b/Modules/_struct.c
@@ -12,6 +12,9 @@
 #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]
@@ -80,6 +83,9 @@ typedef struct { char c; int x; } st_int;
 typedef struct { char c; long x; } st_long;
 typedef struct { char c; float x; } st_float;
 typedef struct { char c; double x; } st_double;
+#ifdef Py_HAVE_C_COMPLEX
+typedef struct { char c; double complex x; } st_double_complex;
+#endif
 typedef struct { char c; void *x; } st_void_p;
 typedef struct { char c; size_t x; } st_size_t;
 typedef struct { char c; _Bool x; } st_bool;
@@ -89,6 +95,9 @@ typedef struct { char c; _Bool x; } st_bool;
 #define LONG_ALIGN (sizeof(st_long) - sizeof(long))
 #define FLOAT_ALIGN (sizeof(st_float) - sizeof(float))
 #define DOUBLE_ALIGN (sizeof(st_double) - sizeof(double))
+#ifdef Py_HAVE_C_COMPLEX
+#define DOUBLE_COMPLEX_ALIGN (sizeof(st_double_complex) - sizeof(double complex))
+#endif
 #define VOID_P_ALIGN (sizeof(st_void_p) - sizeof(void *))
 #define SIZE_T_ALIGN (sizeof(st_size_t) - sizeof(size_t))
 #define BOOL_ALIGN (sizeof(st_bool) - sizeof(_Bool))
@@ -518,6 +527,17 @@ nu_double(_structmodulestate *state, const char *p, const formatdef *f)
     return PyFloat_FromDouble(x);
 }
 
+#ifdef Py_HAVE_C_COMPLEX
+static PyObject *
+nu_double_complex(_structmodulestate *state, const char *p, const formatdef *f)
+{
+    double complex x;
+
+    memcpy(&x, p, sizeof(x));
+    return PyComplex_FromDoubles(creal(x), cimag(x));
+}
+#endif
+
 static PyObject *
 nu_void_p(_structmodulestate *state, const char *p, const formatdef *f)
 {
@@ -791,6 +811,24 @@ np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
     return 0;
 }
 
+#ifdef Py_HAVE_C_COMPLEX
+static int
+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);
+
+    if (c.real == -1 && PyErr_Occurred()) {
+        PyErr_SetString(state->StructError,
+                        "required argument is not a complex");
+        return -1;
+    }
+    memcpy(p, (char *)&x, sizeof(x));
+    return 0;
+}
+#endif
+
 static int
 np_void_p(_structmodulestate *state, char *p, PyObject *v, const formatdef *f)
 {
@@ -829,6 +867,9 @@ static const formatdef native_table[] = {
     {'e',       sizeof(short),  SHORT_ALIGN,    nu_halffloat,   np_halffloat},
     {'f',       sizeof(float),  FLOAT_ALIGN,    nu_float,       np_float},
     {'d',       sizeof(double), DOUBLE_ALIGN,   nu_double,      np_double},
+#ifdef Py_HAVE_C_COMPLEX
+    {'C',       sizeof(double complex), DOUBLE_COMPLEX_ALIGN, nu_double_complex, np_double_complex},
+#endif
     {'P',       sizeof(void *), VOID_P_ALIGN,   nu_void_p,      np_void_p},
     {0}
 };

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

Linked PRs

Activity

  1. added 3 commits that reference this issue on Jul 11, 2024
  2. added a commit that references this issue on Oct 7, 2024
  3. vstinner commented on Oct 7, 2024

    @vstinner
    Member

    Implemented by change 7487db4.

  4. efimov-mikhail commented on Oct 10, 2024

    @efimov-mikhail
    Member

    IMO, we should provide a support for long double complex in struct module.
    For now there is a little inconvience:

    -> % cat struct_calcsize_complex.py
    import struct
    print(struct.calcsize("d"))
    print(struct.calcsize("C"))
    print(struct.calcsize("E"))
    print(struct.calcsize("F"))
    
    -> % ./python struct_calcsize_complex.py
    8
    16
    8
    Traceback (most recent call last):
      File "/home/mikhail.efimov/projects/cpython/struct_calcsize_complex.py", line 5, in <module>
        print(struct.calcsize("F"))
              ~~~~~~~~~~~~~~~^^^^^
    struct.error: bad char in struct format
    
  5. skirpichev commented on Oct 10, 2024

    @skirpichev
    MemberAuthor

    we should provide a support for long double complex in struct module.

    But we don't have support for long double here. I don't know for sure why it wasn't here historically, will dig into it.

    Probably together with support for long double - a support for long double complex will also make sense.

  6. efimov-mikhail commented on Oct 10, 2024

    @efimov-mikhail
    Member

    Even long double is not supported?
    Probably, there is not by intention, just because a lack of time.

    It seems like support for long double complex now is partial.
    In come cases they are allowed (call csqrtl, for example), but in other they aren't (struct module).
    I've just wanted to emphasize this.

  7. skirpichev commented on Oct 10, 2024

    @skirpichev
    MemberAuthor

    Probably, there is not by intention

    Maybe is. The long double is a more complex beast: https://en.wikipedia.org/wiki/Long_double

  8. vstinner commented on Oct 10, 2024

    @vstinner
    Member

    I would prefer to not support long double, it looks complicated for little usage.

  9. skirpichev commented on Oct 12, 2024

    @skirpichev
    MemberAuthor

    @vstinner, I don't think it's too complicated. On technical side we, probably, have to add only PyFloat_Pack/Unpack16() and PyFloat_Pack/Unpack10() functions for the IEEE 754 quadruples and the x86 extended precision format. Say, "m" and "o" format codes for the struct module.

    • "m" - like "f" and "d" will use IEEE binary128 for the packed representation;
    • "o" - like "e" will use x86 extended precision format for native conversion as well.

    These are popular standards and it's nice to have conversion helpers, available in the Python.

    The "F" format for the long double complex type will be implemented like "E" and "C", i.e. using IEEE binary128.

    If that sounds, I'll open an issue. Hardly discussion on d.p.o does make sense.

  10. added a commit that references this issue on Mar 29, 2025
  11. added a commit that references this issue on Apr 1, 2025
  12. 26 remaining items

  13. skirpichev commented on May 4, 2025

    @skirpichev
    MemberAuthor

    I think this is done. The ctypes module has own issue.

  14. added 2 commits that reference this issue on Jul 12, 2025
  15. added 3 commits that reference this issue on Jun 26, 2026
  16. vstinner commented on Jun 30, 2026

    @vstinner
    Member

    @skirpichev: All PRs attached to this issues are merged. Can we close this issue?

  17. skirpichev commented on Jun 30, 2026

    @skirpichev
    MemberAuthor

    I think so :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-featureA feature request or enhancement

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions