Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6988ff0

Browse filesBrowse 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
Copy full SHA for 6988ff0

File tree

Expand file treeCollapse file tree

17 files changed

+316
-17
lines changed
Filter options
Expand file treeCollapse file tree

17 files changed

+316
-17
lines changed

‎Doc/library/ctypes.rst

Copy file name to clipboardExpand all lines: Doc/library/ctypes.rst
+18Lines changed: 18 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Lib/ctypes/__init__.py
+6Lines changed: 6 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_libc.py
+12Lines changed: 12 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Lib/test/test_ctypes/test_numbers.py
+73-12Lines changed: 73 additions & 12 deletions
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

Copy file name to clipboardExpand all lines: Makefile.pre.in
+1-1Lines changed: 1 addition & 1 deletion
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@
+3Lines changed: 3 additions & 0 deletions
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

Copy file name to clipboard
+34Lines changed: 34 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Modules/_ctypes/_ctypes.c
+15-1Lines changed: 15 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: Modules/_ctypes/_ctypes_test.c
+13Lines changed: 13 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Modules/_ctypes/callproc.c
+7Lines changed: 7 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Modules/_ctypes/cfield.c
+33Lines changed: 33 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.