Skip to content

Commit 6988ff0

Browse files
skirpichevnineteendopicnixzvstinner
authored
gh-61103: Support double complex (_Complex) type in ctypes (#120894)
Example: ```pycon >>> import ctypes >>> ctypes.__STDC_IEC_559_COMPLEX__ 1 >>> libm = ctypes.CDLL('libm.so.6') >>> libm.clog.argtypes = [ctypes.c_double_complex] >>> libm.clog.restype = ctypes.c_double_complex >>> libm.clog(1+1j) (0.34657359027997264+0.7853981633974483j) ``` Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent a0b8b34 commit 6988ff0

File tree

17 files changed

+316
-17
lines changed

17 files changed

+316
-17
lines changed

Doc/library/ctypes.rst

+18
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,16 @@ Fundamental data types
266266
(1)
267267
The constructor accepts any object with a truth value.
268268

269+
Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported, the following
270+
complex types are available:
271+
272+
+----------------------------------+---------------------------------+-----------------+
273+
| ctypes type | C type | Python type |
274+
+==================================+=================================+=================+
275+
| :class:`c_double_complex` | :c:expr:`double complex` | complex |
276+
+----------------------------------+---------------------------------+-----------------+
277+
278+
269279
All these types can be created by calling them with an optional initializer of
270280
the correct type and value::
271281

@@ -2284,6 +2294,14 @@ These are the fundamental ctypes data types:
22842294
optional float initializer.
22852295

22862296

2297+
.. class:: c_double_complex
2298+
2299+
Represents the C :c:expr:`double complex` datatype, if available. The
2300+
constructor accepts an optional :class:`complex` initializer.
2301+
2302+
.. versionadded:: 3.14
2303+
2304+
22872305
.. class:: c_int
22882306

22892307
Represents the C :c:expr:`signed int` datatype. The constructor accepts an

Lib/ctypes/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ class c_longdouble(_SimpleCData):
205205
if sizeof(c_longdouble) == sizeof(c_double):
206206
c_longdouble = c_double
207207

208+
try:
209+
class c_double_complex(_SimpleCData):
210+
_type_ = "C"
211+
except AttributeError:
212+
pass
213+
208214
if _calcsize("l") == _calcsize("q"):
209215
# if long and long long have the same size, make c_longlong an alias for c_long
210216
c_longlong = c_long

Lib/test/test_ctypes/test_libc.py

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ctypes
12
import math
23
import unittest
34
from ctypes import (CDLL, CFUNCTYPE, POINTER, create_string_buffer, sizeof,
@@ -21,6 +22,17 @@ def test_sqrt(self):
2122
self.assertEqual(lib.my_sqrt(4.0), 2.0)
2223
self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0))
2324

25+
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
26+
"requires C11 complex type")
27+
def test_csqrt(self):
28+
lib.my_csqrt.argtypes = ctypes.c_double_complex,
29+
lib.my_csqrt.restype = ctypes.c_double_complex
30+
self.assertEqual(lib.my_csqrt(4), 2+0j)
31+
self.assertAlmostEqual(lib.my_csqrt(-1+0.01j),
32+
0.004999937502734214+1.0000124996093955j)
33+
self.assertAlmostEqual(lib.my_csqrt(-1-0.01j),
34+
0.004999937502734214-1.0000124996093955j)
35+
2436
def test_qsort(self):
2537
comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char))
2638
lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc

Lib/test/test_ctypes/test_numbers.py

