Skip to content

Commit 94ee023

Browse files
committed
gh-146151: memoryview supports 'F' and 'D' format types (complex)
1 parent 52c0186 commit 94ee023

File tree

6 files changed

+94
-11
lines changed

6 files changed

+94
-11
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,11 @@ Other language changes
598598
making it a :term:`generic type`.
599599
(Contributed by James Hilton-Balfe in :gh:`128335`.)
600600

601+
* The class :class:`memoryview` now supports the :c:expr:`float complex` and
602+
:c:expr:`double complex` C types (formatting characters ``'F'`` and ``'D'``
603+
respectively).
604+
(Contributed by Sergey B Kirpichev in :gh:`146151`.)
605+
601606

602607
New modules
603608
===========

Lib/test/test_buffer.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
'?':0, 'c':0, 'b':0, 'B':0,
6767
'h':0, 'H':0, 'i':0, 'I':0,
6868
'l':0, 'L':0, 'n':0, 'N':0,
69-
'e':0, 'f':0, 'd':0, 'P':0
69+
'e':0, 'f':0, 'd':0, 'P':0,
70+
'F':0, 'D':0
7071
}
7172

7273
# NumPy does not have 'n' or 'N':
@@ -92,7 +93,9 @@
9293
'l':(-(1<<31), 1<<31), 'L':(0, 1<<32),
9394
'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64),
9495
'e':(-65519, 65520), 'f':(-(1<<63), 1<<63),
95-
'd':(-(1<<1023), 1<<1023)
96+
'd':(-(1<<1023), 1<<1023),
97+
'F':(-(1<<63), 1<<63),
98+
'D':(-(1<<1023), 1<<1023)
9699
}
97100

98101
def native_type_range(fmt):
@@ -107,6 +110,10 @@ def native_type_range(fmt):
107110
lh = (-(1<<63), 1<<63)
108111
elif fmt == 'd':
109112
lh = (-(1<<1023), 1<<1023)
113+
elif fmt == 'F':
114+
lh = (-(1<<63), 1<<63)
115+
elif fmt == 'D':
116+
lh = (-(1<<1023), 1<<1023)
110117
else:
111118
for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7):
112119
try:
@@ -175,6 +182,11 @@ def randrange_fmt(mode, char, obj):
175182
if char in 'efd':
176183
x = struct.pack(char, x)
177184
x = struct.unpack(char, x)[0]
185+
if char in 'FD':
186+
y = randrange(*fmtdict[mode][char])
187+
x = complex(x, y)
188+
x = struct.pack(char, x)
189+
x = struct.unpack(char, x)[0]
178190
return x
179191

180192
def gen_item(fmt, obj):
@@ -3015,7 +3027,7 @@ def test_memoryview_assign(self):
30153027
m = memoryview(nd)
30163028
self.assertRaises(TypeError, m.__setitem__, 0, 100)
30173029

3018-
ex = ndarray(list(range(120)), shape=[1,2,3,4,5], flags=ND_WRITABLE)
3030+
ex = ndarray(list(range(144)), shape=[1,2,3,4,6], flags=ND_WRITABLE)
30193031
m1 = memoryview(ex)
30203032

30213033
for fmt, _range in fmtdict['@'].items():
@@ -3025,7 +3037,7 @@ def test_memoryview_assign(self):
30253037
continue
30263038
m2 = m1.cast(fmt)
30273039
lo, hi = _range
3028-
if fmt == 'd' or fmt == 'f':
3040+
if fmt in "dfDF":
30293041
lo, hi = -2**1024, 2**1024
30303042
if fmt != 'P': # PyLong_AsVoidPtr() accepts negative numbers
30313043
self.assertRaises(ValueError, m2.__setitem__, 0, lo-1)

Lib/test/test_memoryview.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,14 @@ def test_half_float(self):
682682
self.assertEqual(half_view.nbytes * 2, float_view.nbytes)
683683
self.assertListEqual(half_view.tolist(), float_view.tolist())
684684

685+
def test_complex_types(self):
686+
float_complex_data = struct.pack('FFF', 0.0, -1.5j, 1+2j)
687+
double_complex_data = struct.pack('DDD', 0.0, -1.5j, 1+2j)
688+
float_complex_view = memoryview(float_complex_data).cast('F')
689+
double_complex_view = memoryview(double_complex_data).cast('D')
690+
self.assertEqual(float_complex_view.nbytes * 2, double_complex_view.nbytes)
691+
self.assertListEqual(float_complex_view.tolist(), double_complex_view.tolist())
692+
685693
def test_memoryview_hex(self):
686694
# Issue #9951: memoryview.hex() segfaults with non-contiguous buffers.
687695
x = b'0' * 200000
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:class:`memoryview` now supports the :c:expr:`float complex` and
2+
:c:expr:`double complex` C types (formatting characters ``'F'`` and ``'D'``
3+
respectively). Patch by Sergey B Kirpichev.

Modules/_testbuffer.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ pack_from_list(PyObject *obj, PyObject *items, PyObject *format,
351351

352352
item = PySequence_Fast_GET_ITEM(items, i);
353353
if ((PyBytes_Check(item) || PyLong_Check(item) ||
354-
PyFloat_Check(item)) && nmemb == 1) {
354+
PyFloat_Check(item) || PyComplex_Check(item)) && nmemb == 1) {
355355
PyTuple_SET_ITEM(args, 2, item);
356356
}
357357
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)
433433
PyTuple_SET_ITEM(args, 1, zero);
434434

