Skip to content

Commit 8d75a13

Browse files
corona10pitrou
andauthored
gh-90751: memoryview now supports half-float (#96738)
Co-authored-by: Antoine Pitrou <[email protected]>
1 parent c4e57fb commit 8d75a13

File tree

5 files changed

+60
-10
lines changed

5 files changed

+60
-10
lines changed

Doc/whatsnew/3.12.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ Other Language Changes
9494
length limitation <int_max_str_digits>` documentation. The default limit
9595
is 4300 digits in string form.
9696

97+
* :class:`memoryview` now supports the half-float type (the "e" format code).
98+
(Contributed by Dong-hee Na and Antoine Pitrou in :gh:`90751`.)
99+
97100

98101
New Modules
99102
===========

Lib/test/test_buffer.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
'?':0, 'c':0, 'b':0, 'B':0,
6565
'h':0, 'H':0, 'i':0, 'I':0,
6666
'l':0, 'L':0, 'n':0, 'N':0,
67-
'f':0, 'd':0, 'P':0
67+
'e':0, 'f':0, 'd':0, 'P':0
6868
}
6969

7070
# NumPy does not have 'n' or 'N':
@@ -89,7 +89,8 @@
8989
'i':(-(1<<31), 1<<31), 'I':(0, 1<<32),
9090
'l':(-(1<<31), 1<<31), 'L':(0, 1<<32),
9191
'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64),
92-
'f':(-(1<<63), 1<<63), 'd':(-(1<<1023), 1<<1023)
92+
'e':(-65519, 65520), 'f':(-(1<<63), 1<<63),
93+
'd':(-(1<<1023), 1<<1023)
9394
}
9495

9596
def native_type_range(fmt):
@@ -98,6 +99,8 @@ def native_type_range(fmt):
9899
lh = (0, 256)
99100
elif fmt == '?':
100101
lh = (0, 2)
102+
elif fmt == 'e':
103+
lh = (-65519, 65520)
101104
elif fmt == 'f':
102105
lh = (-(1<<63), 1<<63)
103106
elif fmt == 'd':
@@ -125,7 +128,10 @@ def native_type_range(fmt):
125128
for fmt in fmtdict['@']:
126129
fmtdict['@'][fmt] = native_type_range(fmt)
127130

131+
# Format codes suppported by the memoryview object
128132
MEMORYVIEW = NATIVE.copy()
133+
134+
# Format codes suppported by array.array
129135
ARRAY = NATIVE.copy()
130136
for k in NATIVE:
131137
if not k in "bBhHiIlLfd":
@@ -164,7 +170,7 @@ def randrange_fmt(mode, char, obj):
164170
x = b'\x01'
165171
if char == '?':
166172
x = bool(x)
167-
if char == 'f' or char == 'd':
173+
if char in 'efd':
168174
x = struct.pack(char, x)
169175
x = struct.unpack(char, x)[0]
170176
return x
@@ -2246,7 +2252,7 @@ def test_py_buffer_to_contiguous(self):
22462252
###
22472253
### Fortran output:
22482254
### ---------------
2249-
### >>> fortran_buf = nd.tostring(order='F')
2255+
### >>> fortran_buf = nd.tobytes(order='F')
22502256
### >>> fortran_buf
22512257
### b'\x00\x04\x08\x01\x05\t\x02\x06\n\x03\x07\x0b'
22522258
###
@@ -2289,7 +2295,7 @@ def test_py_buffer_to_contiguous(self):
22892295
self.assertEqual(memoryview(y), memoryview(nd))
22902296

22912297
if numpy_array:
2292-
self.assertEqual(b, na.tostring(order='C'))
2298+
self.assertEqual(b, na.tobytes(order='C'))
22932299

22942300
# 'F' request
22952301
if f == 0: # 'C' to 'F'
@@ -2312,7 +2318,7 @@ def test_py_buffer_to_contiguous(self):
23122318
self.assertEqual(memoryview(y), memoryview(nd))
23132319

23142320
if numpy_array:
2315-
self.assertEqual(b, na.tostring(order='F'))
2321+
self.assertEqual(b, na.tobytes(order='F'))
23162322

23172323
# 'A' request
23182324
if f == ND_FORTRAN:
@@ -2336,7 +2342,7 @@ def test_py_buffer_to_contiguous(self):
23362342
self.assertEqual(memoryview(y), memoryview(nd))
23372343

23382344
if numpy_array:
2339-
self.assertEqual(b, na.tostring(order='A'))
2345+
self.assertEqual(b, na.tobytes(order='A'))
23402346

23412347
# multi-dimensional, non-contiguous input
23422348
nd = ndarray(list(range(12)), shape=[3, 4], flags=ND_WRITABLE|ND_PIL)

Lib/test/test_memoryview.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io
1414
import copy
1515
import pickle
16+
import struct
1617

1718
from test.support import import_helper
1819

@@ -527,6 +528,14 @@ def test_ctypes_cast(self):
527528
m[2:] = memoryview(p6).cast(format)[2:]
528529
self.assertEqual(d.value, 0.6)
529530

531+
def test_half_float(self):
532+
half_data = struct.pack('eee', 0.0, -1.5, 1.5)
533+
float_data = struct.pack('fff', 0.0, -1.5, 1.5)
534+
half_view = memoryview(half_data).cast('e')
535+
float_view = memoryview(float_data).cast('f')
536+
self.assertEqual(half_view.nbytes * 2, float_view.nbytes)
537+
self.assertListEqual(half_view.tolist(), float_view.tolist())
538+
530539
def test_memoryview_hex(self):
531540
# Issue #9951: memoryview.hex() segfaults with non-contiguous buffers.
532541
x = b'0' * 200000
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`memoryview` now supports half-floats.
2+
Patch by Dong-hee Na and Antoine Pitrou.

Objects/memoryobject.c

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ get_native_fmtchar(char *result, const char *fmt)
11351135
case 'n': case 'N': size = sizeof(Py_ssize_t); break;
11361136
case 'f': size = sizeof(float); break;
11371137
case 'd': size = sizeof(double); break;
1138+
case 'e': size = sizeof(float) / 2; break;
11381139
case '?': size = sizeof(_Bool); break;
11391140
case 'P': size = sizeof(void *); break;
11401141
}
@@ -1178,6 +1179,7 @@ get_native_fmtstr(const char *fmt)
11781179
case 'N': RETURN("N");
11791180
case 'f': RETURN("f");
11801181
case 'd': RETURN("d");
1182+
case 'e': RETURN("e");
11811183
case '?': RETURN("?");
11821184
case 'P': RETURN("P");
11831185
}
@@ -1697,6 +1699,12 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
16971699

16981700
CHECK_RELEASED_AGAIN(self);
16991701

1702+
#if PY_LITTLE_ENDIAN
1703+
int endian = 1;
1704+
#else
1705+
int endian = 0;
1706+
#endif
1707+
17001708
switch (fmt[0]) {
17011709

17021710
/* signed integers and fast path for 'B' */
@@ -1725,6 +1733,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
17251733
/* floats */
17261734
case 'f': UNPACK_SINGLE(d, ptr, float); goto convert_double;
17271735
case 'd': UNPACK_SINGLE(d, ptr, double); goto convert_double;
1736+
case 'e': d = PyFloat_Unpack2(ptr, endian); goto convert_double;
17281737

17291738
/* bytes object */
17301739
case 'c': goto convert_bytes;
@@ -1786,6 +1795,11 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
17861795
double d;
17871796
void *p;
17881797

1798+
#if PY_LITTLE_ENDIAN
1799+
int endian = 1;
1800+
#else
1801+
int endian = 0;
1802+
#endif
17891803
switch (fmt[0]) {
17901804
/* signed integers */
17911805
case 'b': case 'h': case 'i': case 'l':
@@ -1862,17 +1876,22 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
18621876
break;
18631877

18641878
/* floats */
1865-
case 'f': case 'd':
1879+
case 'f': case 'd': case 'e':
18661880
d = PyFloat_AsDouble(item);
18671881
if (d == -1.0 && PyErr_Occurred())
18681882
goto err_occurred;
18691883
CHECK_RELEASED_INT_AGAIN(self);
18701884
if (fmt[0] == 'f') {
18711885
PACK_SINGLE(ptr, d, float);
18721886
}
1873-
else {
1887+
else if (fmt[0] == 'd') {
18741888
PACK_SINGLE(ptr, d, double);
18751889
}
1890+
else {
1891+
if (PyFloat_Pack2(d, ptr, endian) < 0) {
1892+
goto err_occurred;
1893+
}
1894+
}
18761895
break;
18771896

18781897
/* bool */
@@ -1882,7 +1901,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
18821901
return -1; /* preserve original error */
18831902
CHECK_RELEASED_INT_AGAIN(self);
18841903
PACK_SINGLE(ptr, ld, _Bool);
1885-
break;
1904+
break;
18861905

18871906
/* bytes object */
18881907
case 'c':
@@ -2748,6 +2767,17 @@ unpack_cmp(const char *p, const char *q, char fmt,
27482767
/* XXX DBL_EPSILON? */
27492768
case 'f': CMP_SINGLE(p, q, float); return equal;
27502769
case 'd': CMP_SINGLE(p, q, double); return equal;
2770+
case 'e': {
2771+
#if PY_LITTLE_ENDIAN
2772+
int endian = 1;
2773+
#else
2774+
int endian = 0;
2775+
#endif
2776+
/* Note: PyFloat_Unpack2 should never fail */
2777+
double u = PyFloat_Unpack2(p, endian);
2778+
double v = PyFloat_Unpack2(q, endian);
2779+
return (u == v);
2780+
}
27512781

27522782
/* bytes object */
27532783
case 'c': return *p == *q;

0 commit comments

Comments
 (0)