]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-146151: memoryview supports 'F' and 'D' format types (complex) (#146241)
authorSergey B Kirpichev <skirpichev@gmail.com>
Thu, 26 Mar 2026 14:26:34 +0000 (17:26 +0300)
committerGitHub <noreply@github.com>
Thu, 26 Mar 2026 14:26:34 +0000 (15:26 +0100)
Co-authored-by: Victor Stinner <vstinner@python.org>
Doc/whatsnew/3.15.rst
Lib/test/test_buffer.py
Lib/test/test_memoryview.py
Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst [new file with mode: 0644]
Modules/_testbuffer.c
Objects/memoryobject.c

index 0973c387a1e5958fb3685600dbd5a3231b0b444e..57b72cf548eee99b087090f04516cabeaa7604d7 100644 (file)
@@ -598,6 +598,11 @@ Other language changes
   making it a :term:`generic type`.
   (Contributed by James Hilton-Balfe in :gh:`128335`.)
 
+* The class :class:`memoryview` now supports the :c:expr:`float complex` and
+  :c:expr:`double complex` C types: formatting characters ``'F'`` and ``'D'``
+  respectively.
+  (Contributed by Sergey B Kirpichev in :gh:`146151`.)
+
 
 New modules
 ===========
index ab65a44bda6e7eb0cd3d9b9c650fb5e5785b515b..5a4f031e298c2fd9685896094ce1fb7e39d12a9b 100644 (file)
@@ -66,7 +66,8 @@ NATIVE = {
     '?':0, 'c':0, 'b':0, 'B':0,
     'h':0, 'H':0, 'i':0, 'I':0,
     'l':0, 'L':0, 'n':0, 'N':0,
-    'e':0, 'f':0, 'd':0, 'P':0
+    'e':0, 'f':0, 'd':0, 'P':0,
+    'F':0, 'D':0
 }
 
 # NumPy does not have 'n' or 'N':
@@ -92,7 +93,9 @@ STANDARD = {
     'l':(-(1<<31), 1<<31), 'L':(0, 1<<32),
     'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64),
     'e':(-65519, 65520),   'f':(-(1<<63), 1<<63),
-    'd':(-(1<<1023), 1<<1023)
+    'd':(-(1<<1023), 1<<1023),
+    'F':(-(1<<63), 1<<63),
+    'D':(-(1<<1023), 1<<1023)
 }
 
 def native_type_range(fmt):
@@ -107,6 +110,10 @@ def native_type_range(fmt):
         lh = (-(1<<63), 1<<63)
     elif fmt == 'd':
         lh = (-(1<<1023), 1<<1023)
+    elif fmt == 'F':
+        lh = (-(1<<63), 1<<63)
+    elif fmt == 'D':
+        lh = (-(1<<1023), 1<<1023)
     else:
         for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7):
             try:
@@ -175,6 +182,11 @@ def randrange_fmt(mode, char, obj):
     if char in 'efd':
         x = struct.pack(char, x)
         x = struct.unpack(char, x)[0]
+    if char in 'FD':
+        y = randrange(*fmtdict[mode][char])
+        x = complex(x, y)
+        x = struct.pack(char, x)
+        x = struct.unpack(char, x)[0]
     return x
 
 def gen_item(fmt, obj):
@@ -3015,7 +3027,7 @@ class TestBufferProtocol(unittest.TestCase):
         m = memoryview(nd)
         self.assertRaises(TypeError, m.__setitem__, 0, 100)
 
-        ex = ndarray(list(range(120)), shape=[1,2,3,4,5], flags=ND_WRITABLE)
+        ex = ndarray(list(range(144)), shape=[1,2,3,4,6], flags=ND_WRITABLE)
         m1 = memoryview(ex)
 
         for fmt, _range in fmtdict['@'].items():
@@ -3025,7 +3037,7 @@ class TestBufferProtocol(unittest.TestCase):
                 continue
             m2 = m1.cast(fmt)
             lo, hi = _range