435435
if ((PyBytes_Check(item) || PyLong_Check(item) ||
436-
PyFloat_Check(item)) && nmemb == 1) {
436+
PyFloat_Check(item) || PyComplex_Check(item)) && nmemb == 1) {
437437
PyTuple_SET_ITEM(args, 2, item);
438438
}
439439
else if ((PyList_Check(item) || PyTuple_Check(item)) &&

Objects/memoryobject.c

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,8 @@ get_native_fmtchar(char *result, const char *fmt)
12161216
case 'f': size = sizeof(float); break;
12171217
case 'd': size = sizeof(double); break;
12181218
case 'e': size = sizeof(float) / 2; break;
1219+
case 'F': size = 2*sizeof(float); break;
1220+
case 'D': size = 2*sizeof(double); break;
12191221
case '?': size = sizeof(_Bool); break;
12201222
case 'P': size = sizeof(void *); break;
12211223
}
@@ -1260,6 +1262,8 @@ get_native_fmtstr(const char *fmt)
12601262
case 'f': RETURN("f");
12611263
case 'd': RETURN("d");
12621264
case 'e': RETURN("e");
1265+
case 'F': RETURN("F");
1266+
case 'D': RETURN("D");
12631267
case '?': RETURN("?");
12641268
case 'P': RETURN("P");
12651269
}
@@ -1785,7 +1789,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
17851789
long long lld;
17861790
long ld;
17871791
Py_ssize_t zd;
1788-
double d;
1792+
double d[2];
17891793
unsigned char uc;
17901794
void *p;
17911795

@@ -1823,9 +1827,20 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
18231827
case 'N': UNPACK_SINGLE(zu, ptr, size_t); goto convert_zu;
18241828

18251829
/* floats */
1826-
case 'f': UNPACK_SINGLE(d, ptr, float); goto convert_double;
1827-
case 'd': UNPACK_SINGLE(d, ptr, double); goto convert_double;
1828-
case 'e': d = PyFloat_Unpack2(ptr, endian); goto convert_double;
1830+
case 'f': UNPACK_SINGLE(d[0], ptr, float); goto convert_double;
1831+
case 'd': UNPACK_SINGLE(d[0], ptr, double); goto convert_double;
1832+
case 'e': d[0] = PyFloat_Unpack2(ptr, endian); goto convert_double;
1833+
1834+
/* complexes */
1835+
case 'F':
1836+
d[0] = PyFloat_Unpack4(ptr, endian);
1837+
d[1] = PyFloat_Unpack4(ptr + sizeof(float), endian);
1838+
goto convert_double_complex;
1839+
1840+
case 'D':
1841+
d[0] = PyFloat_Unpack8(ptr, endian);
1842+
d[1] = PyFloat_Unpack8(ptr + sizeof(double), endian);
1843+
goto convert_double_complex;
18291844

18301845
/* bytes object */
18311846
case 'c': goto convert_bytes;
@@ -1853,7 +1868,9 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
18531868
convert_zu:
18541869
return PyLong_FromSize_t(zu);
18551870
convert_double:
1856-
return PyFloat_FromDouble(d);
1871+
return PyFloat_FromDouble(d[0]);
1872+
convert_double_complex:
1873+
return PyComplex_FromDoubles(d[0], d[1]);
18571874
convert_bool:
18581875
return PyBool_FromLong(ld);
18591876
convert_bytes:
@@ -1885,6 +1902,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
18851902
long ld;
18861903
Py_ssize_t zd;
18871904
double d;
1905+
Py_complex c;
18881906
void *p;
18891907

18901908
#if PY_LITTLE_ENDIAN
@@ -1986,6 +2004,25 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
19862004
}
19872005
break;
19882006

2007+
/* complexes */
2008+
case 'F': case 'D':
2009+
c = PyComplex_AsCComplex(item);
2010+
if (c.real == -1.0 && PyErr_Occurred()) {
2011+
goto err_occurred;
2012+
}
2013+
CHECK_RELEASED_INT_AGAIN(self);
2014+
if (fmt[0] == 'D') {
2015+
double x[2] = {c.real, c.imag};
2016+
2017+
memcpy(ptr, &x, sizeof(x));
2018+
}
2019+
else {
2020+
float x[2] = {(float)c.real, (float)c.imag};
2021+
2022+
memcpy(ptr, &x, sizeof(x));
2023+
}
2024+
break;
2025+
19892026
/* bool */
19902027
case '?':
19912028
ld = PyObject_IsTrue(item);
@@ -3023,6 +3060,24 @@ unpack_cmp(const char *p, const char *q, char fmt,
30233060
return (u == v);
30243061
}
30253062

3063+
/* complexes */
3064+
case 'F':
3065+
{
3066+
float x[2], y[2];
3067+
3068+
memcpy(&x, p, sizeof(x));
3069+
memcpy(&y, q, sizeof(y));
3070+
return (x[0] == y[0]) && (x[1] == y[1]);
3071+
}
3072+
case 'D':
3073+
{
3074+
double x[2], y[2];
3075+
3076+
memcpy(&x, p, sizeof(x));
3077+
memcpy(&y, q, sizeof(y));
3078+
return (x[0] == y[0]) && (x[1] == y[1]);
3079+
}
3080+
30263081
/* bytes object */
30273082
case 'c': return *p == *q;
30283083

0 commit comments

Comments
 (0)