From e520fa2e0fb3cfafe27a1f9e7e9b230dd58d7a33 Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Wed, 25 Oct 2023 19:17:47 +0200 Subject: [PATCH] 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 Signed-off-by: Damien George --- docs/library/struct.rst | 2 + py/binary.c | 109 +++++++++++++++++++++++++++++++++++- py/mpconfig.h | 9 +++ tests/float/float_struct.py | 2 +- 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/docs/library/struct.rst b/docs/library/struct.rst index 026cb5e8ac..c235750596 100644 --- a/docs/library/struct.rst +++ b/docs/library/struct.rst @@ -45,6 +45,8 @@ The following data types are supported: +--------+--------------------+-------------------+---------------+ | Q | unsigned long long | integer (`1`) | 8 | +--------+--------------------+-------------------+---------------+ +| e | n/a (half-float) | float (`2`) | 2 | ++--------+--------------------+-------------------+---------------+ | f | float | float (`2`) | 4 | +--------+--------------------+-------------------+---------------+ | d | double | float (`2`) | 8 | diff --git a/py/binary.c b/py/binary.c index 4c8b6ffcdc..7c01cfa1c8 100644 --- a/py/binary.c +++ b/py/binary.c @@ -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; diff --git a/py/mpconfig.h b/py/mpconfig.h index cb0f050a74..d9cff930d1 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -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 diff --git a/tests/float/float_struct.py b/tests/float/float_struct.py index 47fe405018..6e282a6afe 100644 --- a/tests/float/float_struct.py +++ b/tests/float/float_struct.py @@ -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", "e", ">f", ">d", "