From dbbaa959c85c04dbbcde5908b5d0775b574e44e7 Mon Sep 17 00:00:00 2001 From: Yoctopuce dev Date: Fri, 6 Jun 2025 14:55:21 +0200 Subject: py/formatfloat: Improve accuracy of float formatting code. Following discussions in PR #16666, this commit updates the float formatting code to improve the `repr` reversibility, i.e. the percentage of valid floating point numbers that do parse back to the same number when formatted by `repr` (in CPython it's 100%). This new code offers a choice of 3 float conversion methods, depending on the desired tradeoff between code size and conversion precision: - BASIC method is the smallest code footprint - APPROX method uses an iterative method to approximate the exact representation, which is a bit slower but but does not have a big impact on code size. It provides `repr` reversibility on >99.8% of the cases in double precision, and on >98.5% in single precision (except with REPR_C, where reversibility is 100% as the last two bits are not taken into account). - EXACT method uses higher-precision floats during conversion, which provides perfect results but has a higher impact on code size. It is faster than APPROX method, and faster than the CPython equivalent implementation. It is however not available on all compilers when using FLOAT_IMPL_DOUBLE. Here is the table comparing the impact of the three conversion methods on code footprint on PYBV10 (using single-precision floats) and reversibility rate for both single-precision and double-precision floats. The table includes current situation as a baseline for the comparison: PYBV10 REPR_C FLOAT DOUBLE current = 364688 12.9% 27.6% 37.9% basic = 364812 85.6% 60.5% 85.7% approx = 365080 100.0% 98.5% 99.8% exact = 366408 100.0% 100.0% 100.0% Signed-off-by: Yoctopuce dev --- py/objfloat.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) (limited to 'py/objfloat.c') diff --git a/py/objfloat.c b/py/objfloat.c index 81b0daa62..125b576fb 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -110,23 +110,7 @@ mp_int_t mp_float_hash(mp_float_t src) { static void float_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { (void)kind; mp_float_t o_val = mp_obj_float_get(o_in); - #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT - char buf[16]; - #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C - const int precision = 6; - #else - const int precision = 7; - #endif - #else - char buf[32]; - const int precision = 16; - #endif - mp_format_float(o_val, buf, sizeof(buf), 'g', precision, '\0'); - mp_print_str(print, buf); - if (strchr(buf, '.') == NULL && strchr(buf, 'e') == NULL && strchr(buf, 'n') == NULL) { - // Python floats always have decimal point (unless inf or nan) - mp_print_str(print, ".0"); - } + mp_print_float(print, o_val, 'g', PF_FLAG_ALWAYS_DECIMAL, '\0', -1, MP_FLOAT_REPR_PREC); } static mp_obj_t float_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { -- cgit v1.2.3