gh-61103: Support float and long double complex types in ctypes module (#121248)

This amends 6988ff02a5: memory allocation for
stginfo->ffi_type_pointer.elements in PyCSimpleType_init() should be
more generic (perhaps someday fmt->pffi_type->elements will be not a
two-elements array).

It should finally resolve #61103.

Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
Sergey B Kirpichev 2024-07-03 12:08:11 +03:00 committed by GitHub
parent c9bdfbe868
commit 51c4a324c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 135 additions and 10 deletions

View File

@ -272,8 +272,12 @@ complex types are available:
+----------------------------------+---------------------------------+-----------------+
| ctypes type | C type | Python type |
+==================================+=================================+=================+
| :class:`c_float_complex` | :c:expr:`float complex` | complex |
+----------------------------------+---------------------------------+-----------------+
| :class:`c_double_complex` | :c:expr:`double complex` | complex |
+----------------------------------+---------------------------------+-----------------+
| :class:`c_longdouble_complex` | :c:expr:`long double complex` | complex |
+----------------------------------+---------------------------------+-----------------+
All these types can be created by calling them with an optional initializer of
@ -2302,6 +2306,22 @@ These are the fundamental ctypes data types:
.. versionadded:: 3.14
.. class:: c_float_complex
Represents the C :c:expr:`float complex` datatype, if available. The
constructor accepts an optional :class:`complex` initializer.
.. versionadded:: 3.14
.. class:: c_longdouble_complex
Represents the C :c:expr:`long double complex` datatype, if available. The
constructor accepts an optional :class:`complex` initializer.
.. versionadded:: 3.14
.. class:: c_int
Represents the C :c:expr:`signed int` datatype. The constructor accepts an

View File

@ -208,6 +208,10 @@ if sizeof(c_longdouble) == sizeof(c_double):
try:
class c_double_complex(_SimpleCData):
_type_ = "C"
class c_float_complex(_SimpleCData):
_type_ = "E"
class c_longdouble_complex(_SimpleCData):
_type_ = "F"
except AttributeError:
pass

View File

@ -33,6 +33,20 @@ class LibTest(unittest.TestCase):
self.assertAlmostEqual(lib.my_csqrt(-1-0.01j),
0.004999937502734214-1.0000124996093955j)
lib.my_csqrtf.argtypes = ctypes.c_float_complex,
lib.my_csqrtf.restype = ctypes.c_float_complex
self.assertAlmostEqual(lib.my_csqrtf(-1+0.01j),
0.004999937502734214+1.0000124996093955j)
self.assertAlmostEqual(lib.my_csqrtf(-1-0.01j),
0.004999937502734214-1.0000124996093955j)
lib.my_csqrtl.argtypes = ctypes.c_longdouble_complex,
lib.my_csqrtl.restype = ctypes.c_longdouble_complex
self.assertAlmostEqual(lib.my_csqrtl(-1+0.01j),
0.004999937502734214+1.0000124996093955j)
self.assertAlmostEqual(lib.my_csqrtl(-1-0.01j),
0.004999937502734214-1.0000124996093955j)
def test_qsort(self):
comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char))
lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc

View File

@ -146,7 +146,8 @@ class NumberTestCase(unittest.TestCase):
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
"requires C11 complex type")
def test_complex(self):
for t in [ctypes.c_double_complex]:
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
ctypes.c_longdouble_complex]:
self.assertEqual(t(1).value, 1+0j)
self.assertEqual(t(1.0).value, 1+0j)
self.assertEqual(t(1+0.125j).value, 1+0.125j)
@ -162,9 +163,10 @@ class NumberTestCase(unittest.TestCase):
values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2,
-3, INF, -INF, NAN], 2)]
for z in values:
with self.subTest(z=z):
z2 = ctypes.c_double_complex(z).value
self.assertComplexesAreIdentical(z, z2)
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
ctypes.c_longdouble_complex]:
with self.subTest(z=z, type=t):
self.assertComplexesAreIdentical(z, t(z).value)
def test_integers(self):
f = FloatLike()

View File

