I've written this class to wrap a collection of bytes and interpret them as 16-bit floats. It's supposed to work like memoryview(buf).cast('f') or array.array('f', buf) I'm trying to avoid converting back and forth between values as much as possible. cPython does not currently support using the format code 'e' as the format argument to array.array.
The motivation is to support 16bit floating point arrays from this RFC: https://www.rfc-editor.org/rfc/rfc8746.html#name-types-of-numbers as part of this decoder: https://github.com/agronholm/cbor2
Is there anything else I can add or take away?
import struct
from collections.abc import Sequence
class Float16Array(Sequence):
"""
Takes a bytes or bytearray object and interprets it as an array of
16-bit IEEE half-floats
Behaves a bit like if you could create an array.array('e', [1, 2, 3.7])
"""
def __init__(self, buf):
self.hbuf = memoryview(buf).cast('H')
@staticmethod
def _to_h(v):
"convert float to an unsigned 16 bit integer representation"
return struct.unpack('H', struct.pack('e', v))[0]
@staticmethod
def _to_v(h):
"convert 16-bit integer back to regular float"
return struct.unpack('e', struct.pack('H', h))[0]
def __len__(self):
return len(self.hbuf)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.hbuf == other.hbuf
if isinstance(other, Sequence):
if len(self) != len(other):
return False
for hval, oval in zip(self.hbuf, other):
try:
if hval != self._to_h(oval):
return False
except struct.error:
return False
return True
else:
raise NotImplemented
def __getitem__(self, key):
if isinstance(key, slice):
return self.__class__(self.hbuf[key].cast('B'))
item = self.hbuf[key]
return self._to_v(item)
def __contains__(self, value):
try:
return self._to_h(value) in self.hbuf
except struct.error:
return False
def __reversed__(self):
for item in reversed(self.hbuf):
yield self._to_v(item)
def index(self, value, start=0, stop=None):
buf = self.hbuf[start:stop]
try:
buf_val = self._to_h(value)
except struct.error:
raise TypeError('value must be float or int') from None
for i, v in enumerate(buf):
if v is buf_val or v == buf_val:
return i
raise ValueError
def count(self, value):
try:
buf_val = self._to_h(value)
except struct.error:
raise TypeError('value must be float or int') from None
return sum(1 for v in self.hbuf if v == buf_val)
def __repr__(self):
contents = ', '.join('{:.2f}'.format(v).rstrip('0') for v in self)
return self.__class__.__name__ + '(' + contents + ')'
if __name__ == '__main__':
my_array = Float16Array(struct.pack('eeee', 0.1, 0.1, 72.0, 3.141))
assert 0.1 in my_array
assert my_array.count(72) == 1
assert my_array.count(0.1)
assert my_array == [0.1, 0.1, 72.0, 3.141]
print(list(reversed(my_array)))
print(my_array)
assert my_array[0:-1] == Float16Array(struct.pack('eee', 0.1, 0.1, 72.0))