Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
===========
Expand Down
20 changes: 16 additions & 4 deletions Lib/test/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
'?':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':
Expand All @@ -92,7 +93,9 @@
'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):
Expand All @@ -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:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -3015,7 +3027,7 @@ def test_memoryview_assign(self):
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():
Expand All @@ -3025,7 +3037,7 @@ def test_memoryview_assign(self):
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)
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,14 @@ def test_half_float(self):
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions Modules/_testbuffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) &&
Expand Down Expand Up @@ -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)) &&
Expand Down
65 changes: 60 additions & 5 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1853,7 +1868,9 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down
Loading