-            if fmt == 'd' or fmt == 'f':
+            if fmt in "dfDF":
                 lo, hi = -2**1024, 2**1024
             if fmt != 'P': # PyLong_AsVoidPtr() accepts negative numbers
                 self.assertRaises(ValueError, m2.__setitem__, 0, lo-1)
index 0f7dc15b8c6f2cea1e333736d8d1b45b82974fa3..6e9f916f77b34656bc59e14e3c2f2255be854170 100644 (file)
@@ -636,6 +636,18 @@ class ArrayMemoryviewTest(unittest.TestCase,
                 m = memoryview(a)
                 check_equal(m, True)
 
+        # Test complex formats
+        for complex_format in 'FD':
+            with self.subTest(format=complex_format):
+                data = struct.pack(complex_format * 3, 1.0, 2.0, float('nan'))
+                m = memoryview(data).cast(complex_format)
+                # nan is not equal to nan
+                check_equal(m, False)
+
+                data = struct.pack(complex_format * 3, 1.0, 2.0, 3.0)
+                m = memoryview(data).cast(complex_format)
+                check_equal(m, True)
+
 
 class BytesMemorySliceTest(unittest.TestCase,
     BaseMemorySliceTests, BaseBytesMemoryTests):
@@ -682,6 +694,14 @@ class OtherTest(unittest.TestCase):
         self.assertEqual(half_view.nbytes * 2, float_view.nbytes)
         self.assertListEqual(half_view.tolist(), float_view.tolist())
 
+    def test_complex_types(self):
+        float_complex_data = struct.pack('FFF', 0.0, -1.5j, 1+2j)
+        double_complex_data = struct.pack('DDD', 0.0, -1.5j, 1+2j)
+        float_complex_view = memoryview(float_complex_data).cast('F')
+        double_complex_view = memoryview(double_complex_data).cast('D')
+        self.assertEqual(float_complex_view.nbytes * 2, double_complex_view.nbytes)
+        self.assertListEqual(float_complex_view.tolist(), double_complex_view.tolist())
+
     def test_memoryview_hex(self):
         # Issue #9951: memoryview.hex() segfaults with non-contiguous buffers.
         x = b'0' * 200000
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst
new file mode 100644 (file)
index 0000000..d4a65d3
--- /dev/null
@@ -0,0 +1,3 @@
+:class:`memoryview` now supports the :c:expr:`float complex` and
+:c:expr:`double complex` C types: formatting characters ``'F'`` and ``'D'``
+respectively.  Patch by Sergey B Kirpichev.
index d2e61e9d6acf24c0f00ae214c670f3b909438003..8b6b617aafa427d709380d4646d19a9676d069c3 100644 (file)
@@ -351,7 +351,7 @@ pack_from_list(PyObject *obj, PyObject *items, PyObject *format,
 
         item = PySequence_Fast_GET_ITEM(items, i);
         if ((PyBytes_Check(item) || PyLong_Check(item) ||
-             PyFloat_Check(item)) && nmemb == 1) {
+             PyFloat_Check(item) || PyComplex_Check(item)) && nmemb == 1) {
             PyTuple_SET_ITEM(args, 2, item);
         }
         else if ((PyList_Check(item) || PyTuple_Check(item)) &&
@@ -433,7 +433,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt, Py_ssize_t itemsize)
     PyTuple_SET_ITEM(args, 1, zero);
 
     if ((PyBytes_Check(item) || PyLong_Check(item) ||
-         PyFloat_Check(item)) && nmemb == 1) {
+         PyFloat_Check(item) || PyComplex_Check(item)) && nmemb == 1) {
          PyTuple_SET_ITEM(args, 2, item);
     }
     else if ((PyList_Check(item) || PyTuple_Check(item)) &&
index 0ad4f02d80bf50c3283b22986f472f3bb3c7db9c..00e7955d15118ae0445d13b9d169bb5e41c1e290 100644 (file)
@@ -1216,6 +1216,8 @@ get_native_fmtchar(char *result, const char *fmt)
     case 'f': size = sizeof(float); break;
     case 'd': size = sizeof(double); break;
     case 'e': size = sizeof(float) / 2; break;
+    case 'F': size = 2*sizeof(float); break;
+    case 'D': size = 2*sizeof(double); break;
     case '?': size = sizeof(_Bool); break;
     case 'P': size = sizeof(void *); break;
     }
