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>
38 lines
1.4 KiB
Python
38 lines
1.4 KiB
Python
# test float formatting
|
|
|
|
# general rounding
|
|
for val in (116, 1111, 1234, 5010, 11111):
|
|
print("Test on %d / 1000:" % val)
|
|
for fmt in ("%.5e", "%.3e", "%.1e", "%.0e", "%.3f", "%.1f", "%.0f", "%.3g", "%.1g", "%.0g"):
|
|
print(fmt, fmt % (val / 1000))
|
|
|
|
# make sure round-up to the next unit is handled properly
|
|
for val in range(4, 9):
|
|
divi = 10**val
|
|
print("Test on 99994 / (10 ** %d):" % val)
|
|
for fmt in ("%.5e", "%.3e", "%.1e", "%.0e", "%.3f", "%.1f", "%.0f", "%.3g", "%.1g", "%.0g"):
|
|
print(fmt, fmt % (99994 / divi))
|
|
|
|
# make sure rounding is done at the correct precision
|
|
for prec in range(8):
|
|
print(("%%.%df" % prec) % 6e-5)
|
|
|
|
# make sure trailing zeroes are added properly
|
|
for prec in range(8):
|
|
print(("%%.%df" % prec) % 1e19)
|
|
|
|
# check certain cases that had a digit value of 10 render as a ":" character
|
|
print("%.2e" % float("9" * 51 + "e-39"))
|
|
print("%.2e" % float("9" * 40 + "e-21"))
|
|
|
|
# check a case that would render negative digit values, eg ")" characters
|
|
# the string is converted back to a float to check for no illegal characters
|
|
float("%.23e" % 1e-80)
|
|
|
|
# Check a problem with malformed "e" format numbers on the edge of 1.0e-X.
|
|
for r in range(38):
|
|
s = "%.12e" % float("1e-" + str(r))
|
|
# It may format as 1e-r, or 9.999...e-(r+1), both are OK.
|
|
# But formatting as 0.999...e-r is NOT ok.
|
|
if s[0] == "0":
|
|
print("FAIL:", s)
|