+73-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import array
2+
import ctypes
23
import struct
34
import sys
45
import unittest
6+
from itertools import combinations
7+
from math import copysign, isnan
58
from operator import truth
69
from ctypes import (byref, sizeof, alignment,
710
c_char, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
@@ -38,8 +41,55 @@ def valid_ranges(*types):
3841
signed_ranges = valid_ranges(*signed_types)
3942
bool_values = [True, False, 0, 1, -1, 5000, 'test', [], [1]]
4043

44+
class IntLike:
45+
def __int__(self):
46+
return 2
47+
48+
class IndexLike:
49+
def __index__(self):
50+
return 2
51+
52+
class FloatLike:
53+
def __float__(self):
54+
return 2.0
55+
56+
class ComplexLike:
57+
def __complex__(self):
58+
return 1+1j
59+
60+
61+
INF = float("inf")
62+
NAN = float("nan")
63+
4164

4265
class NumberTestCase(unittest.TestCase):
66+
# from Lib/test/test_complex.py
67+
def assertFloatsAreIdentical(self, x, y):
68+
"""assert that floats x and y are identical, in the sense that:
69+
(1) both x and y are nans, or
70+
(2) both x and y are infinities, with the same sign, or
71+
(3) both x and y are zeros, with the same sign, or
72+
(4) x and y are both finite and nonzero, and x == y
73+
74+
"""
75+
msg = 'floats {!r} and {!r} are not identical'
76+
77+
if isnan(x) or isnan(y):
78+
if isnan(x) and isnan(y):
79+
return
80+
elif x == y:
81+
if x != 0.0:
82+
return
83+
# both zero; check that signs match
84+
elif copysign(1.0, x) == copysign(1.0, y):
85+
return
86+
else:
87+
msg += ': zeros have different signs'
88+
self.fail(msg.format(x, y))
89+
90+
def assertComplexesAreIdentical(self, x, y):
91+
self.assertFloatsAreIdentical(x.real, y.real)
92+
self.assertFloatsAreIdentical(x.imag, y.imag)
4393

4494
def test_default_init(self):
4595
# default values are set to zero
@@ -86,28 +136,39 @@ def test_byref(self):
86136
def test_floats(self):
87137
# c_float and c_double can be created from
88138
# Python int and float
89-
class FloatLike:
90-
def __float__(self):
91-
return 2.0
92139
f = FloatLike()
93140
for t in float_types:
94141
self.assertEqual(t(2.0).value, 2.0)
95142
self.assertEqual(t(2).value, 2.0)
96143
self.assertEqual(t(2).value, 2.0)
97144
self.assertEqual(t(f).value, 2.0)
98145

146+
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
147+
"requires C11 complex type")
148+
def test_complex(self):
149+
for t in [ctypes.c_double_complex]:
150+
self.assertEqual(t(1).value, 1+0j)
151+
self.assertEqual(t(1.0).value, 1+0j)
152+
self.assertEqual(t(1+0.125j).value, 1+0.125j)
153+
self.assertEqual(t(IndexLike()).value, 2+0j)
154+
self.assertEqual(t(FloatLike()).value, 2+0j)
155+
self.assertEqual(t(ComplexLike()).value, 1+1j)
156+
157+
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
158+
"requires C11 complex type")
159+
def test_complex_round_trip(self):
160+
# Ensure complexes transformed exactly. The CMPLX macro should
161+
# preserve special components (like inf/nan or signed zero).
162+
values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2,
163+
-3, INF, -INF, NAN], 2)]
164+
for z in values:
165+
with self.subTest(z=z):
166+
z2 = ctypes.c_double_complex(z).value
167+
self.assertComplexesAreIdentical(z, z2)
168+
99169
def test_integers(self):
100-
class FloatLike:
101-
def __float__(self):
102-
return 2.0
103170
f = FloatLike()
104-
class IntLike:
105-
def __int__(self):
106-
return 2
107171
d = IntLike()
108-
class IndexLike:
109-
def __index__(self):
110-
return 2
111172
i = IndexLike()
112173
# integers cannot be constructed from floats,
113174
# but from integer-like objects

