py/binary: Support half-float 'e' format in struct pack/unpack.

This commit implements the 'e' half-float format: 10-bit mantissa, 5-bit
exponent.  It uses native _Float16 if supported by the compiler, otherwise
uses custom bitshifting encoding/decoding routines.

Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Damien George <damien@micropython.org>
pull/12799/head
Matthias Urlichs 2023-10-25 19:17:47 +02:00 zatwierdzone przez Damien George
rodzic 77f08b72ca
commit e520fa2e0f
4 zmienionych plików z 119 dodań i 3 usunięć

Wyświetl plik

@ -45,6 +45,8 @@ The following data types are supported:
+--------+--------------------+-------------------+---------------+
| Q | unsigned long long | integer (`1<fn>`) | 8 |
+--------+--------------------+-------------------+---------------+
| e | n/a (half-float) | float (`2<fn>`) | 2 |
+--------+--------------------+-------------------+---------------+
| f | float | float (`2<fn>`) | 4 |
+--------+--------------------+-------------------+---------------+
| d | double | float (`2<fn>`) | 8 |

Wyświetl plik

@ -74,11 +74,14 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
case 'S':
size = sizeof(void *);
break;
case 'e':
size = 2;
break;
case 'f':
size = sizeof(float);
size = 4;
break;
case 'd':
size = sizeof(double);
size = 8;
break;
}
break;
@ -122,6 +125,10 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
align = alignof(void *);
size = sizeof(void *);
break;
case 'e':
align = 2;
size = 2;
break;
case 'f':
align = alignof(float);
size = sizeof(float);
@ -144,6 +151,99 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
return size;
}
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_USE_NATIVE_FLT16
static inline float mp_decode_half_float(uint16_t hf) {
union {
uint16_t i;
_Float16 f;
} fpu = { .i = hf };
return fpu.f;
}
static inline uint16_t mp_encode_half_float(float x) {
union {
uint16_t i;
_Float16 f;
} fp_sp = { .f = (_Float16)x };
return fp_sp.i;
}
#elif MICROPY_PY_BUILTINS_FLOAT
static float mp_decode_half_float(uint16_t hf) {
union {
uint32_t i;
float f;
} fpu;
uint16_t m = hf & 0x3ff;
int e = (hf >> 10) & 0x1f;
if (e == 0x1f) {
// Half-float is infinity.
e = 0xff;
} else if (e) {
// Half-float is normal.
e += 127 - 15;
} else if (m) {
// Half-float is subnormal, make it normal.
e = 127 - 15;
while (!(m & 0x400)) {
m <<= 1;
--e;
}
m -= 0x400;
++e;
}
fpu.i = ((hf & 0x8000) << 16) | (e << 23) | (m << 13);
return fpu.f;
}
static uint16_t mp_encode_half_float(float x) {
union {
uint32_t i;
float f;
} fpu = { .f = x };
uint16_t m = (fpu.i >> 13) & 0x3ff;
if (fpu.i & (1 << 12)) {
// Round up.
++m;
}
int e = (fpu.i >> 23) & 0xff;
if (e == 0xff) {
// Infinity.
e = 0x1f;
} else if (e != 0) {
e -= 127 - 15;
if (e < 0) {
// Underflow: denormalized, or zero.
if (e >= -11) {
m = (m | 0x400) >> -e;
if (m & 1) {
m = (m >> 1) + 1;
} else {
m >>= 1;
}
} else {
m = 0;
}
e = 0;
} else if (e > 0x3f) {
// Overflow: infinity.
e = 0x1f;
m = 0;
}
}
uint16_t bits = ((fpu.i >> 16) & 0x8000) | (e << 10) | m;
return bits;
}
#endif
mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) {
mp_int_t val = 0;
switch (typecode) {
@ -240,6 +340,8 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte *
const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val;
return mp_obj_new_str(s_val, strlen(s_val));
#if MICROPY_PY_BUILTINS_FLOAT
} else if (val_type == 'e') {
return mp_obj_new_float_from_f(mp_decode_half_float(val));
} else if (val_type == 'f') {
union {
uint32_t i;
@ -309,6 +411,9 @@ void mp_binary_set_val(char struct_type, char val_type, mp_obj_t val_in, byte *p
val = (mp_uint_t)val_in;
break;
#if MICROPY_PY_BUILTINS_FLOAT
case 'e':
val = mp_encode_half_float(mp_obj_get_float_to_f(val_in));
break;
case 'f': {
union {
uint32_t i;

Wyświetl plik

@ -830,6 +830,15 @@ typedef double mp_float_t;
#define MICROPY_PY_BUILTINS_COMPLEX (MICROPY_PY_BUILTINS_FLOAT)
#endif
// Whether to use the native _Float16 for 16-bit float support
#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16
#ifdef __FLT16_MAX__
#define MICROPY_FLOAT_USE_NATIVE_FLT16 (1)
#else
#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0)
#endif
#endif
// Whether to provide a high-quality hash for float and complex numbers.
// Otherwise the default is a very simple but correct hashing function.
#ifndef MICROPY_FLOAT_HIGH_QUALITY_HASH

Wyświetl plik

@ -8,7 +8,7 @@ except ImportError:
i = 1.0 + 1 / 2
# TODO: it looks like '=' format modifier is not yet supported
# for fmt in ('f', 'd', '>f', '>d', '<f', '<d', '=f', '=d'):
for fmt in ("f", "d", ">f", ">d", "<f", "<d"):
for fmt in ("e", "f", "d", ">e", ">f", ">d", "<e", "<f", "<d"):
x = struct.pack(fmt, i)
v = struct.unpack(fmt, x)[0]
print("%2s: %.17f - %s" % (fmt, v, (i == v) and "passed" or "failed"))