Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure of the behavior on underflow or overflow of the double to float cast, so I tested:

>>> import struct, sys
>>> data=struct.pack('F', 0.0) * 4
>>> m=memoryview(bytearray(data)).cast('F')
>>> m[0]=math.nextafter(0, 1)
>>> m[1]=sys.float_info.min
>>> m[2]=sys.float_info.max
>>> m[3]=float("nan")
>>> m.tolist()
[0j, 0j, (inf+0j), (nan+0j)]

I got the values that I expected. I suppose that these conversions are well specified by IEEE 754.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior here is same as for 'f' format, just component-wise, see PACK_SINGLE macro.


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