Makefile.pre.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -3125,7 +3125,7 @@ MODULE_MATH_DEPS=$(srcdir)/Modules/_math.h
31253125
MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@
31263126
MODULE_UNICODEDATA_DEPS=$(srcdir)/Modules/unicodedata_db.h $(srcdir)/Modules/unicodename_db.h
31273127
MODULE__BLAKE2_DEPS=$(srcdir)/Modules/_blake2/impl/blake2-config.h $(srcdir)/Modules/_blake2/impl/blake2-impl.h $(srcdir)/Modules/_blake2/impl/blake2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2b-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2b-ref.c $(srcdir)/Modules/_blake2/impl/blake2b-round.h $(srcdir)/Modules/_blake2/impl/blake2b.c $(srcdir)/Modules/_blake2/impl/blake2s-load-sse2.h $(srcdir)/Modules/_blake2/impl/blake2s-load-sse41.h $(srcdir)/Modules/_blake2/impl/blake2s-load-xop.h $(srcdir)/Modules/_blake2/impl/blake2s-ref.c $(srcdir)/Modules/_blake2/impl/blake2s-round.h $(srcdir)/Modules/_blake2/impl/blake2s.c $(srcdir)/Modules/_blake2/blake2module.h $(srcdir)/Modules/hashlib.h
3128-
MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h
3128+
MODULE__CTYPES_DEPS=$(srcdir)/Modules/_ctypes/ctypes.h $(srcdir)/Modules/_complex.h
31293129
MODULE__CTYPES_TEST_DEPS=$(srcdir)/Modules/_ctypes/_ctypes_test_generated.c.h
31303130
MODULE__CTYPES_MALLOC_CLOSURE=@MODULE__CTYPES_MALLOC_CLOSURE@
31313131
MODULE__DECIMAL_DEPS=$(srcdir)/Modules/_decimal/docstrings.h @LIBMPDEC_INTERNAL@
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Support :c:expr:`double complex` C type in :mod:`ctypes` via
2+
:class:`~ctypes.c_double_complex` if compiler has C11 complex
3+
arithmetic. Patch by Sergey B Kirpichev.

Modules/_complex.h

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* Workarounds for buggy complex number arithmetic implementations. */
2+
3+
#ifndef Py_HAVE_C_COMPLEX
4+
# error "this header file should only be included if Py_HAVE_C_COMPLEX is defined"
5+
#endif
6+
7+
#include <complex.h>
8+
9+
/* Other compilers (than clang), that claims to
10+
implement C11 *and* define __STDC_IEC_559_COMPLEX__ - don't have
11+
issue with CMPLX(). This is specific to glibc & clang combination:
12+
https://sourceware.org/bugzilla/show_bug.cgi?id=26287
13+
14+
Here we fallback to using __builtin_complex(), available in clang
15+
v12+. Else CMPLX implemented following C11 6.2.5p13: "Each complex type
16+
has the same representation and alignment requirements as an array
17+
type containing exactly two elements of the corresponding real type;
18+
the first element is equal to the real part, and the second element
19+
to the imaginary part, of the complex number.
20+
*/
21+
#if !defined(CMPLX)
22+
# if defined(__clang__) && __has_builtin(__builtin_complex)
23+
# define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y))
24+
# else
25+
static inline double complex
26+
CMPLX(double real, double imag)
27+
{
28+
double complex z;
29+
((double *)(&z))[0] = real;
30+
((double *)(&z))[1] = imag;
31+
return z;
32+
}
33+
# endif
34+
#endif

Modules/_ctypes/_ctypes.c

+15-1
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,11 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type"
17501750
[clinic start generated code]*/
17511751
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/
17521752

1753+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
1754+
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCfuzZqQPXOv?g";
1755+
#else
17531756
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g";
1757+
#endif
17541758

