Skip to content

Commit e520fa2

Browse files
smurfixdpgeorge
authored andcommitted
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>
1 parent 77f08b7 commit e520fa2

File tree

4 files changed

+119
-3
lines changed

4 files changed

+119
-3
lines changed

docs/library/struct.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ The following data types are supported:
4545
+--------+--------------------+-------------------+---------------+
4646
| Q | unsigned long long | integer (`1<fn>`) | 8 |
4747
+--------+--------------------+-------------------+---------------+
48+
| e | n/a (half-float) | float (`2<fn>`) | 2 |
49+
+--------+--------------------+-------------------+---------------+
4850
| f | float | float (`2<fn>`) | 4 |
4951
+--------+--------------------+-------------------+---------------+
5052
| d | double | float (`2<fn>`) | 8 |

py/binary.c

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,14 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
7474
case 'S':
7575
size = sizeof(void *);
7676
break;
77+
case 'e':
78+
size = 2;
79+
break;
7780
case 'f':
78-
size = sizeof(float);
81+
size = 4;
7982
break;
8083
case 'd':
81-
size = sizeof(double);
84+
size = 8;
8285
break;
8386
}
8487
break;
@@ -122,6 +125,10 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
122125
align = alignof(void *);
123126
size = sizeof(void *);
124127
break;
128+
case 'e':
129+
align = 2;
130+
size = 2;
131+
break;
125132
case 'f':
126133
align = alignof(float);
127134
size = sizeof(float);
@@ -144,6 +151,99 @@ size_t mp_binary_get_size(char struct_type, char val_type, size_t *palign) {
144151
return size;
145152
}
146153

154+
#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_USE_NATIVE_FLT16
155+
156+
static inline float mp_decode_half_float(uint16_t hf) {
157+
union {
158+
uint16_t i;
159+
_Float16 f;
160+
} fpu = { .i = hf };
161+
return fpu.f;
162+
}
163+
164+
static inline uint16_t mp_encode_half_float(float x) {
165+
union {
166+
uint16_t i;
167+
_Float16 f;
168+
} fp_sp = { .f = (_Float16)x };
169+
return fp_sp.i;
170+
}
171+
172+
#elif MICROPY_PY_BUILTINS_FLOAT
173+
174+
static float mp_decode_half_float(uint16_t hf) {
175+
union {
176+
uint32_t i;
177+
float f;
178+
} fpu;
179+
180+
uint16_t m = hf & 0x3ff;
181+
int e = (hf >> 10) & 0x1f;
182+
if (e == 0x1f) {
183+
// Half-float is infinity.
184+
e = 0xff;
185+
} else if (e) {
186+
// Half-float is normal.
187+
e += 127 - 15;
188+
} else if (m) {
189+
// Half-float is subnormal, make it normal.
190+
e = 127 - 15;
191+
while (!(m & 0x400)) {
192+
m <<= 1;
193+
--e;
194+
}
195+
m -= 0x400;
196+
++e;
197+
}
198+
199+
fpu.i = ((hf & 0x8000) << 16) | (e << 23) | (m << 13);
200+
return fpu.f;
201+
}
202+
203+
static uint16_t mp_encode_half_float(float x) {
204+
union {
205+
uint32_t i;
206+
float f;
207+
} fpu = { .f = x };
208+
209+
uint16_t m = (fpu.i >> 13) & 0x3ff;
210+
if (fpu.i & (1 << 12)) {
211+
// Round up.
212+
++m;
213+
}
214+
int e = (fpu.i >> 23) & 0xff;
215+
216+
if (e == 0xff) {
217+
// Infinity.
218+
e = 0x1f;
219+
} else if (e != 0) {
220+
e -= 127 - 15;
221+
if (e < 0) {
222+
// Underflow: denormalized, or zero.
223+
if (e >= -11) {
224+
m = (m | 0x400) >> -e;
225+
if (m & 1) {
226+
m = (m >> 1) + 1;
227+
} else {
228+
m >>= 1;
229+
}
230+
} else {
231+
m = 0;
232+
}
233+
e = 0;
234+
} else if (e > 0x3f) {
235+
// Overflow: infinity.
236+
e = 0x1f;
237+
m = 0;
238+
}
239+
}
240+
241+
uint16_t bits = ((fpu.i >> 16) & 0x8000) | (e << 10) | m;
242+
return bits;
243+
}
244+
245+
#endif
246+
147247
mp_obj_t mp_binary_get_val_array(char typecode, void *p, size_t index) {
148248
mp_int_t val = 0;
149249
switch (typecode) {
@@ -240,6 +340,8 @@ mp_obj_t mp_binary_get_val(char struct_type, char val_type, byte *p_base, byte *
240340
const char *s_val = (const char *)(uintptr_t)(mp_uint_t)val;
241341
return mp_obj_new_str(s_val, strlen(s_val));
242342
#if MICROPY_PY_BUILTINS_FLOAT
343+
} else if (val_type == 'e') {
344+
return mp_obj_new_float_from_f(mp_decode_half_float(val));
243345
} else if (val_type == 'f') {
244346
union {
245347
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
309411
val = (mp_uint_t)val_in;
310412
break;
311413
#if MICROPY_PY_BUILTINS_FLOAT
414+
case 'e':
415+
val = mp_encode_half_float(mp_obj_get_float_to_f(val_in));
416+
break;
312417
case 'f': {
313418
union {
314419
uint32_t i;

py/mpconfig.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,15 @@ typedef double mp_float_t;
830830
#define MICROPY_PY_BUILTINS_COMPLEX (MICROPY_PY_BUILTINS_FLOAT)
831831
#endif
832832

833+
// Whether to use the native _Float16 for 16-bit float support
834+
#ifndef MICROPY_FLOAT_USE_NATIVE_FLT16
835+
#ifdef __FLT16_MAX__
836+
#define MICROPY_FLOAT_USE_NATIVE_FLT16 (1)
837+
#else
838+
#define MICROPY_FLOAT_USE_NATIVE_FLT16 (0)
839+
#endif
840+
#endif
841+
833842
// Whether to provide a high-quality hash for float and complex numbers.
834843
// Otherwise the default is a very simple but correct hashing function.
835844
#ifndef MICROPY_FLOAT_HIGH_QUALITY_HASH

tests/float/float_struct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
i = 1.0 + 1 / 2
99
# TODO: it looks like '=' format modifier is not yet supported
1010
# for fmt in ('f', 'd', '>f', '>d', '<f', '<d', '=f', '=d'):
11-
for fmt in ("f", "d", ">f", ">d", "<f", "<d"):
11+
for fmt in ("e", "f", "d", ">e", ">f", ">d", "<e", "<f", "<d"):
1212
x = struct.pack(fmt, i)
1313
v = struct.unpack(fmt, x)[0]
1414
print("%2s: %.17f - %s" % (fmt, v, (i == v) and "passed" or "failed"))

0 commit comments

Comments
 (0)