Skip to content

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

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

Open
skirpichev opened this issue Jul 2, 2024 · 20 comments
Open

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

skirpichev opened this issue Jul 2, 2024 · 20 comments
Labels
extension-modules C modules in the Modules dir type-feature A feature request or enhancement

Comments

@skirpichev
Copy link
Member

skirpichev commented Jul 2, 2024

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

@skirpichev skirpichev added the type-feature A feature request or enhancement label Jul 2, 2024
skirpichev added a commit to skirpichev/cpython that referenced this issue Jul 11, 2024
skirpichev added a commit to skirpichev/cpython that referenced this issue Jul 11, 2024
skirpichev added a commit to skirpichev/cpython that referenced this issue Jul 11, 2024
vstinner added a commit that referenced this issue Oct 7, 2024
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
@vstinner
Copy link
Member

vstinner commented Oct 7, 2024

Implemented by change 7487db4.

@vstinner vstinner closed this as completed Oct 7, 2024
@efimov-mikhail
Copy link
Contributor

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

@skirpichev
Copy link
Member Author

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.

@efimov-mikhail
Copy link
Contributor

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.

@skirpichev
Copy link
Member Author

Probably, there is not by intention

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

@vstinner
Copy link
Member

vstinner commented Oct 10, 2024

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

@skirpichev
Copy link
Member Author

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

skirpichev added a commit to skirpichev/cpython that referenced this issue Mar 29, 2025
AA-Turner added a commit that referenced this issue Apr 1, 2025
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
seehwan pushed a commit to seehwan/cpython that referenced this issue Apr 16, 2025
)

Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
@dalcinl
Copy link

dalcinl commented Apr 23, 2025

@skirpichev @vstinner Is the choice of the "E" and "C" something that could be reconsidered?

The ~20 years old NumPy project have been using the typecodes "F" for float complex, "D" for double complex, and "G" for long double complex, that is, the uppercase of "f" for float, "d" for double, and "g" for long double. It would be a pity to deviate for these very well established conventions.

In [1]: import numpy as np

In [2]: np.typecodes
Out[2]: 
{'Character': 'c',
 'Integer': 'bhilqnp',
 'UnsignedInteger': 'BHILQNP',
 'Float': 'efdg',
 'Complex': 'FDG',
 'AllInteger': 'bBhHiIlLqQnNpP',
 'AllFloat': 'efdgFDG',
 'Datetime': 'Mm',
 'All': '?bhilqnpBHILQNPefdgFDGSUVOMm'}

In [3]: np.typename("F")
Out[3]: 'complex single precision'

In [4]: np.typename("D")
Out[4]: 'complex double precision'

In [5]: np.typename("G")
Out[5]: 'complex long double precision'

On a somewhat less urgent matter, tying the availability of these format characters in struct to the support of C99 complex is somewhat unnecessary restrictive. IIRC, the C standard mandates that if complex types are supported, its layout should be compatible to an array/struct of two successive floating items . As the struct module deals with binary packing/unpacking, and not arithmetics, I would argue that the float/double complex format characters could be unconditionally supported on all platforms, and such support should be easy to implement.

PS: If support for long double [complex] is ever added into Python, then it would be good to use "g" and "G", or otherwise reserve these two formats as they are used by NumPy. I would also reserve "e" and "E" for a half precision real and complex. NumPy currently uses "e" for float16_t, but there is no half precision complex yet.

@skirpichev
Copy link
Member Author

@dalcinl, thanks. Yes, I think it could be reconsidered.

Codes for the struct module chosen to be consistent wrt codes in the ctypes module. Both could and, probably, should be adjusted. @vstinner ?

On a somewhat less urgent matter, tying the availability of these format characters in struct to the support of C99 complex is somewhat unnecessary restrictive.