17551759
/*[clinic input]
17561760
_ctypes.c_wchar_p.from_param as c_wchar_p_from_param
@@ -2226,7 +2230,17 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
22262230
goto error;
22272231
}
22282232

2229-
stginfo->ffi_type_pointer = *fmt->pffi_type;
2233+
if (!fmt->pffi_type->elements) {
2234+
stginfo->ffi_type_pointer = *fmt->pffi_type;
2235+
}
2236+
else {
2237+
stginfo->ffi_type_pointer.size = fmt->pffi_type->size;
2238+
stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment;
2239+
stginfo->ffi_type_pointer.type = fmt->pffi_type->type;
2240+
stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type));
2241+
memcpy(stginfo->ffi_type_pointer.elements,
2242+
fmt->pffi_type->elements, 2 * sizeof(ffi_type));
2243+
}
22302244
stginfo->align = fmt->pffi_type->alignment;
22312245
stginfo->length = 0;
22322246
stginfo->size = fmt->pffi_type->size;

Modules/_ctypes/_ctypes_test.c

+13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313

1414
#include <Python.h>
1515

16+
#include <ffi.h> // FFI_TARGET_HAS_COMPLEX_TYPE
17+
18+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
19+
# include "../_complex.h" // csqrt()
20+
# undef I // for _ctypes_test_generated.c.h
21+
#endif
1622
#include <stdio.h> // printf()
1723
#include <stdlib.h> // qsort()
1824
#include <string.h> // memset()
@@ -443,6 +449,13 @@ EXPORT(double) my_sqrt(double a)
443449
return sqrt(a);
444450
}
445451

452+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
453+
EXPORT(double complex) my_csqrt(double complex a)
454+
{
455+
return csqrt(a);
456+
}
457+
#endif
458+
446459
EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*))
447460
{
448461
qsort(base, num, width, compare);

Modules/_ctypes/callproc.c

+7
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ module _ctypes
105105
#include "pycore_global_objects.h"// _Py_ID()
106106
#include "pycore_traceback.h" // _PyTraceback_Add()
107107

108+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
109+
#include "../_complex.h" // complex
110+
#endif
111+
108112
#include "clinic/callproc.c.h"
109113

110114
#define CTYPES_CAPSULE_NAME_PYMEM "_ctypes pymem"
@@ -651,6 +655,9 @@ union result {
651655
double d;
652656
float f;
653657
void *p;
658+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
659+
double complex C;
660+
#endif
654661
};
655662

656663
struct argument {

Modules/_ctypes/cfield.c

+33
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#include <ffi.h>
1515
#include "ctypes.h"
1616

17+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
18+
# include "../_complex.h" // complex
19+
#endif
1720

1821
#define CTYPES_CFIELD_CAPSULE_NAME_PYMEM "_ctypes/cfield.c pymem"
1922

@@ -1087,6 +1090,30 @@ d_get(void *ptr, Py_ssize_t size)
10871090
return PyFloat_FromDouble(val);
10881091
}
10891092

1093+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
1094+
static PyObject *
1095+
C_set(void *ptr, PyObject *value, Py_ssize_t size)
1096+
{
1097+
Py_complex c = PyComplex_AsCComplex(value);
1098+
1099+
if (c.real == -1 && PyErr_Occurred()) {
1100+
return NULL;
1101+
}
1102+
double complex x = CMPLX(c.real, c.imag);
1103+
memcpy(ptr, &x, sizeof(x));
1104+
_RET(value);
1105+
}
1106+
1107+
static PyObject *
1108+
C_get(void *ptr, Py_ssize_t size)
1109+
{
1110+
double complex x;
1111+
1112+
memcpy(&x, ptr, sizeof(x));
1113+
return PyComplex_FromDoubles(creal(x), cimag(x));
1114+
}
1115+
#endif
1116+
10901117
static PyObject *
10911118
d_set_sw(void *ptr, PyObject *value, Py_ssize_t size)
10921119
{
@@ -1592,6 +1619,9 @@ static struct fielddesc formattable[] = {
15921619
{ 'B', B_set, B_get, NULL},
15931620
{ 'c', c_set, c_get, NULL},
15941621
{ 'd', d_set, d_get, NULL, d_set_sw, d_get_sw},
1622+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
1623+
{ 'C', C_set, C_get, NULL},
1624+
#endif
15951625
{ 'g', g_set, g_get, NULL},
15961626
{ 'f', f_set, f_get, NULL, f_set_sw, f_get_sw},
15971627
{ 'h', h_set, h_get, NULL, h_set_sw, h_get_sw},
@@ -1642,6 +1672,9 @@ _ctypes_init_fielddesc(void)
16421672
case 'B': fd->pffi_type = &ffi_type_uchar; break;
16431673
case 'c': fd->pffi_type = &ffi_type_schar; break;
16441674
case 'd': fd->pffi_type = &ffi_type_double; break;
1675+
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
1676+
case 'C': fd->pffi_type = &ffi_type_complex_double; break;
1677+
#endif
16451678
case 'g': fd->pffi_type = &ffi_type_longdouble; break;
16461679
case 'f': fd->pffi_type = &ffi_type_float; break;
16471680
case 'h': fd->pffi_type = &ffi_type_sshort; break;

0 commit comments

Comments
 (0)