Skip to content
Merged
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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
20 changes: 20 additions & 0 deletions Lib/test/test_memoryview.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -636,6 +636,18 @@ def check_equal(view, is_equal):
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):
Expand DownExpand Up@@ -682,6 +694,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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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:

>>>importstruct, 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
MemberAuthor

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 DownExpand 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