Maybe it does make sense for the struct module, but not for the ctypes (as the standard doesn't say anything about directly passing a complex as argument or return value to a function call).

its layout should be compatible to an array/struct of two successive floating items .

Not with struct (as structs aren't arrays - they can have padding between members).

@skirpichev skirpichev self-assigned this Apr 23, 2025
@skirpichev skirpichev reopened this Apr 23, 2025
@dalcinl
Copy link

dalcinl commented Apr 23, 2025

@dalcinl, thanks. Yes, I think it could be reconsidered.

Codes for the struct module chosen to be consistent wrt codes in the ctypes module. Both could and, probably, should be adjusted. @vstinner ?

Oh, I missed that. If ctypes is using E and C, we cannot change that for backward compatibility reasons, but maybe we can make ctypes accept F and D on input.

EDIT: Or maybe we can? Should ctypes's _type_ slots be considered a private implementation detail as per usual naming conventions?

Maybe it does make sense for the struct module, but not for the ctypes (as the standard doesn't say anything about directly passing a complex as argument or return value to a function call).

You are definitely right.

its layout should be compatible to an array/struct of two successive floating items .

Not with struct (as structs aren't arrays - they can have padding between members).

You may be technically right, but I think that every compiler out there satisfies sizeof(Py_complex) == 2*sizeof(double), maybe simply because there is no point in adding padding between to slots of the same POD type. Anyway, to avoid confusions, let me amend my comment as array of two floating elements.

@vstinner
Copy link
Member

Using the same format letter in ctypes and numpy sounds like a good idea. Python 3.14.0 final is not released yet, so we can still change ctypes formats.

@skirpichev
Copy link
Member Author

If ctypes is using E and C, we cannot change that for backward compatibility reasons

No, it's still an option as ctypes support also will be available only in 3.14.

Should ctypes's type slots be considered a private implementation detail

This slot referenced in sphinx docs, though not actual type codes.

maybe simply because there is no point in adding padding between to slots of the same POD type.

Yeah, I don't know a real-life example, when it's wrong. But this expectation is not backed by the standard.

@skirpichev
Copy link
Member Author

Using the same format letter in ctypes and numpy sounds like a good idea. Python 3.14.0 final is not released yet, so we can still change ctypes formats.

Good, I'll prepare a patch.

skirpichev added a commit to skirpichev/cpython that referenced this issue Apr 23, 2025
…ctypes modules

F - for float _Complex
D - for double _Complex
G - for long double _Complex (not supported by the struct module)
@skirpichev
Copy link
Member Author

PR is ready for review: #132827

I'll prepare another to enable complex types unconditionally for the struct module.

@dalcinl
Copy link

dalcinl commented Apr 23, 2025

I'll prepare another to enable complex types unconditionally for the struct module.

Regarding ctypes support, I'm wondering whether you truly need the C compiler to support complex, or you can just rely on complex support in the FFI library and just use float[2]/double[2] in ctypes' C code. The complex type size is twice the size of the real type, and the alignment is the same.
In short, we can get rid of Py_HAVE_C_COMPLEX and rely only in Py_FFI_SUPPORT_C_COMPLEX.

@skirpichev
Copy link
Member Author

you can just rely on complex support in the FFI library

We can, but this support requires complex support on the compiler side.

The complex type size is twice the size of the real type, and the alignment is the same.

The problem is passing arguments (or return values). For complex types it's usually not just a pointer to array of suitable real type.

In short, we can get rid of Py_HAVE_C_COMPLEX and rely only in Py_FFI_SUPPORT_C_COMPLEX.

That does make sense, but only for the ctypes module.

@dalcinl
Copy link

dalcinl commented Apr 23, 2025

We can, but this support requires complex support on the compiler side.

Yes, it requires complex support when the FFI library is built, but not when CPython is built (if using a different compiler).

The problem is passing arguments (or return values)

But if the FFI library have complex support, it knows how to do it. Does python still need complex support from the C compiler? If yes, what for? What am I missing?

In short, we can get rid of Py_HAVE_C_COMPLEX and rely only in Py_FFI_SUPPORT_C_COMPLEX.

That does make sense, but only for the ctypes module.

The point I'm trying to make is the following: No C code in CPython source tree needs to use float complex or double complex explicitly. Both the struct and ctypes modules can be implemented just using float[2] and double[2]. Do you agree with my claim or should I back my claim with a prototype patch?

vstinner pushed a commit that referenced this issue Apr 23, 2025
…#132827)

* F - for float _Complex
* D - for double _Complex
* G - for long double _Complex (not supported by the struct module)
skirpichev added a commit to skirpichev/cpython that referenced this issue Apr 24, 2025
It seems, no code actually uses these names, only sizes of the unnamed
union members are important.  Though, I think it's good to be here
consistent wrt type codes ('g' for long double, etc).

This amends 85f89cb.
skirpichev added a commit to skirpichev/cpython that referenced this issue Apr 24, 2025
…struct module

Each complex type interpreted as an array type containing exactly two
elements of the corresponding real type (real and imaginary parts,
respectively).
@skirpichev
Copy link
Member Author

Ok, here is a small pr that amends 85f89cb: #132863

PR with unconditional support for complex types in the struct module: #132864

Both the struct and ctypes modules can be implemented just using float[2] and double[2].

You are correct for the struct module, see patch above.

In principle, we could implement getters/setters in the ctypes module just like that (use arrays and indexing instead of CMPLX/creal/cimag). Feel free to submit a patch if you think this will simplify something (e.g. we can get rid of _complex.h header).

But I'm not sure if this worth: you can't pass arguments & unpack return values without support for complex types both from platform and libffi. I tried to explain why it so - above, here is another attempt: #61103 (comment)

@skirpichev skirpichev removed their assignment Apr 24, 2025
vstinner pushed a commit that referenced this issue Apr 24, 2025
It seems, no code actually uses these names, only sizes of the unnamed
union members are important.  Though, I think it's good to be here
consistent wrt type codes ('g' for long double, etc).

This amends 85f89cb.
@skirpichev
Copy link
Member Author

Patch for ctypes (on top of #132864) attached. It has some pros: we can remove Modules/_complex.h header and some <complex.h> includes. Not sure it worth. @vstinner ?

a patch
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 886bd7ae1c..fc1bd0d819 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -3304,7 +3304,7 @@ MODULE_CMATH_DEPS=$(srcdir)/Modules/_math.h
 MODULE_MATH_DEPS=$(srcdir)/Modules/_math.h
 MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@
 MODULE_UNICODEDATA_DEPS=$(srcdir)/Modules/unicodedata_db.h $(srcdir)/Modules/unicodename_db.h
-MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h $(srcdir)/Modules/_complex.h
+MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h
 MODULE__CTYPES_TEST_DEPS=$(srcdir)/Modules/_ctypes/_ctypes_test_generated.c.h
 MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@
 MODULE__DECIMAL_DEPS=$(srcdir)/Modules/_decimal/docstrings.h @LIBMPDEC_INTERNAL@
diff --git a/Modules/_complex.h b/Modules/_complex.h
deleted file mode 100644
index 28d4a32794..0000000000
--- a/Modules/_complex.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Workarounds for buggy complex number arithmetic implementations. */
-
-#ifndef Py_HAVE_C_COMPLEX
-#  error "this header file should only be included if Py_HAVE_C_COMPLEX is defined"
-#endif
-
-#include <complex.h>
-
-/* Other compilers (than clang), that claims to
-   implement C11 *and* define __STDC_IEC_559_COMPLEX__ - don't have
-   issue with CMPLX().  This is specific to glibc & clang combination:
-   https://sourceware.org/bugzilla/show_bug.cgi?id=26287
-
-   Here we fallback to using __builtin_complex(), available in clang
-   v12+.  Else CMPLX implemented following C11 6.2.5p13: "Each complex type
-   has the same representation and alignment requirements as an array
-   type containing exactly two elements of the corresponding real type;
-   the first element is equal to the real part, and the second element
-   to the imaginary part, of the complex number.
- */
-#if !defined(CMPLX)
-#  if defined(__clang__) && __has_builtin(__builtin_complex)
-#    define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y))
-#    define CMPLXF(x, y) __builtin_complex ((float) (x), (float) (y))
-#    define CMPLXL(x, y) __builtin_complex ((long double) (x), (long double) (y))
-#  else
-static inline double complex
-CMPLX(double real, double imag)
-{
-    double complex z;
-    ((double *)(&z))[0] = real;
-    ((double *)(&z))[1] = imag;
-    return z;
-}
-
-static inline float complex
-CMPLXF(float real, float imag)
-{
-    float complex z;
-    ((float *)(&z))[0] = real;
-    ((float *)(&z))[1] = imag;
-    return z;
-}
-
-static inline long double complex
-CMPLXL(long double real, long double imag)
-{
-    long double complex z;
-    ((long double *)(&z))[0] = real;
-    ((long double *)(&z))[1] = imag;
-    return z;
-}
-#  endif
-#endif
diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c
index 2268072545..557bfa85b1 100644
--- a/Modules/_ctypes/_ctypes_test.c
+++ b/Modules/_ctypes/_ctypes_test.c
@@ -24,7 +24,7 @@
 #endif
 
 #if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-#  include "../_complex.h"        // csqrt()
+#  include <complex.h>            // csqrt()
 #  undef I                        // for _ctypes_test_generated.c.h
 #endif
 #include <stdio.h>                // printf()
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index 83471aa3a4..48589c9892 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -103,9 +103,6 @@ module _ctypes
 #include "pycore_global_objects.h"// _Py_ID()
 #include "pycore_traceback.h"     // _PyTraceback_Add()
 
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-#include "../_complex.h"          // complex
-#endif
 #define clinic_state() (get_module_state(module))
 #include "clinic/callproc.c.h"
 #undef clinic_state
@@ -653,9 +650,9 @@ union result {
     float f;
     void *p;
 #if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-    double complex D;
-    float complex F;
-    long double complex G;
+    double D[2];
+    float F[2];
+    long double G[2];
 #endif
 };
 
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index 580ea18af2..d7e4bfdb82 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -14,10 +14,6 @@
 #include <ffi.h>
 #include "ctypes.h"
 
-#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-#  include "../_complex.h"        // complex
-#endif
-
 #define CTYPES_CFIELD_CAPSULE_NAME_PYMEM "_ctypes/cfield.c pymem"
 
 /*[clinic input]
@@ -768,13 +764,13 @@ d_get(void *ptr, Py_ssize_t size)
 static PyObject *
 D_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
-    assert(NUM_BITS(size) || (size == sizeof(double complex)));
+    assert(NUM_BITS(size) || (size == 2*sizeof(double)));
     Py_complex c = PyComplex_AsCComplex(value);
 
     if (c.real == -1 && PyErr_Occurred()) {
         return NULL;
     }
-    double complex x = CMPLX(c.real, c.imag);
+    double x[2] = {c.real, c.imag};
     memcpy(ptr, &x, sizeof(x));
     _RET(value);
 }
@@ -782,24 +778,24 @@ D_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 D_get(void *ptr, Py_ssize_t size)
 {
-    assert(NUM_BITS(size) || (size == sizeof(double complex)));
-    double complex x;
+    assert(NUM_BITS(size) || (size == 2*sizeof(double)));
+    double x[2];
 
     memcpy(&x, ptr, sizeof(x));
-    return PyComplex_FromDoubles(creal(x), cimag(x));
+    return PyComplex_FromDoubles(x[0], x[1]);
 }
 
 /* F: float complex */
 static PyObject *
 F_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
-    assert(NUM_BITS(size) || (size == sizeof(float complex)));
+    assert(NUM_BITS(size) || (size == 2*sizeof(float)));
     Py_complex c = PyComplex_AsCComplex(value);
 
     if (c.real == -1 && PyErr_Occurred()) {
         return NULL;
     }
-    float complex x = CMPLXF((float)c.real, (float)c.imag);
+    float x[2] = {(float)c.real, (float)c.imag};
     memcpy(ptr, &x, sizeof(x));
     _RET(value);
 }
@@ -807,24 +803,24 @@ F_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 F_get(void *ptr, Py_ssize_t size)
 {
-    assert(NUM_BITS(size) || (size == sizeof(float complex)));
-    float complex x;
+    assert(NUM_BITS(size) || (size == 2*sizeof(float)));
+    float x[2];
 
     memcpy(&x, ptr, sizeof(x));
-    return PyComplex_FromDoubles(crealf(x), cimagf(x));
+    return PyComplex_FromDoubles(x[0], x[1]);
 }
 
 /* G: long double complex */
 static PyObject *
 G_set(void *ptr, PyObject *value, Py_ssize_t size)
 {
-    assert(NUM_BITS(size) || (size == sizeof(long double complex)));
+    assert(NUM_BITS(size) || (size == 2*sizeof(long double)));
     Py_complex c = PyComplex_AsCComplex(value);
 
     if (c.real == -1 && PyErr_Occurred()) {
         return NULL;
     }
-    long double complex x = CMPLXL(c.real, c.imag);
+    long double x[2] = {c.real, c.imag};
     memcpy(ptr, &x, sizeof(x));
     _RET(value);
 }
@@ -832,11 +828,11 @@ G_set(void *ptr, PyObject *value, Py_ssize_t size)
 static PyObject *
 G_get(void *ptr, Py_ssize_t size)
 {
-    assert(NUM_BITS(size) || (size == sizeof(long double complex)));
-    long double complex x;
+    assert(NUM_BITS(size) || (size == 2*sizeof(long double)));
+    long double x[2];
 
     memcpy(&x, ptr, sizeof(x));
-    return PyComplex_FromDoubles((double)creall(x), (double)cimagl(x));
+    return PyComplex_FromDoubles((double)x[0], (double)x[1]);
 }
 #endif
 
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index 3b6d390728..30814dc43e 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -12,7 +12,6 @@
 // Do we support C99 complex types in ffi?
 // For Apple's libffi, this must be determined at runtime (see gh-128156).
 #if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-#   include "../_complex.h"       // complex
 #   if USING_APPLE_OS_LIBFFI && defined(__has_builtin)
 #       if __has_builtin(__builtin_available)
 #           define Py_FFI_COMPLEX_AVAILABLE __builtin_available(macOS 10.15, *)
@@ -494,9 +493,9 @@ struct tagPyCArgObject {
         float f;
         void *p;
 #if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
-        double complex D;
-        float complex F;
-        long double complex G;
+        double D[2];
+        float F[2];
+        long double G[2];
 #endif
     } value;
     PyObject *obj;

@dalcinl
Copy link

dalcinl commented Apr 24, 2025

Your patch looks great! I think you can still get rid of defined(Py_HAVE_C_COMPLEX) in a couple places (Modules/_ctypes/ctypes.h and Modules/_ctypes/callproc.c).

@picnixz picnixz added the extension-modules C modules in the Modules dir label Apr 24, 2025
skirpichev added a commit to skirpichev/cpython that referenced this issue May 1, 2025
…struct module

Each complex type interpreted as an array type containing exactly two
elements of the corresponding real type (real and imaginary parts,
respectively).
skirpichev added a commit to skirpichev/cpython that referenced this issue May 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
extension-modules C modules in the Modules dir type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

5 participants