diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/float/float_format.py | 17 | ||||
| -rw-r--r-- | tests/float/float_format_accuracy.py | 73 | ||||
| -rw-r--r-- | tests/float/float_format_ints.py | 32 | ||||
| -rw-r--r-- | tests/float/float_struct_e.py | 2 | ||||
| -rw-r--r-- | tests/float/float_struct_e_doubleprec.py | 43 | ||||
| -rw-r--r-- | tests/float/float_struct_e_fp30.py | 43 | ||||
| -rw-r--r-- | tests/float/string_format_modulo3.py | 2 | ||||
| -rw-r--r-- | tests/float/string_format_modulo3.py.exp | 2 | ||||
| -rw-r--r-- | tests/ports/unix/extra_coverage.py.exp | 4 | ||||
| -rw-r--r-- | tests/ports/unix/ffi_float2.py | 3 | ||||
| -rw-r--r-- | tests/ports/unix/ffi_float2.py.exp | 12 | ||||
| -rwxr-xr-x | tests/run-tests.py | 4 |
12 files changed, 217 insertions, 20 deletions
diff --git a/tests/float/float_format.py b/tests/float/float_format.py index 98ed0eb09..0eb8b232b 100644 --- a/tests/float/float_format.py +++ b/tests/float/float_format.py @@ -2,14 +2,25 @@ # general rounding for val in (116, 1111, 1234, 5010, 11111): - print("%.0f" % val) - print("%.1f" % val) - print("%.3f" % val) + 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")) diff --git a/tests/float/float_format_accuracy.py b/tests/float/float_format_accuracy.py new file mode 100644 index 000000000..f9467f9c0 --- /dev/null +++ b/tests/float/float_format_accuracy.py @@ -0,0 +1,73 @@ +# Test accuracy of `repr` conversions. +# This test also increases code coverage for corner cases. + +try: + import array, math, random +except ImportError: + print("SKIP") + raise SystemExit + +# The largest errors come from seldom used very small numbers, near the +# limit of the representation. So we keep them out of this test to keep +# the max relative error display useful. +if float("1e-100") == 0.0: + # single-precision + float_type = "f" + float_size = 4 + # testing range + min_expo = -96 # i.e. not smaller than 1.0e-29 + # Expected results (given >=50'000 samples): + # - MICROPY_FLTCONV_IMPL_EXACT: 100% exact conversions + # - MICROPY_FLTCONV_IMPL_APPROX: >=98.53% exact conversions, max relative error <= 1.01e-7 + min_success = 0.980 # with only 1200 samples, the success rate is lower + max_rel_err = 1.1e-7 + # REPR_C is typically used with FORMAT_IMPL_BASIC, which has a larger error + is_REPR_C = float("1.0000001") == float("1.0") + if is_REPR_C: # REPR_C + min_success = 0.83 + max_rel_err = 5.75e-07 +else: + # double-precision + float_type = "d" + float_size = 8 + # testing range + min_expo = -845 # i.e. not smaller than 1.0e-254 + # Expected results (given >=200'000 samples): + # - MICROPY_FLTCONV_IMPL_EXACT: 100% exact conversions + # - MICROPY_FLTCONV_IMPL_APPROX: >=99.83% exact conversions, max relative error <= 2.7e-16 + min_success = 0.997 # with only 1200 samples, the success rate is lower + max_rel_err = 2.7e-16 + + +# Deterministic pseudorandom generator. Designed to be uniform +# on mantissa values and exponents, not on the represented number +def pseudo_randfloat(): + rnd_buff = bytearray(float_size) + for _ in range(float_size): + rnd_buff[_] = random.getrandbits(8) + return array.array(float_type, rnd_buff)[0] + + +random.seed(42) +stats = 0 +N = 1200 +max_err = 0 +for _ in range(N): + f = pseudo_randfloat() + while type(f) is not float or math.isinf(f) or math.isnan(f) or math.frexp(f)[1] <= min_expo: + f = pseudo_randfloat() + + str_f = repr(f) + f2 = float(str_f) + if f2 == f: + stats += 1 + else: + error = abs((f2 - f) / f) + if max_err < error: + max_err = error + +print(N, "values converted") +if stats / N >= min_success and max_err <= max_rel_err: + print("float format accuracy OK") +else: + print("FAILED: repr rate=%.3f%% max_err=%.3e" % (100 * stats / N, max_err)) 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"))) diff --git a/tests/float/float_struct_e.py b/tests/float/float_struct_e.py index 403fbc5db..ba4134f33 100644 --- a/tests/float/float_struct_e.py +++ b/tests/float/float_struct_e.py @@ -32,7 +32,7 @@ for j in test_values: for i in (j, -j): x = struct.pack("<e", i) v = struct.unpack("<e", x)[0] - print("%.7f %s %.15f %s" % (i, x, v, i == v)) + print("%.7f %s %.7f %s" % (i, x, v, i == v)) # In CPython, packing a float that doesn't fit into a half-float raises OverflowError. # But in MicroPython it does not, but rather stores the value as inf. diff --git a/tests/float/float_struct_e_doubleprec.py b/tests/float/float_struct_e_doubleprec.py new file mode 100644 index 000000000..403fbc5db --- /dev/null +++ b/tests/float/float_struct_e_doubleprec.py @@ -0,0 +1,43 @@ +# Test struct pack/unpack with 'e' typecode. + +try: + import struct +except ImportError: + print("SKIP") + raise SystemExit + +test_values = ( + 1e-7, + 2e-7, + 1e-6, + 1e-5, + 1e-4, + 1e-3, + 1e-2, + 0.1, + 0, + 1, + 2, + 4, + 8, + 10, + 100, + 1e3, + 1e4, + 6e4, + float("inf"), +) + +for j in test_values: + for i in (j, -j): + x = struct.pack("<e", i) + v = struct.unpack("<e", x)[0] + print("%.7f %s %.15f %s" % (i, x, v, i == v)) + +# In CPython, packing a float that doesn't fit into a half-float raises OverflowError. +# But in MicroPython it does not, but rather stores the value as inf. +# This test is here for coverage. +try: + struct.pack("e", 1e15) +except OverflowError: + pass diff --git a/tests/float/float_struct_e_fp30.py b/tests/float/float_struct_e_fp30.py new file mode 100644 index 000000000..6ef84ca06 --- /dev/null +++ b/tests/float/float_struct_e_fp30.py @@ -0,0 +1,43 @@ +# Test struct pack/unpack with 'e' typecode. + +try: + import struct +except ImportError: + print("SKIP") + raise SystemExit + +test_values = ( + 1e-7, + 2e-7, + 1e-6, + 1e-5, + 1e-4, + 1e-3, + 1e-2, + 0.1, + 0, + 1, + 2, + 4, + 8, + 10, + 100, + 1e3, + 1e4, + 6e4, + float("inf"), +) + +for j in test_values: + for i in (j, -j): + x = struct.pack("<e", i) + v = struct.unpack("<e", x)[0] + print("%.7f %s %.5f %s" % (i, x, v, i == v)) + +# In CPython, packing a float that doesn't fit into a half-float raises OverflowError. +# But in MicroPython it does not, but rather stores the value as inf. +# This test is here for coverage. +try: + struct.pack("e", 1e15) +except OverflowError: + pass diff --git a/tests/float/string_format_modulo3.py b/tests/float/string_format_modulo3.py index f9d9c43cd..f8aeeda20 100644 --- a/tests/float/string_format_modulo3.py +++ b/tests/float/string_format_modulo3.py @@ -1,3 +1,3 @@ -# uPy and CPython outputs differ for the following +# Test corner cases where MicroPython and CPython outputs used to differ in the past print("%.1g" % -9.9) # round up 'g' with '-' sign print("%.2g" % 99.9) # round up diff --git a/tests/float/string_format_modulo3.py.exp b/tests/float/string_format_modulo3.py.exp deleted file mode 100644 index 71432b340..000000000 --- a/tests/float/string_format_modulo3.py.exp +++ /dev/null @@ -1,2 +0,0 @@ --10 -100 diff --git a/tests/ports/unix/extra_coverage.py.exp b/tests/ports/unix/extra_coverage.py.exp index e20871273..85e0e18cb 100644 --- a/tests/ports/unix/extra_coverage.py.exp +++ b/tests/ports/unix/extra_coverage.py.exp @@ -127,10 +127,6 @@ TypeError: can't convert NoneType to int TypeError: can't convert NoneType to int ValueError: Warning: test -# format float -? -+1e+00 -+1e+00 # binary 123 456 diff --git a/tests/ports/unix/ffi_float2.py b/tests/ports/unix/ffi_float2.py index bbed69666..eac6cd106 100644 --- a/tests/ports/unix/ffi_float2.py +++ b/tests/ports/unix/ffi_float2.py @@ -29,4 +29,5 @@ except OSError: for fun in (tgammaf,): for val in (0.5, 1, 1.0, 1.5, 4, 4.0): - print("%.6f" % fun(val)) + # limit to 5 decimals in order to pass with REPR_C with FORMAT_IMPL_BASIC + print("%.5f" % fun(val)) diff --git a/tests/ports/unix/ffi_float2.py.exp b/tests/ports/unix/ffi_float2.py.exp index 58fc6a01a..4c750e223 100644 --- a/tests/ports/unix/ffi_float2.py.exp +++ b/tests/ports/unix/ffi_float2.py.exp @@ -1,6 +1,6 @@ -1.772454 -1.000000 -1.000000 -0.886227 -6.000000 -6.000000 +1.77245 +1.00000 +1.00000 +0.88623 +6.00000 +6.00000 diff --git a/tests/run-tests.py b/tests/run-tests.py index 59aec327f..48dfc2f26 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -785,12 +785,16 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add( "float/float2int_intbig.py" ) # requires fp32, there's float2int_fp30_intbig.py instead + skip_tests.add( + "float/float_struct_e.py" + ) # requires fp32, there's float_struct_e_fp30.py instead skip_tests.add("float/bytes_construct.py") # requires fp32 skip_tests.add("float/bytearray_construct.py") # requires fp32 skip_tests.add("float/float_format_ints_power10.py") # requires fp32 if upy_float_precision < 64: skip_tests.add("float/float_divmod.py") # tested by float/float_divmod_relaxed.py instead skip_tests.add("float/float2int_doubleprec_intbig.py") + skip_tests.add("float/float_struct_e_doubleprec.py") skip_tests.add("float/float_format_ints_doubleprec.py") skip_tests.add("float/float_parse_doubleprec.py") |