@@ -1260,6 +1262,8 @@ get_native_fmtstr(const char *fmt)
     case 'f': RETURN("f");
     case 'd': RETURN("d");
     case 'e': RETURN("e");
+    case 'F': RETURN("F");
+    case 'D': RETURN("D");
     case '?': RETURN("?");
     case 'P': RETURN("P");
     }
@@ -1785,7 +1789,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
     long long lld;
     long ld;
     Py_ssize_t zd;
-    double d;
+    double d[2];
     unsigned char uc;
     void *p;
 
@@ -1823,9 +1827,20 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
     case 'N': UNPACK_SINGLE(zu, ptr, size_t); goto convert_zu;
 
     /* floats */
-    case 'f': UNPACK_SINGLE(d, ptr, float); goto convert_double;
-    case 'd': UNPACK_SINGLE(d, ptr, double); goto convert_double;
-    case 'e': d = PyFloat_Unpack2(ptr, endian); goto convert_double;
+    case 'f': UNPACK_SINGLE(d[0], ptr, float); goto convert_double;
+    case 'd': UNPACK_SINGLE(d[0], ptr, double); goto convert_double;
+    case 'e': d[0] = PyFloat_Unpack2(ptr, endian); goto convert_double;
+
+    /* complexes */
+    case 'F':
+        d[0] = PyFloat_Unpack4(ptr, endian);
+        d[1] = PyFloat_Unpack4(ptr + sizeof(float), endian);
+        goto convert_double_complex;
+
+    case 'D':
+        d[0] = PyFloat_Unpack8(ptr, endian);
+        d[1] = PyFloat_Unpack8(ptr + sizeof(double), endian);
+        goto convert_double_complex;
 
     /* bytes object */
     case 'c': goto convert_bytes;
@@ -1853,7 +1868,9 @@ convert_zd:
 convert_zu:
     return PyLong_FromSize_t(zu);
 convert_double:
-    return PyFloat_FromDouble(d);
+    return PyFloat_FromDouble(d[0]);
+convert_double_complex:
+    return PyComplex_FromDoubles(d[0], d[1]);
 convert_bool:
     return PyBool_FromLong(ld);
 convert_bytes:
@@ -1885,6 +1902,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
     long ld;
     Py_ssize_t zd;
     double d;
+    Py_complex c;
     void *p;
 
 #if PY_LITTLE_ENDIAN
@@ -1986,6 +2004,25 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
         }
         break;
 
+    /* complexes */
+    case 'F': case 'D':
+        c = PyComplex_AsCComplex(item);
+        if (c.real == -1.0 && PyErr_Occurred()) {
+            goto err_occurred;
+        }
+        CHECK_RELEASED_INT_AGAIN(self);
+        if (fmt[0] == 'D') {
+            double x[2] = {c.real, c.imag};
+
+            memcpy(ptr, &x, sizeof(x));
+        }
+        else {
+            float x[2] = {(float)c.real, (float)c.imag};
+
+            memcpy(ptr, &x, sizeof(x));
+        }
+        break;
+
     /* bool */
     case '?':
         ld = PyObject_IsTrue(item);
@@ -3023,6 +3060,24 @@ unpack_cmp(const char *p, const char *q, char fmt,
         return (u == v);
     }
 
+    /* complexes */
+    case 'F':
+    {
+         float x[2], y[2];
+
+         memcpy(&x, p, sizeof(x));
+         memcpy(&y, q, sizeof(y));
+         return (x[0] == y[0]) && (x[1] == y[1]);
+    }
+    case 'D':
+    {
+         double x[2], y[2];
+
+         memcpy(&x, p, sizeof(x));
+         memcpy(&y, q, sizeof(y));
+         return (x[0] == y[0]) && (x[1] == y[1]);
+    }
+
     /* bytes object */
     case 'c': return *p == *q;