@ -1,3 +1,5 @@
Support :c:expr:`double complex` C type in :mod:`ctypes` via
:class:`~ctypes.c_double_complex` if compiler has C11 complex
arithmetic. Patch by Sergey B Kirpichev.
Support :c:expr:`float complex`, :c:expr:`double complex` and
:c:expr:`long double complex` C types in :mod:`ctypes` as
:class:`~ctypes.c_float_complex`, :class:`~ctypes.c_double_complex` and
:class:`~ctypes.c_longdouble_complex` if the compiler has C11 complex arithmetic.
Patch by Sergey B Kirpichev.

View File

@ -21,6 +21,8 @@
#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)
@ -30,5 +32,23 @@ CMPLX(double real, double imag)
((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

View File

@ -1751,7 +1751,7 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type"
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCfuzZqQPXOv?g";
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g";
#else
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g";
#endif
@ -2234,12 +2234,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
stginfo->ffi_type_pointer = *fmt->pffi_type;
}
else {
const size_t els_size = sizeof(fmt->pffi_type->elements);
stginfo->ffi_type_pointer.size = fmt->pffi_type->size;
stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment;
stginfo->ffi_type_pointer.type = fmt->pffi_type->type;
stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type));
stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size);
memcpy(stginfo->ffi_type_pointer.elements,
fmt->pffi_type->elements, 2 * sizeof(ffi_type));
fmt->pffi_type->elements, els_size);
}
stginfo->align = fmt->pffi_type->alignment;
stginfo->length = 0;

View File

@ -454,6 +454,16 @@ EXPORT(double complex) my_csqrt(double complex a)
{
return csqrt(a);
}
EXPORT(float complex) my_csqrtf(float complex a)
{
return csqrtf(a);
}
EXPORT(long double complex) my_csqrtl(long double complex a)
{
return csqrtl(a);
}
#endif
EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*))

View File

@ -657,6 +657,8 @@ union result {
void *p;
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
double complex C;
float complex E;
long double complex F;
#endif
};

View File

@ -1112,6 +1112,50 @@ C_get(void *ptr, Py_ssize_t size)
memcpy(&x, ptr, sizeof(x));
return PyComplex_FromDoubles(creal(x), cimag(x));
}
static PyObject *
E_set(void *ptr, PyObject *value, Py_ssize_t size)
{
Py_complex c = PyComplex_AsCComplex(value);
if (c.real == -1 && PyErr_Occurred()) {
return NULL;
}
float complex x = CMPLXF((float)c.real, (float)c.imag);
memcpy(ptr, &x, sizeof(x));
_RET(value);
}
static PyObject *
E_get(void *ptr, Py_ssize_t size)
{
float complex x;
memcpy(&x, ptr, sizeof(x));
return PyComplex_FromDoubles(crealf(x), cimagf(x));
}
static PyObject *
F_set(void *ptr, PyObject *value, Py_ssize_t size)
{
Py_complex c = PyComplex_AsCComplex(value);
if (c.real == -1 && PyErr_Occurred()) {
return NULL;
}
long double complex x = CMPLXL(c.real, c.imag);
memcpy(ptr, &x, sizeof(x));
_RET(value);
}
static PyObject *
F_get(void *ptr, Py_ssize_t size)
{
long double complex x;
memcpy(&x, ptr, sizeof(x));
return PyComplex_FromDoubles((double)creall(x), (double)cimagl(x));
}
#endif
static PyObject *
@ -1621,6 +1665,8 @@ static struct fielddesc formattable[] = {
{ 'd', d_set, d_get, NULL, d_set_sw, d_get_sw},
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
{ 'C', C_set, C_get, NULL},
{ 'E', E_set, E_get, NULL},
{ 'F', F_set, F_get, NULL},
#endif
{ 'g', g_set, g_get, NULL},
{ 'f', f_set, f_get, NULL, f_set_sw, f_get_sw},
@ -1674,6 +1720,8 @@ _ctypes_init_fielddesc(void)
case 'd': fd->pffi_type = &ffi_type_double; break;
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
case 'C': fd->pffi_type = &ffi_type_complex_double; break;
case 'E': fd->pffi_type = &ffi_type_complex_float; break;
case 'F': fd->pffi_type = &ffi_type_complex_longdouble; break;
#endif
case 'g': fd->pffi_type = &ffi_type_longdouble; break;
case 'f': fd->pffi_type = &ffi_type_float; break;

View File

@ -401,6 +401,8 @@ struct tagPyCArgObject {
void *p;
#if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE)
double complex C;
float complex E;
long double complex F;
#endif
} value;
PyObject *obj;