summaryrefslogtreecommitdiff
path: root/tests/float/float_format_ints.py
diff options
context:
space:
mode:
authorYoctopuce dev <dev@yoctopuce.com>2025-06-06 14:55:21 +0200
committerDamien George <damien@micropython.org>2025-08-01 00:47:33 +1000
commitdbbaa959c85c04dbbcde5908b5d0775b574e44e7 (patch)
tree050bd1670b061788d291c0d88af22a6aad722f64 /tests/float/float_format_ints.py
parente4e1c9f4132f839dac0291557d9b992f67577fd3 (diff)
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 <dev@yoctopuce.com>
Diffstat (limited to 'tests/float/float_format_ints.py')
-rw-r--r--tests/float/float_format_ints.py32
1 files changed, 30 insertions, 2 deletions
diff --git a/tests/float/float_format_ints.py b/tests/float/float_format_ints.py
index df4444166..7b7b30c4b 100644
--- a/tests/float/float_format_ints.py
+++ b/tests/float/float_format_ints.py
@@ -12,14 +12,42 @@ for b in [13, 123, 457, 23456]:
print(title, "with format", f_fmt, "gives", f_fmt.format(f))
print(title, "with format", g_fmt, "gives", g_fmt.format(f))
+# The tests below check border cases involving all mantissa bits.
+# In case of REPR_C, where the mantissa is missing two bits, the
+# the string representation for such numbers might not always be exactly
+# the same but nevertheless be correct, so we must allow a few exceptions.
+is_REPR_C = float("1.0000001") == float("1.0")
+
# 16777215 is 2^24 - 1, the largest integer that can be completely held
# in a float32.
-print("{:f}".format(16777215))
+val_str = "{:f}".format(16777215)
+
+# When using REPR_C, 16777215.0 is the same as 16777212.0 or 16777214.4
+# (depending on the implementation of pow() function, the result may differ)
+if is_REPR_C and (val_str == "16777212.000000" or val_str == "16777214.400000"):
+ val_str = "16777215.000000"
+
+print(val_str)
+
# 4294967040 = 16777215 * 128 is the largest integer that is exactly
# represented by a float32 and that will also fit within a (signed) int32.
# The upper bound of our integer-handling code is actually double this,
# but that constant might cause trouble on systems using 32 bit ints.
-print("{:f}".format(2147483520))
+val_str = "{:f}".format(2147483520)
+
+# When using FLOAT_IMPL_FLOAT, 2147483520.0 == 2147483500.0
+# Both representations are valid, the second being "simpler"
+is_float32 = float("1e300") == float("inf")
+if is_float32 and val_str == "2147483500.000000":
+ val_str = "2147483520.000000"
+
+# When using REPR_C, 2147483520.0 is the same as 2147483200.0
+# Both representations are valid, the second being "simpler"
+if is_REPR_C and val_str == "2147483200.000000":
+ val_str = "2147483520.000000"
+
+print(val_str)
+
# Very large positive integers can be a test for precision and resolution.
# This is a weird way to represent 1e38 (largest power of 10 for float32).
print("{:.6e}".format(float("9" * 30 + "e8")))