From fb38153938f1c320ec6c1300bf056153d034017e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 12 Jun 2025 23:55:32 +0200 Subject: [PATCH 01/15] [format] Add a check for standard scientific notation --- pylint/checkers/format.py | 35 +++++++- .../u/use/use_standard_scientific_notation.py | 82 +++++++++++++++++++ .../use/use_standard_scientific_notation.txt | 11 +++ 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 tests/functional/u/use/use_standard_scientific_notation.py create mode 100644 tests/functional/u/use/use_standard_scientific_notation.txt diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index a7375859e3..a90f57a3d1 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -111,6 +111,11 @@ "unexpected-line-ending-format", "Used when there is different newline than expected.", ), + "C0329": ( + "Scientific notation should be '%s' instead", + "use-standard-scientific-notation", + "Emitted when a number is written in non-standard scientific notation.", + ), } @@ -372,6 +377,16 @@ def _check_keyword_parentheses( self._check_keyword_parentheses(tokens[i:], 0) return + @staticmethod + def to_standard_scientific_notation(number: float) -> str: + # number is always going to be > 0 because node constants are always positive + # Format with high precision to capture all digits + s = f"{number:.15e}" + base, exp = s.split("e") + # Remove trailing zeros and possible trailing decimal point + base = base.rstrip("0").rstrip(".") + return f"{base}e{int(exp)}" + def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: """Process tokens and search for: @@ -430,9 +445,23 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: if check_equal: check_equal = False self.check_indent_level(line, indents[-1], line_num) - - if tok_type == tokenize.NUMBER and string.endswith("l"): - self.add_message("lowercase-l-suffix", line=line_num) + if tok_type == tokenize.NUMBER: + # Check for wrong scientific notation + if ( + ("e" in string or "E" in string) + and "x" not in string # not a hexadecimal + and "j" not in string # not a complex + ): + value = float(string.lower().split("e")[0]) + if not (1 <= value < 10): + self.add_message( + "use-standard-scientific-notation", + args=(self.to_standard_scientific_notation(value)), + line=line_num, + col_offset=start[1], + ) + if string.endswith("l"): + self.add_message("lowercase-l-suffix", line=line_num) if string in _KEYWORD_TOKENS: self._check_keyword_parentheses(tokens, idx) diff --git a/tests/functional/u/use/use_standard_scientific_notation.py b/tests/functional/u/use/use_standard_scientific_notation.py new file mode 100644 index 0000000000..23f0043889 --- /dev/null +++ b/tests/functional/u/use/use_standard_scientific_notation.py @@ -0,0 +1,82 @@ +# pylint: disable=missing-docstring,invalid-name + +wrong_big = 45.3e6 # [use-standard-scientific-notation] +uppercase_e_wrong = 45.3E6 # [use-standard-scientific-notation] +wrong_small = 0.00012e-26 # [use-standard-scientific-notation] +wrong_negative_and_big = -10e3 # [use-standard-scientific-notation] +actual_trolling = 11000e26 # [use-standard-scientific-notation] +scientific_double_digit = 12e8 # [use-standard-scientific-notation] +scientific_triple_digit = 123e3 # [use-standard-scientific-notation] +zero_before_decimal_small = 0.0001e-5 # [use-standard-scientific-notation] +zero_before_decimal_big = 0.0001e5 # [use-standard-scientific-notation] +negative_decimal = -0.5e10 # [use-standard-scientific-notation] +zero_only = 0e10 # [use-standard-scientific-notation] + +one_only = 1e6 +correct_1 = 4.53e7 +uppercase_e_correct = 4.53E7 +uppercase_e_with_plus = 1.2E+10 +uppercase_e_with_minus = 5.67E-8 +correct_2 = 1.2e-28 +correct_3 = -1.0e4 +correct_4 = 1.1E30 +correct_with_digits = 4.567e8 +correct_with_plus = 1.2e+10 +correct_decimal_only = 3.14 +negative_correct = -5.67e-8 +correct_small_exponent = 1.5e1 +correct_tiny_exponent = 9.0e0 +correct_precise = 6.02214076e23 + +hex_constant = 0x1e4 # Hexadecimal, not scientific notation +binary_constant = 0b1010 +octal_constant = 0o1234 +inside_string = "Temperature: 10e3 degrees" +inside_multiline = """ +This is a test with 45.3e6 inside +""" +inside_comment = 1.0 # This comment has 12e4 in it +in_variable_name = measurement_10e3 = 45 +inside_f_string = f"Value is {1.0} not 10e6" + +# Potential false negatives +barely_violation = 9.99e0 # Should this be 9.99? +integer_sci = int(1e10) # Integer call with scientific notation +complex_number = 1.5e3 + 2.5e3j # Complex number with scientific notation +tuple_of_sci = (1.2e4, 3.4e5) +list_of_sci = [5.6e6, 7.8e7] +dict_with_sci = {"a": 9.1e8, "b": 1.2e9} + +# Mathematical operations +addition = 1.0e3 + 2.0e3 +multiplication = 1.0e3 * 2.0 +division = 1.0e3 / 2.0 +power = 1.0e3 ** 2.0 + +# Function calls with scientific notation +def function_with_sci(param=1.0e3, other_param=2.0e3): + return param, other_param + +result = function_with_sci(2.0e3) +positional_and_keyword = function_with_sci(1.0, other_param=3.0e4) + +# Assignments with operations +a = 1 +a += 1.0e3 +b = 2 +b *= 2.0e3 + +# Scientific notation in different contexts +inside_list_comp = [x * 2 for x in [1.0e3, 2.0e3]] +inside_dict_comp = {str(x): x for x in [3.0e3, 4.0e3]} +inside_generator = (x + 1 for x in [5.0e3, 6.0e3]) + +# Boundary cases for normalization +boundary_small = 9.999e0 # Almost 10, but not quite +boundary_large = 1.001e0 # Just above 1 +boundary_case = 1.0e0 # Equal to 1 + +# Constants from physics/science (correctly formatted) +speed_of_light = 2.99792458e8 # m/s +planck_constant = 6.62607015e-34 # J⋅s +electron_charge = 1.602176634e-19 # C diff --git a/tests/functional/u/use/use_standard_scientific_notation.txt b/tests/functional/u/use/use_standard_scientific_notation.txt new file mode 100644 index 0000000000..83eec9f988 --- /dev/null +++ b/tests/functional/u/use/use_standard_scientific_notation.txt @@ -0,0 +1,11 @@ +use-standard-scientific-notation:3:12:None:None::Scientific notation should be '4.53e1' instead:UNDEFINED +use-standard-scientific-notation:4:20:None:None::Scientific notation should be '4.53e1' instead:UNDEFINED +use-standard-scientific-notation:5:14:None:None::Scientific notation should be '1.2e-4' instead:UNDEFINED +use-standard-scientific-notation:6:26:None:None::Scientific notation should be '1e1' instead:UNDEFINED +use-standard-scientific-notation:7:18:None:None::Scientific notation should be '1.1e4' instead:UNDEFINED +use-standard-scientific-notation:8:26:None:None::Scientific notation should be '1.2e1' instead:UNDEFINED +use-standard-scientific-notation:9:26:None:None::Scientific notation should be '1.23e2' instead:UNDEFINED +use-standard-scientific-notation:10:28:None:None::Scientific notation should be '1e-4' instead:UNDEFINED +use-standard-scientific-notation:11:26:None:None::Scientific notation should be '1e-4' instead:UNDEFINED +use-standard-scientific-notation:12:20:None:None::Scientific notation should be '5e-1' instead:UNDEFINED +use-standard-scientific-notation:13:12:None:None::Scientific notation should be '0e0' instead:UNDEFINED From c741b28ef503a49af5f32e6e7554b4d5097306b8 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 13 Jun 2025 08:58:36 +0200 Subject: [PATCH 02/15] Add esoteric-underscore-grouping Co-authored-by: shauss --- pylint/checkers/format.py | 59 +++++++++++-- tests/checkers/unittest_format.py | 14 +++ .../u/use/use_standard_scientific_notation.py | 87 +++++++++++-------- .../use/use_standard_scientific_notation.txt | 66 +++++++++++--- 4 files changed, 176 insertions(+), 50 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index a90f57a3d1..fe52419358 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -13,6 +13,7 @@ from __future__ import annotations +import re import tokenize from functools import reduce from re import Match @@ -116,6 +117,11 @@ "use-standard-scientific-notation", "Emitted when a number is written in non-standard scientific notation.", ), + "C0331": ( + "Non standard grouping of numeric literals using underscores should be %s", + "esoteric-underscore-grouping", + "Used when numeric literals use underscore separators not in groups of 3 digits.", + ), } @@ -387,6 +393,32 @@ def to_standard_scientific_notation(number: float) -> str: base = base.rstrip("0").rstrip(".") return f"{base}e{int(exp)}" + @staticmethod + def to_standard_underscore_grouping(number: str) -> str: + if "e" in number.lower() or "E" in number.lower(): + return FormatChecker.to_standard_scientific_notation(float(number)) + + number = number.replace("_", "") + # Split into whole and decimal parts (if present) + if "." in number: + whole, decimal = number.split(".") + else: + whole, decimal = number, "" + + # Format whole part with proper grouping (right to left) + if len(whole) > 3: + grouped_whole = "" + for i in range(len(whole), 0, -3): + start = max(0, i - 3) + group = whole[start:i] + if grouped_whole: + grouped_whole = group + "_" + grouped_whole + else: + grouped_whole = group + whole = grouped_whole + return f"{whole}.{decimal}" if decimal else whole + + # pylint: disable-next=too-many-branches,too-many-statements def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: """Process tokens and search for: @@ -446,12 +478,15 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: check_equal = False self.check_indent_level(line, indents[-1], line_num) if tok_type == tokenize.NUMBER: - # Check for wrong scientific notation - if ( - ("e" in string or "E" in string) - and "x" not in string # not a hexadecimal + not_hex_oct_or_complex = ( + # You don't deserve a linter if you mix non-decimal notation with + # and exponential or underscore, + "x" not in string # not a hexadecimal + and "o" not in string # not an octal and "j" not in string # not a complex - ): + ) + # Wrong scientific notation + if ("e" in string or "E" in string) and not_hex_oct_or_complex: value = float(string.lower().split("e")[0]) if not (1 <= value < 10): self.add_message( @@ -459,6 +494,20 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: args=(self.to_standard_scientific_notation(value)), line=line_num, col_offset=start[1], + confidence=HIGH, + ) + # proper underscore grouping in numeric literals + if "_" in string and not_hex_oct_or_complex: + if not re.match( + r"^\d{0,3}(_\d{3})*\.?\d*([eE]-?\d{0,3}(_\d{3})*)?$", string + ): + suggested = self.to_standard_underscore_grouping(string) + self.add_message( + "esoteric-underscore-grouping", + args=(suggested), + line=line_num, + col_offset=start[1], + confidence=HIGH, ) if string.endswith("l"): self.add_message("lowercase-l-suffix", line=line_num) diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index c0659ad763..d9043cd400 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -9,6 +9,7 @@ import tokenize import astroid +import pytest from pylint import lint, reporters from pylint.checkers.base.basic_checker import BasicChecker @@ -180,3 +181,16 @@ def test_disable_global_option_end_of_line() -> None: assert not myreporter.messages finally: os.remove(file_.name) + + +@pytest.mark.parametrize( + "value, expected", + [ + ("1_000_000", "1_000_000"), + ("1000_000", "1_000_000"), + ("10_5415_456_4654984.16354698489", "1_054_154_564_654_984.16354698489"), + ], +) +def test_to_standard_underscore_grouping(value: str, expected: str) -> None: + """Test the conversion of numbers to standard underscore grouping.""" + assert FormatChecker.to_standard_underscore_grouping(value) == expected diff --git a/tests/functional/u/use/use_standard_scientific_notation.py b/tests/functional/u/use/use_standard_scientific_notation.py index 23f0043889..727961f1d0 100644 --- a/tests/functional/u/use/use_standard_scientific_notation.py +++ b/tests/functional/u/use/use_standard_scientific_notation.py @@ -3,6 +3,7 @@ wrong_big = 45.3e6 # [use-standard-scientific-notation] uppercase_e_wrong = 45.3E6 # [use-standard-scientific-notation] wrong_small = 0.00012e-26 # [use-standard-scientific-notation] +uppercase_e_wrong_small = 0.00012E-26 # [use-standard-scientific-notation] wrong_negative_and_big = -10e3 # [use-standard-scientific-notation] actual_trolling = 11000e26 # [use-standard-scientific-notation] scientific_double_digit = 12e8 # [use-standard-scientific-notation] @@ -25,10 +26,12 @@ correct_decimal_only = 3.14 negative_correct = -5.67e-8 correct_small_exponent = 1.5e1 -correct_tiny_exponent = 9.0e0 -correct_precise = 6.02214076e23 +actually_nine = 9e0 +actually_one = 1.0e0 + hex_constant = 0x1e4 # Hexadecimal, not scientific notation +hex_constant_bad = 0x10e4 binary_constant = 0b1010 octal_constant = 0o1234 inside_string = "Temperature: 10e3 degrees" @@ -39,44 +42,60 @@ in_variable_name = measurement_10e3 = 45 inside_f_string = f"Value is {1.0} not 10e6" -# Potential false negatives -barely_violation = 9.99e0 # Should this be 9.99? -integer_sci = int(1e10) # Integer call with scientific notation complex_number = 1.5e3 + 2.5e3j # Complex number with scientific notation -tuple_of_sci = (1.2e4, 3.4e5) -list_of_sci = [5.6e6, 7.8e7] -dict_with_sci = {"a": 9.1e8, "b": 1.2e9} +# false negative for complex numbers: +complex_number_wrong = 15e3 + 25e3j # [use-standard-scientific-notation] -# Mathematical operations -addition = 1.0e3 + 2.0e3 -multiplication = 1.0e3 * 2.0 -division = 1.0e3 / 2.0 -power = 1.0e3 ** 2.0 -# Function calls with scientific notation -def function_with_sci(param=1.0e3, other_param=2.0e3): +#+1: [use-standard-scientific-notation, use-standard-scientific-notation] +def function_with_sci(param=10.0e3, other_param=20.0e3): return param, other_param -result = function_with_sci(2.0e3) -positional_and_keyword = function_with_sci(1.0, other_param=3.0e4) +#+1: [use-standard-scientific-notation, use-standard-scientific-notation] +result = function_with_sci(20.0e3, 10.0e3) + +valid_underscore_int = 1_000_000 +valid_underscore_float = 1_000_000.12345 +valid_underscore_float_exp = 123_000_000.12345e12_000_000 # [use-standard-scientific-notation] +valid_underscore_float_exp_cap = 123_000_000.12345E123_000_000 # [use-standard-scientific-notation] -# Assignments with operations -a = 1 -a += 1.0e3 -b = 2 -b *= 2.0e3 +invalid_underscore_octal = 0o123_456 # octal with underscores bypassed +invalid_underscore_hexa = 0x12c_456 # hexa with underscores bypassed -# Scientific notation in different contexts -inside_list_comp = [x * 2 for x in [1.0e3, 2.0e3]] -inside_dict_comp = {str(x): x for x in [3.0e3, 4.0e3]} -inside_generator = (x + 1 for x in [5.0e3, 6.0e3]) +invalid_underscore_float_no_int = .123_456 # [esoteric-underscore-grouping] +invalid_underscore_float_no_frac = 123_456.123_456 # [esoteric-underscore-grouping] +incorrect_sci_underscore = 1.234_567e6 # [esoteric-underscore-grouping] +incorrect_sci_uppercase = 1.234_567E6 # [esoteric-underscore-grouping] +incorrect_sci_underscore_exp = 1.2e1_0 # [esoteric-underscore-grouping] +invalid_underscore_float = 1_234.567_89 # [esoteric-underscore-grouping] +invalid_underscore_binary = 0b1010_1010 # [esoteric-underscore-grouping] +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +wrong_big_underscore = 45.3_45e6 +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +wrong_small_underscore = 0.000_12e-26 +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +scientific_double_digit_underscore = 1_2e8 +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +scientific_triple_digit_underscore = 12_3e3 +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +invalid_underscore_sci = 1_234.567_89e10 +invalid_underscore_sci_exp = 1.2e1_0 # [esoteric-underscore-grouping] +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +invalid_underscore_sci_combined = 1_2.3_4e5_6 +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +invalid_uppercase_sci = 1_234.567_89E10 +edge_underscore_1 = 1_0e6 # [use-standard-scientific-notation, esoteric-underscore-grouping] +mixed_underscore_1 = 1_000_000.0e-3 # [use-standard-scientific-notation] +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +mixed_underscore_2 = 0.000_001e3 +mixed_underscore_3 = 1_0.0e2 # [use-standard-scientific-notation, esoteric-underscore-grouping] -# Boundary cases for normalization -boundary_small = 9.999e0 # Almost 10, but not quite -boundary_large = 1.001e0 # Just above 1 -boundary_case = 1.0e0 # Equal to 1 +# Complex numbers with underscores +complex_underscore = 1.5_6e3 + 2.5_6e3j # [esoteric-underscore-grouping] +#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] +complex_underscore_wrong = 15_6e2 + 25_6e2j -# Constants from physics/science (correctly formatted) -speed_of_light = 2.99792458e8 # m/s -planck_constant = 6.62607015e-34 # J⋅s -electron_charge = 1.602176634e-19 # C +#+2: [esoteric-underscore-grouping, esoteric-underscore-grouping] +#+1: [use-standard-scientific-notation, use-standard-scientific-notation] +def function_with_underscore(param=10.0_0e3, other_param=20.0_0e3): + return param, other_param diff --git a/tests/functional/u/use/use_standard_scientific_notation.txt b/tests/functional/u/use/use_standard_scientific_notation.txt index 83eec9f988..4414ffeb8f 100644 --- a/tests/functional/u/use/use_standard_scientific_notation.txt +++ b/tests/functional/u/use/use_standard_scientific_notation.txt @@ -1,11 +1,55 @@ -use-standard-scientific-notation:3:12:None:None::Scientific notation should be '4.53e1' instead:UNDEFINED -use-standard-scientific-notation:4:20:None:None::Scientific notation should be '4.53e1' instead:UNDEFINED -use-standard-scientific-notation:5:14:None:None::Scientific notation should be '1.2e-4' instead:UNDEFINED -use-standard-scientific-notation:6:26:None:None::Scientific notation should be '1e1' instead:UNDEFINED -use-standard-scientific-notation:7:18:None:None::Scientific notation should be '1.1e4' instead:UNDEFINED -use-standard-scientific-notation:8:26:None:None::Scientific notation should be '1.2e1' instead:UNDEFINED -use-standard-scientific-notation:9:26:None:None::Scientific notation should be '1.23e2' instead:UNDEFINED -use-standard-scientific-notation:10:28:None:None::Scientific notation should be '1e-4' instead:UNDEFINED -use-standard-scientific-notation:11:26:None:None::Scientific notation should be '1e-4' instead:UNDEFINED -use-standard-scientific-notation:12:20:None:None::Scientific notation should be '5e-1' instead:UNDEFINED -use-standard-scientific-notation:13:12:None:None::Scientific notation should be '0e0' instead:UNDEFINED +use-standard-scientific-notation:3:12:None:None::Scientific notation should be '4.53e1' instead:HIGH +use-standard-scientific-notation:4:20:None:None::Scientific notation should be '4.53e1' instead:HIGH +use-standard-scientific-notation:5:14:None:None::Scientific notation should be '1.2e-4' instead:HIGH +use-standard-scientific-notation:6:26:None:None::Scientific notation should be '1.2e-4' instead:HIGH +use-standard-scientific-notation:7:26:None:None::Scientific notation should be '1e1' instead:HIGH +use-standard-scientific-notation:8:18:None:None::Scientific notation should be '1.1e4' instead:HIGH +use-standard-scientific-notation:9:26:None:None::Scientific notation should be '1.2e1' instead:HIGH +use-standard-scientific-notation:10:26:None:None::Scientific notation should be '1.23e2' instead:HIGH +use-standard-scientific-notation:11:28:None:None::Scientific notation should be '1e-4' instead:HIGH +use-standard-scientific-notation:12:26:None:None::Scientific notation should be '1e-4' instead:HIGH +use-standard-scientific-notation:13:20:None:None::Scientific notation should be '5e-1' instead:HIGH +use-standard-scientific-notation:14:12:None:None::Scientific notation should be '0e0' instead:HIGH +use-standard-scientific-notation:47:23:None:None::Scientific notation should be '1.5e1' instead:HIGH +use-standard-scientific-notation:51:28:None:None::Scientific notation should be '1e1' instead:HIGH +use-standard-scientific-notation:51:48:None:None::Scientific notation should be '2e1' instead:HIGH +use-standard-scientific-notation:55:35:None:None::Scientific notation should be '1e1' instead:HIGH +use-standard-scientific-notation:55:27:None:None::Scientific notation should be '2e1' instead:HIGH +use-standard-scientific-notation:59:29:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH +use-standard-scientific-notation:60:33:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH +esoteric-underscore-grouping:65:34:None:None::Non standard grouping of numeric literals using underscores should be .123456:HIGH +esoteric-underscore-grouping:66:35:None:None::Non standard grouping of numeric literals using underscores should be 123_456.123456:HIGH +esoteric-underscore-grouping:67:27:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH +esoteric-underscore-grouping:68:26:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH +esoteric-underscore-grouping:69:31:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH +esoteric-underscore-grouping:70:27:None:None::Non standard grouping of numeric literals using underscores should be 1_234.56789:HIGH +esoteric-underscore-grouping:71:28:None:None::Non standard grouping of numeric literals using underscores should be 0_b10_101_010:HIGH +esoteric-underscore-grouping:73:23:None:None::Non standard grouping of numeric literals using underscores should be 4.5345e7:HIGH +use-standard-scientific-notation:73:23:None:None::Scientific notation should be '4.5345e1' instead:HIGH +esoteric-underscore-grouping:75:25:None:None::Non standard grouping of numeric literals using underscores should be 1.2e-30:HIGH +use-standard-scientific-notation:75:25:None:None::Scientific notation should be '1.2e-4' instead:HIGH +esoteric-underscore-grouping:77:37:None:None::Non standard grouping of numeric literals using underscores should be 1.2e9:HIGH +use-standard-scientific-notation:77:37:None:None::Scientific notation should be '1.2e1' instead:HIGH +esoteric-underscore-grouping:79:37:None:None::Non standard grouping of numeric literals using underscores should be 1.23e5:HIGH +use-standard-scientific-notation:79:37:None:None::Scientific notation should be '1.23e2' instead:HIGH +esoteric-underscore-grouping:81:25:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH +use-standard-scientific-notation:81:25:None:None::Scientific notation should be '1.23456789e3' instead:HIGH +esoteric-underscore-grouping:82:29:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH +esoteric-underscore-grouping:84:34:None:None::Non standard grouping of numeric literals using underscores should be 1.234e57:HIGH +use-standard-scientific-notation:84:34:None:None::Scientific notation should be '1.234e1' instead:HIGH +esoteric-underscore-grouping:86:24:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH +use-standard-scientific-notation:86:24:None:None::Scientific notation should be '1.23456789e3' instead:HIGH +esoteric-underscore-grouping:87:20:None:None::Non standard grouping of numeric literals using underscores should be 1e7:HIGH +use-standard-scientific-notation:87:20:None:None::Scientific notation should be '1e1' instead:HIGH +use-standard-scientific-notation:88:21:None:None::Scientific notation should be '1e6' instead:HIGH +esoteric-underscore-grouping:90:21:None:None::Non standard grouping of numeric literals using underscores should be 1e-3:HIGH +use-standard-scientific-notation:90:21:None:None::Scientific notation should be '1e-6' instead:HIGH +esoteric-underscore-grouping:91:21:None:None::Non standard grouping of numeric literals using underscores should be 1e3:HIGH +use-standard-scientific-notation:91:21:None:None::Scientific notation should be '1e1' instead:HIGH +esoteric-underscore-grouping:94:21:None:None::Non standard grouping of numeric literals using underscores should be 1.56e3:HIGH +esoteric-underscore-grouping:96:27:None:None::Non standard grouping of numeric literals using underscores should be 1.56e4:HIGH +use-standard-scientific-notation:96:27:None:None::Scientific notation should be '1.56e2' instead:HIGH +esoteric-underscore-grouping:100:35:None:None::Non standard grouping of numeric literals using underscores should be 1e4:HIGH +esoteric-underscore-grouping:100:57:None:None::Non standard grouping of numeric literals using underscores should be 2e4:HIGH +use-standard-scientific-notation:100:35:None:None::Scientific notation should be '1e1' instead:HIGH +use-standard-scientific-notation:100:57:None:None::Scientific notation should be '2e1' instead:HIGH From 3bd31760a2dbfa52ffe1c4707148a3f0c8d593e6 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 13 Jun 2025 21:34:57 +0200 Subject: [PATCH 03/15] Some design and add doc to give an idea of the design before actual implementation --- .../messages/b/bad-float-notation/bad/bad.py | 1 + .../bad/engineering_notation.py | 4 + .../bad/engineering_notation.rc | 2 + .../b/bad-float-notation/bad/pep515.py | 3 + .../b/bad-float-notation/bad/pep515.rc | 2 + .../bad/scientific_notation.py | 3 + .../bad/scientific_notation.rc | 2 + .../messages/b/bad-float-notation/details.rst | 6 + .../good/engineering_notation.py | 3 + .../b/bad-float-notation/good/good.py | 3 + .../b/bad-float-notation/good/pep515.py | 3 + .../good/scientific_notation.py | 3 + .../messages/b/bad-float-notation/related.rst | 1 + doc/test_messages_documentation.py | 4 +- doc/user_guide/checkers/features.rst | 4 + doc/user_guide/configuration/all-options.rst | 36 ++ doc/user_guide/messages/messages_overview.rst | 1 + doc/whatsnew/fragments/10425.feature | 3 + pylint/checkers/format.py | 328 ++++++++++++++---- tests/checkers/unittest_format.py | 61 +++- .../bad_float_engineering_notation.py | 9 + .../bad_float_engineering_notation.rc | 4 + .../b/bad_float/bad_float_notation_default.py | 133 +++++++ .../b/bad_float/bad_float_notation_default.rc | 4 + .../bad_float/bad_float_notation_default.txt | 55 +++ .../b/bad_float/bad_float_pep515.py | 8 + .../b/bad_float/bad_float_pep515.rc | 4 + .../bad_float_scientific_notation.py | 8 + .../bad_float_scientific_notation.rc | 4 + .../u/use/use_standard_scientific_notation.py | 101 ------ .../use/use_standard_scientific_notation.txt | 55 --- 31 files changed, 622 insertions(+), 236 deletions(-) create mode 100644 doc/data/messages/b/bad-float-notation/bad/bad.py create mode 100644 doc/data/messages/b/bad-float-notation/bad/engineering_notation.py create mode 100644 doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc create mode 100644 doc/data/messages/b/bad-float-notation/bad/pep515.py create mode 100644 doc/data/messages/b/bad-float-notation/bad/pep515.rc create mode 100644 doc/data/messages/b/bad-float-notation/bad/scientific_notation.py create mode 100644 doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc create mode 100644 doc/data/messages/b/bad-float-notation/details.rst create mode 100644 doc/data/messages/b/bad-float-notation/good/engineering_notation.py create mode 100644 doc/data/messages/b/bad-float-notation/good/good.py create mode 100644 doc/data/messages/b/bad-float-notation/good/pep515.py create mode 100644 doc/data/messages/b/bad-float-notation/good/scientific_notation.py create mode 100644 doc/data/messages/b/bad-float-notation/related.rst create mode 100644 doc/whatsnew/fragments/10425.feature create mode 100644 tests/functional/b/bad_float/bad_float_engineering_notation.py create mode 100644 tests/functional/b/bad_float/bad_float_engineering_notation.rc create mode 100644 tests/functional/b/bad_float/bad_float_notation_default.py create mode 100644 tests/functional/b/bad_float/bad_float_notation_default.rc create mode 100644 tests/functional/b/bad_float/bad_float_notation_default.txt create mode 100644 tests/functional/b/bad_float/bad_float_pep515.py create mode 100644 tests/functional/b/bad_float/bad_float_pep515.rc create mode 100644 tests/functional/b/bad_float/bad_float_scientific_notation.py create mode 100644 tests/functional/b/bad_float/bad_float_scientific_notation.rc delete mode 100644 tests/functional/u/use/use_standard_scientific_notation.py delete mode 100644 tests/functional/u/use/use_standard_scientific_notation.txt diff --git a/doc/data/messages/b/bad-float-notation/bad/bad.py b/doc/data/messages/b/bad-float-notation/bad/bad.py new file mode 100644 index 0000000000..dec6d44520 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/bad.py @@ -0,0 +1 @@ +mindless_anarchy = 1504e5 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/engineering_notation.py b/doc/data/messages/b/bad-float-notation/bad/engineering_notation.py new file mode 100644 index 0000000000..5c6506a9ea --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/engineering_notation.py @@ -0,0 +1,4 @@ +exponent_not_multiple_of_three = 123e4 # [bad-float-notation] +base_not_between_one_and_a_thousand = 12345e6 # [bad-float-notation] +above_threshold_without_exponent = 10000000 # [bad-float-notation] +under_a_thousand_with_exponent = 9.9e2 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc b/doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc new file mode 100644 index 0000000000..c7f36b28ce --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc @@ -0,0 +1,2 @@ +[format] +strict-engineering-notation = true diff --git a/doc/data/messages/b/bad-float-notation/bad/pep515.py b/doc/data/messages/b/bad-float-notation/bad/pep515.py new file mode 100644 index 0000000000..ae2d485b13 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/pep515.py @@ -0,0 +1,3 @@ +not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] +mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] +above_threshold_without_grouping = 123456789 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/pep515.rc b/doc/data/messages/b/bad-float-notation/bad/pep515.rc new file mode 100644 index 0000000000..a76dc794c2 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/pep515.rc @@ -0,0 +1,2 @@ +[main] +strict-underscore-notation = true diff --git a/doc/data/messages/b/bad-float-notation/bad/scientific_notation.py b/doc/data/messages/b/bad-float-notation/bad/scientific_notation.py new file mode 100644 index 0000000000..146d7f0c5a --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/scientific_notation.py @@ -0,0 +1,3 @@ +base_not_between_one_and_ten = 10e3 # [bad-float-notation] +above_threshold_without_exponent = 10000000 # [bad-float-notation] +under_ten_with_exponent = 9.9e0 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc b/doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc new file mode 100644 index 0000000000..ba8b10546f --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc @@ -0,0 +1,2 @@ +[format] +strict-scientific-notation = true diff --git a/doc/data/messages/b/bad-float-notation/details.rst b/doc/data/messages/b/bad-float-notation/details.rst new file mode 100644 index 0000000000..df04c6cb27 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/details.rst @@ -0,0 +1,6 @@ +There's 4 options associated with this message: +- ``strict-engineering-notation`` +- ``strict-scientific-notation`` +- ``strict-underscore-notation`` +- ``float-notation-threshold`` +By default we allow all three standard and the threshold is 10e6. diff --git a/doc/data/messages/b/bad-float-notation/good/engineering_notation.py b/doc/data/messages/b/bad-float-notation/good/engineering_notation.py new file mode 100644 index 0000000000..df1671a01c --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/good/engineering_notation.py @@ -0,0 +1,3 @@ +exponent_multiple_of_three = 1.23e6 +base_between_one_and_a_thousand = 12.345e9 +under_a_thousand = 990 diff --git a/doc/data/messages/b/bad-float-notation/good/good.py b/doc/data/messages/b/bad-float-notation/good/good.py new file mode 100644 index 0000000000..a786589f03 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/good/good.py @@ -0,0 +1,3 @@ +scientific_notation = 1.504e8 +engineering_notation = 150.4e6 +underscore_notation = 150_400_000 diff --git a/doc/data/messages/b/bad-float-notation/good/pep515.py b/doc/data/messages/b/bad-float-notation/good/pep515.py new file mode 100644 index 0000000000..47c96c4b48 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/good/pep515.py @@ -0,0 +1,3 @@ +proper_grouping = 123_456_789 +scientific_notation = 1.2345678e16 +engineering_notation = 12.345678e15 diff --git a/doc/data/messages/b/bad-float-notation/good/scientific_notation.py b/doc/data/messages/b/bad-float-notation/good/scientific_notation.py new file mode 100644 index 0000000000..6202a4550b --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/good/scientific_notation.py @@ -0,0 +1,3 @@ +base_between_one_and_ten = 1e4 +above_threshold_with_exponent = 1e7 +under_ten = 9.9 diff --git a/doc/data/messages/b/bad-float-notation/related.rst b/doc/data/messages/b/bad-float-notation/related.rst new file mode 100644 index 0000000000..c44ff6e84f --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/related.rst @@ -0,0 +1 @@ +- `PEP 515 `_ diff --git a/doc/test_messages_documentation.py b/doc/test_messages_documentation.py index 3af1697700..c69f89ab4a 100644 --- a/doc/test_messages_documentation.py +++ b/doc/test_messages_documentation.py @@ -173,7 +173,9 @@ def _runTest(self) -> None: assert len(actual_messages_raw) >= len(bad_files), self.assert_message_bad( bad_files, actual_messages_raw ) - assert expected_messages == self._get_actual(actual_messages_raw) + actual = self._get_actual(actual_messages_raw) + assert_msg = f"Expected {expected_messages!r} and got {actual!r} in {self._test_file[1]}." + assert expected_messages == actual, assert_msg def assert_message_good(self, messages: list[Message]) -> str: good = self._test_file[1] diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 670b7d67ec..576aec51f0 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -557,6 +557,10 @@ Format checker Messages Used when there is different newline than expected. :superfluous-parens (C0325): *Unnecessary parens after %r keyword* Used when a single item in parentheses follows an if, for, or other keyword. +:bad-float-notation (C0329): *float literal should be written as '%s' instead* + Emitted when a number is written in a non-standard notation. The three + allowed notation above the threshold are the scientific notation, the + engineering notation, and the underscore grouping notation defined in PEP515. Imports checker diff --git a/doc/user_guide/configuration/all-options.rst b/doc/user_guide/configuration/all-options.rst index 7c1093024a..2d6c1f79cc 100644 --- a/doc/user_guide/configuration/all-options.rst +++ b/doc/user_guide/configuration/all-options.rst @@ -879,6 +879,13 @@ Standard Checkers **Default:** ``""`` +--float-notation-threshold +"""""""""""""""""""""""""" +*Threshold for float literals to be expected to be written using the scientific, engineering or underscore notation. If the absolute value of a float literal is greater than this value (or smaller than the inverse of this value for scientific and engineering notation), it will be checked.* + +**Default:** ``1e6`` + + --ignore-long-lines """"""""""""""""""" *Regexp for a line that is allowed to be longer than the limit.* @@ -928,6 +935,27 @@ Standard Checkers **Default:** ``False`` +--strict-engineering-notation +""""""""""""""""""""""""""""" +*Only allow engineering notation for float literals with absolute value bigger than 'float-notation-threshold' or smallerthan the inverse of 'float-notation-threshold'.* + +**Default:** ``False`` + + +--strict-scientific-notation +"""""""""""""""""""""""""""" +*Only allow scientific notation for float literals with absolute value bigger than 'float-notation-threshold' or smallerthan the inverse of 'float-notation-threshold'.* + +**Default:** ``False`` + + +--strict-underscore-notation +"""""""""""""""""""""""""""" +*Only allow underscore notation for float literals bigger than 'float-notation-threshold'.* + +**Default:** ``False`` + + .. raw:: html @@ -942,6 +970,8 @@ Standard Checkers # Possible choices: ['', 'LF', 'CRLF'] expected-line-ending-format = "" + float-notation-threshold = 1000000.0 + ignore-long-lines = "^\\s*(# )??$" indent-after-paren = 4 @@ -956,6 +986,12 @@ Standard Checkers single-line-if-stmt = false + strict-engineering-notation = false + + strict-scientific-notation = false + + strict-underscore-notation = false + .. raw:: html diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index fc487fc25c..f7cfede0a0 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -399,6 +399,7 @@ All messages in the convention category: convention/bad-classmethod-argument convention/bad-docstring-quotes convention/bad-file-encoding + convention/bad-float-notation convention/bad-mcs-classmethod-argument convention/bad-mcs-method-argument convention/consider-iterating-dictionary diff --git a/doc/whatsnew/fragments/10425.feature b/doc/whatsnew/fragments/10425.feature new file mode 100644 index 0000000000..7f50f890f4 --- /dev/null +++ b/doc/whatsnew/fragments/10425.feature @@ -0,0 +1,3 @@ +Added a check for misleading scientific notations and use of underscore grouping in `float` literals. + +Refs #10425 diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index fe52419358..2c90be414c 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -13,6 +13,7 @@ from __future__ import annotations +import math import re import tokenize from functools import reduce @@ -113,14 +114,11 @@ "Used when there is different newline than expected.", ), "C0329": ( - "Scientific notation should be '%s' instead", - "use-standard-scientific-notation", - "Emitted when a number is written in non-standard scientific notation.", - ), - "C0331": ( - "Non standard grouping of numeric literals using underscores should be %s", - "esoteric-underscore-grouping", - "Used when numeric literals use underscore separators not in groups of 3 digits.", + "float literal should be written as '%s' instead", + "bad-float-notation", + "Emitted when a number is written in a non-standard notation. The three " + "allowed notation above the threshold are the scientific notation, the " + "engineering notation, and the underscore grouping notation defined in PEP515.", ), } @@ -258,12 +256,91 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker): ), }, ), + ( + "float-notation-threshold", + { + # default big enough to not trigger on pixel perfect web design + # on big screen + "default": "1e6", + "type": "float", + "metavar": "", + "help": ( + "Threshold for float literals to be expected to be written " + "using the scientific, engineering or underscore notation." + " If the absolute value of a float literal is greater than this " + "value (or smaller than the inverse of this value for scientific " + "and engineering notation), it will be checked." + ), + }, + ), + ( + "strict-engineering-notation", + { + "default": False, + "type": "yn", + "metavar": "", + "help": "Only allow engineering notation for float literals with " + "absolute value bigger than 'float-notation-threshold' or smaller" + "than the inverse of 'float-notation-threshold'.", + }, + ), + ( + "strict-scientific-notation", + { + "default": False, + "type": "yn", + "metavar": "", + "help": "Only allow scientific notation for float literals with " + "absolute value bigger than 'float-notation-threshold' or smaller" + "than the inverse of 'float-notation-threshold'.", + }, + ), + ( + "strict-underscore-notation", + { + "default": False, + "type": "yn", + "metavar": "", + "help": "Only allow underscore notation for float literals bigger than " + "'float-notation-threshold'.", + }, + ), ) def __init__(self, linter: PyLinter) -> None: super().__init__(linter) self._lines: dict[int, str] = {} self._visited_lines: dict[int, Literal[1, 2]] = {} + scientific = self.linter.config.strict_scientific_notation + engineering = self.linter.config.strict_engineering_notation + underscore = self.linter.config.strict_underscore_notation + float_config = sum([scientific, engineering, underscore]) + print( + f"scientific: {scientific}, " + f"engineering: {engineering}," + f" underscore; {underscore}" + f" float_config; {float_config}" + ) + if float_config > 1: + raise ValueError( + "Only one of strict-scientific-notation, " + "strict-engineering-notation, or strict-underscore-notation " + "can be set to True at a time." + ) + if float_config == 0: + # i.e. nothing is strict so we should check all + self.should_check_scientific_notation = True + self.should_check_engineering_notation = True + self.should_check_underscore_notation = True + else: + self.should_check_scientific_notation = scientific + self.should_check_engineering_notation = engineering + self.should_check_underscore_notation = underscore + print( + self.should_check_scientific_notation, + self.should_check_engineering_notation, + self.should_check_underscore_notation, + ) def new_line(self, tokens: TokenWrapper, line_end: int, line_start: int) -> None: """A new line has been encountered, process it if necessary.""" @@ -383,42 +460,6 @@ def _check_keyword_parentheses( self._check_keyword_parentheses(tokens[i:], 0) return - @staticmethod - def to_standard_scientific_notation(number: float) -> str: - # number is always going to be > 0 because node constants are always positive - # Format with high precision to capture all digits - s = f"{number:.15e}" - base, exp = s.split("e") - # Remove trailing zeros and possible trailing decimal point - base = base.rstrip("0").rstrip(".") - return f"{base}e{int(exp)}" - - @staticmethod - def to_standard_underscore_grouping(number: str) -> str: - if "e" in number.lower() or "E" in number.lower(): - return FormatChecker.to_standard_scientific_notation(float(number)) - - number = number.replace("_", "") - # Split into whole and decimal parts (if present) - if "." in number: - whole, decimal = number.split(".") - else: - whole, decimal = number, "" - - # Format whole part with proper grouping (right to left) - if len(whole) > 3: - grouped_whole = "" - for i in range(len(whole), 0, -3): - start = max(0, i - 3) - group = whole[start:i] - if grouped_whole: - grouped_whole = group + "_" + grouped_whole - else: - grouped_whole = group - whole = grouped_whole - return f"{whole}.{decimal}" if decimal else whole - - # pylint: disable-next=too-many-branches,too-many-statements def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: """Process tokens and search for: @@ -478,37 +519,17 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: check_equal = False self.check_indent_level(line, indents[-1], line_num) if tok_type == tokenize.NUMBER: - not_hex_oct_or_complex = ( - # You don't deserve a linter if you mix non-decimal notation with - # and exponential or underscore, + if ( + self.linter.is_message_enabled("bad-float-notation") + and + # You don't deserve a linter if you mix non-decimal notation and + # exponential or underscore, "x" not in string # not a hexadecimal and "o" not in string # not an octal and "j" not in string # not a complex - ) - # Wrong scientific notation - if ("e" in string or "E" in string) and not_hex_oct_or_complex: - value = float(string.lower().split("e")[0]) - if not (1 <= value < 10): - self.add_message( - "use-standard-scientific-notation", - args=(self.to_standard_scientific_notation(value)), - line=line_num, - col_offset=start[1], - confidence=HIGH, - ) - # proper underscore grouping in numeric literals - if "_" in string and not_hex_oct_or_complex: - if not re.match( - r"^\d{0,3}(_\d{3})*\.?\d*([eE]-?\d{0,3}(_\d{3})*)?$", string - ): - suggested = self.to_standard_underscore_grouping(string) - self.add_message( - "esoteric-underscore-grouping", - args=(suggested), - line=line_num, - col_offset=start[1], - confidence=HIGH, - ) + and "b" not in string # not a binary + ): + self._check_bad_float_notation(line_num, start, string) if string.endswith("l"): self.add_message("lowercase-l-suffix", line=line_num) @@ -538,6 +559,169 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: if line_num == last_blank_line_num and line_num > 0: self.add_message("trailing-newlines", line=line_num) + @classmethod + def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]: + """Calculate scientific notation components (base, exponent) for a number. + + Returns a tuple (base, exponent) where: + - base is a number between 1 and 10 (or exact 0) + - exponent is the power of 10 needed to represent the original number + """ + if number == 0: + return "0", "0" + exponent = math.floor(math.log10(abs(number))) + if exponent == 0: + return str(number), "0" + base_value = number / (10**exponent) + # 15 significant digits because if we add more precision then + # we get into rounding errors territory + base_str = f"{base_value:.15g}".rstrip("0").rstrip(".") + exp_str = str(exponent) + return base_str, exp_str + + @classmethod + def to_standard_scientific_notation(cls, number: float) -> str: + base, exp = cls.to_standard_or_engineering_base(number) + if exp != "0": + return f"{base}e{int(exp)}" + if "." in base: + return base + return f"{base}.0" + + @classmethod + def to_standard_engineering_notation(cls, number: float) -> str: + base, exp = cls.to_standard_or_engineering_base(number) + exp_value = int(exp) + remainder = exp_value % 3 + # For negative exponents, the adjustment is different + if exp_value < 0: + # For negative exponents, we need to round down to the next multiple of 3 + # e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2 + adjustment = 3 - ((-exp_value) % 3) + if adjustment == 3: + adjustment = 0 + exp_value = exp_value - adjustment + base_value = float(base) * (10**adjustment) + elif remainder != 0: + # For positive exponents, keep the existing logic + exp_value = exp_value - remainder + base_value = float(base) * (10**remainder) + else: + base_value = float(base) + base = str(base_value).rstrip("0").rstrip(".") + if exp_value != 0: + return f"{base}e{exp_value}" + if "." in base: + return base + return f"{base}.0" + + @classmethod + def to_standard_underscore_grouping(cls, number: float) -> str: + number_str = str(number) + if "e" in number_str or "E" in number_str: + # python itself want to display this as exponential there's no reason to + # not use exponential notation for very small number even for strict + # underscore grouping notation + return number_str + if "." in number_str: + int_part, dec_part = number_str.split(".") + else: + int_part = number_str + dec_part = "0" + grouped_int_part = "" + for i, digit in enumerate(reversed(int_part)): + if i > 0 and i % 3 == 0: + grouped_int_part = "_" + grouped_int_part + grouped_int_part = digit + grouped_int_part + return f"{grouped_int_part}.{dec_part}" + + def _check_bad_float_notation( + self, line_num: int, start: tuple[int, int], string: str + ) -> None: + + def raise_bad_float_notation(suggested: set[str]) -> None: + return self.add_message( + "bad-float-notation", + args=("' or '".join(suggested)), + line=line_num, + col_offset=start[1], + confidence=HIGH, + ) + + value = float(string) + threshold = self.linter.config.float_notation_threshold + abs_value = abs(value) + has_underscore = "_" in string + has_exponent = "e" in string or "E" in string + print( + f"Checking {string} {line_num}: " + f"s:{self.should_check_scientific_notation} " + f"e:{self.should_check_engineering_notation} " + f"u;{self.should_check_underscore_notation} " + f"has_underscore: {has_underscore} " + f"has_exponent: {has_exponent} " + f"abs_value: {abs_value} " + f"threshold: {threshold} " + f"strict_scientific_notation: {self.linter.config.strict_scientific_notation} " + ) + if has_exponent: + if self.linter.config.strict_underscore_notation: + # If we have exponent it means it's not proper underscore + return raise_bad_float_notation( + {self.to_standard_underscore_grouping(value)} + ) + if abs_value > threshold or abs_value < 1 / threshold: + value_as_str, exponent_as_str = string.lower().split("e") + value = float(value_as_str) + wrong_scientific_notation = ( + self.should_check_scientific_notation and not (1 <= value < 10) + ) + if ( + self.linter.config.strict_scientific_notation + and wrong_scientific_notation + ): + return raise_bad_float_notation( + {self.to_standard_scientific_notation(value)} + ) + wrong_engineering_notation = ( + self.should_check_engineering_notation + and not (1 <= value < 1000 and int(exponent_as_str) % 3 == 0) + ) + if ( + self.linter.config.strict_engineering_notation + and wrong_engineering_notation + ): + return raise_bad_float_notation( + {self.to_standard_engineering_notation(value)} + ) + if wrong_scientific_notation and wrong_engineering_notation: + return raise_bad_float_notation( + { + self.to_standard_scientific_notation(value), + self.to_standard_engineering_notation(value), + self.to_standard_underscore_grouping(value), + } + ) + if has_underscore: + # If we have underscore and exponent, we suggest exponent by default + if self.linter.config.strict_scientific_notation: + return raise_bad_float_notation( + {self.to_standard_scientific_notation(value)} + ) + if self.linter.config.strict_engineering_notation: + return raise_bad_float_notation( + {self.to_standard_engineering_notation(value)} + ) + underscore_notation = has_underscore and re.match( + r"^\d{0,3}(_\d{3})*\.?\d*([eE]-?\d{0,3}(_\d{3})*)?$", string + ) + if not underscore_notation: + return raise_bad_float_notation( + {self.to_standard_underscore_grouping(value)} + ) + return None + return None + def _check_line_ending(self, line_ending: str, line_num: int) -> None: # check if line endings are mixed if self._last_line_ending is not None: diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index d9043cd400..502fc3c175 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -184,13 +184,60 @@ def test_disable_global_option_end_of_line() -> None: @pytest.mark.parametrize( - "value, expected", + "value,expected_scientific,expected_engineering,expected_underscore", [ - ("1_000_000", "1_000_000"), - ("1000_000", "1_000_000"), - ("10_5415_456_4654984.16354698489", "1_054_154_564_654_984.16354698489"), + ("0", "0.0", "0.0", "0.0"), + ("0e10", "0.0", "0.0", "0.0"), + ("0e-10", "0.0", "0.0", "0.0"), + ("0.0e10", "0.0", "0.0", "0.0"), + ("1e0", "1.0", "1.0", "1.0"), + ("1e10", "1e10", "10e9", "10_000_000_000.0"), + # no reason to not use exponential notation for very low number + # even for strict underscore grouping notation + ("1e-10", "1e-10", "100e-12", "1e-10"), + ("2e1", "2e1", "20.0", "20.0"), + ("2e-1", "2e-1", "200e-3", "0.2"), + ("3.456e2", "3.456e2", "345.6", "345.6"), + ("3.456e-2", "3.456e-2", "34.56e-3", "0.03456"), + ("4e2", "4e2", "400.0", "400.0"), + ("4e-2", "4e-2", "40e-3", "0.04"), + ("50e2", "5e3", "5e3", "5_000.0"), + ("50e-2", "5e-1", "500e-3", "0.5"), + ("6e6", "6e6", "6e6", "6_000_000.0"), + ("6e-6", "6e-6", "6e-6", "6e-06"), # 6e-06 is what python offer on str(float) + ("10e5", "1e6", "1e6", "1_000_000.0"), + ("10e-5", "1e-4", "100e-6", "0.0001"), + ("1_000_000", "1e6", "1e6", "1_000_000.0"), + ("1000_000", "1e6", "1e6", "1_000_000.0"), + ("20e9", "2e10", "20e9", "20_000_000_000.0"), + ("20e-9", "2e-8", "20e-9", "2e-08"), # 2e-08 is what python offer on str(float) + ( + # 15 significant digits because we get rounding error otherwise + # and 15 seems enough especially since we don't autofix + "10_5415_456_465498.16354698489", + "1.05415456465498e14", + "105.415456465498e12", + "105_415_456_465_498.16", + ), ], ) -def test_to_standard_underscore_grouping(value: str, expected: str) -> None: - """Test the conversion of numbers to standard underscore grouping.""" - assert FormatChecker.to_standard_underscore_grouping(value) == expected +def test_to_another_standard_notation( + value: str, + expected_scientific: str, + expected_engineering: str, + expected_underscore: str, +) -> None: + """Test the conversion of numbers to all possible notations.""" + float_value = float(value) + scientific = FormatChecker.to_standard_scientific_notation(float_value) + assert ( + scientific == expected_scientific + ), f"Scientific notation mismatch expected {expected_scientific}, got {scientific}" + engineering = FormatChecker.to_standard_engineering_notation(float_value) + assert ( + engineering == expected_engineering + ), f"Engineering notation mismatch expected {expected_engineering}, got {engineering}" + underscore = FormatChecker.to_standard_underscore_grouping(float_value) + assert ( + underscore == expected_underscore + ), f"Underscore grouping mismatch expected {expected_underscore}, got {underscore}" diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.py b/tests/functional/b/bad_float/bad_float_engineering_notation.py new file mode 100644 index 0000000000..0ed7d9b8b1 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.py @@ -0,0 +1,9 @@ +# pylint: disable=missing-docstring,invalid-name + +exponent_not_multiple_of_three = 123e4 # [bad-float-notation] +base_not_between_one_and_a_thousand = 12345e6 # [bad-float-notation] +above_threshold_without_exponent = 10000000 # [bad-float-notation] +under_a_thousand_with_exponent = 9.9e2 # [bad-float-notation] +exponent_multiple_of_three = 1.23e6 +base_between_one_and_a_thousand = 12.345e9 +under_a_thousand = 990 diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.rc b/tests/functional/b/bad_float/bad_float_engineering_notation.rc new file mode 100644 index 0000000000..0aeb017a55 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.rc @@ -0,0 +1,4 @@ +[main] +strict-engineering-notation = true +strict-scientific-notation = false +strict-underscore-notation = false diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py new file mode 100644 index 0000000000..97abfdd6be --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -0,0 +1,133 @@ +# pylint: disable=missing-docstring,invalid-name + +# Content of bad/good.py +mindless_anarchy = 1504e5 # [bad-float-notation] +scientific_notation = 1.504e8 +engineering_notation = 150.4e6 +underscore_notation = 150_400_000 + +# Content of pep515 strict tests tested with default configuration +not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] +mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] +above_threshold_without_grouping = 123456789 # [bad-float-notation] +proper_grouping = 123_456_789 +scientific_notation_2 = 1.2345678e16 +engineering_notation_2 = 12.345678e15 + +# Content of bad_float_engineering_notation.py strict tests tested with default configuration +exponent_not_multiple_of_three = 123e4 # [bad-float-notation] +base_not_between_one_and_a_thousand = 12345e6 # [bad-float-notation] +above_threshold_without_exponent = 10000000 # [bad-float-notation] +under_a_thousand_with_exponent = 9.9e2 # [bad-float-notation] +exponent_multiple_of_three = 1.23e6 +base_between_one_and_a_thousand = 12.345e9 +under_a_thousand = 990 + +# Content of bad_float_scientific_notation strict tests tested with default configuration +base_not_between_one_and_ten = 10e3 # [bad-float-notation] +above_threshold_without_exponent_2 = 10000000 # [bad-float-notation] +under_ten_with_exponent = 9.9e0 # [bad-float-notation] +base_between_one_and_ten = 1e4 +above_threshold_with_exponent = 1e7 +under_ten = 9.9 + + +wrong_big = 45.3e6 # [bad-float-notation] +uppercase_e_wrong = 45.3E6 # [bad-float-notation] +wrong_small = 0.00012e-26 # [bad-float-notation] +uppercase_e_wrong_small = 0.00012E-26 # [bad-float-notation] +wrong_negative_and_big = -10e3 # [bad-float-notation] +actual_trolling = 11000e26 # [bad-float-notation] +scientific_double_digit = 12e8 # [bad-float-notation] +scientific_triple_digit = 123e3 # [bad-float-notation] +zero_before_decimal_small = 0.0001e-5 # [bad-float-notation] +zero_before_decimal_big = 0.0001e5 # [bad-float-notation] +negative_decimal = -0.5e10 # [bad-float-notation] +zero_only = 0e10 # [bad-float-notation] + +one_only = 1e6 +correct_1 = 4.53e7 +uppercase_e_correct = 4.53E7 +uppercase_e_with_plus = 1.2E+10 +uppercase_e_with_minus = 5.67E-8 +correct_2 = 1.2e-28 +correct_3 = -1.0e4 +correct_4 = 1.1E30 +correct_with_digits = 4.567e8 +correct_with_plus = 1.2e+10 +correct_decimal_only = 3.14 +negative_correct = -5.67e-8 +correct_small_exponent = 1.5e1 +actually_nine = 9e0 +actually_one = 1.0e0 + + +hex_constant = 0x1e4 # Hexadecimal, not scientific notation +hex_constant_bad = 0x10e4 +binary_constant = 0b1010 +octal_constant = 0o1234 +inside_string = "Temperature: 10e3 degrees" +inside_multiline = """ +This is a test with 45.3e6 inside +""" +inside_comment = 1.0 # This comment has 12e4 in it +in_variable_name = measurement_10e3 = 45 +inside_f_string = f"Value is {1.0} not 10e6" + +complex_number = 1.5e3 + 2.5e3j # Complex number with scientific notation +# false negative for complex numbers: +complex_number_wrong = 15e3 + 25e3j # [bad-float-notation] + + +#+1: [bad-float-notation, bad-float-notation] +def function_with_sci(param=10.0e3, other_param=20.0e3): + return param, other_param + +#+1: [bad-float-notation, bad-float-notation] +result = function_with_sci(20.0e3, 10.0e3) + +valid_underscore_int = 1_000_000 +valid_underscore_float = 1_000_000.12345 +valid_underscore_float_exp = 123_000_000.12345e12_000_000 # [bad-float-notation] +valid_underscore_float_exp_cap = 123_000_000.12345E123_000_000 # [bad-float-notation] + +invalid_underscore_octal = 0o123_456 # octal with underscores bypassed +invalid_underscore_hexa = 0x12c_456 # hexa with underscores bypassed + +invalid_underscore_float_no_int = .123_456 # [bad-float-notation] +invalid_underscore_float_no_frac = 123_456.123_456 # [bad-float-notation] +incorrect_sci_underscore = 1.234_567e6 # [bad-float-notation] +incorrect_sci_uppercase = 1.234_567E6 # [bad-float-notation] +incorrect_sci_underscore_exp = 1.2e1_0 # [bad-float-notation] +invalid_underscore_float = 1_234.567_89 # [bad-float-notation] +invalid_underscore_binary = 0b1010_1010 # [bad-float-notation] +#+1: [bad-float-notation, bad-float-notation] +wrong_big_underscore = 45.3_45e6 +#+1: [bad-float-notation, bad-float-notation] +wrong_small_underscore = 0.000_12e-26 +#+1: [bad-float-notation, bad-float-notation] +scientific_double_digit_underscore = 1_2e8 +#+1: [bad-float-notation, bad-float-notation] +scientific_triple_digit_underscore = 12_3e3 +#+1: [bad-float-notation, bad-float-notation] +invalid_underscore_sci = 1_234.567_89e10 +invalid_underscore_sci_exp = 1.2e1_0 # [bad-float-notation] +#+1: [bad-float-notation, bad-float-notation] +invalid_underscore_sci_combined = 1_2.3_4e5_6 +#+1: [bad-float-notation, bad-float-notation] +invalid_uppercase_sci = 1_234.567_89E10 +edge_underscore_1 = 1_0e6 # [bad-float-notation, bad-float-notation] +mixed_underscore_1 = 1_000_000.0e-3 # [bad-float-notation] +#+1: [bad-float-notation, bad-float-notation] +mixed_underscore_2 = 0.000_001e3 +mixed_underscore_3 = 1_0.0e2 # [bad-float-notation, bad-float-notation] + +# Complex numbers with underscores +complex_underscore = 1.5_6e3 + 2.5_6e3j # [bad-float-notation] +#+1: [bad-float-notation, bad-float-notation] +complex_underscore_wrong = 15_6e2 + 25_6e2j + +#+2: [bad-float-notation, bad-float-notation] +#+1: [bad-float-notation, bad-float-notation] +def function_with_underscore(param=10.0_0e3, other_param=20.0_0e3): + return param, other_param diff --git a/tests/functional/b/bad_float/bad_float_notation_default.rc b/tests/functional/b/bad_float/bad_float_notation_default.rc new file mode 100644 index 0000000000..dc39dc8f42 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_notation_default.rc @@ -0,0 +1,4 @@ +[main] +strict-engineering-notation = false +strict-scientific-notation = false +strict-underscore-notation = false diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt new file mode 100644 index 0000000000..d63c74fc1f --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -0,0 +1,55 @@ +bad-float-notation:3:12:None:None::Scientific notation should be '4.53e1' instead:HIGH +bad-float-notation:4:20:None:None::Scientific notation should be '4.53e1' instead:HIGH +bad-float-notation:5:14:None:None::Scientific notation should be '1.2e-4' instead:HIGH +bad-float-notation:6:26:None:None::Scientific notation should be '1.2e-4' instead:HIGH +bad-float-notation:7:26:None:None::Scientific notation should be '1e1' instead:HIGH +bad-float-notation:8:18:None:None::Scientific notation should be '1.1e4' instead:HIGH +bad-float-notation:9:26:None:None::Scientific notation should be '1.2e1' instead:HIGH +bad-float-notation:10:26:None:None::Scientific notation should be '1.23e2' instead:HIGH +bad-float-notation:11:28:None:None::Scientific notation should be '1e-4' instead:HIGH +bad-float-notation:12:26:None:None::Scientific notation should be '1e-4' instead:HIGH +bad-float-notation:13:20:None:None::Scientific notation should be '5e-1' instead:HIGH +bad-float-notation:14:12:None:None::Scientific notation should be '0e0' instead:HIGH +bad-float-notation:47:23:None:None::Scientific notation should be '1.5e1' instead:HIGH +bad-float-notation:51:28:None:None::Scientific notation should be '1e1' instead:HIGH +bad-float-notation:51:48:None:None::Scientific notation should be '2e1' instead:HIGH +bad-float-notation:55:35:None:None::Scientific notation should be '1e1' instead:HIGH +bad-float-notation:55:27:None:None::Scientific notation should be '2e1' instead:HIGH +bad-float-notation:59:29:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH +bad-float-notation:60:33:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH +bad-float-notation:65:34:None:None::Non standard grouping of numeric literals using underscores should be .123456:HIGH +bad-float-notation:66:35:None:None::Non standard grouping of numeric literals using underscores should be 123_456.123456:HIGH +bad-float-notation:67:27:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH +bad-float-notation:68:26:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH +bad-float-notation:69:31:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH +bad-float-notation:70:27:None:None::Non standard grouping of numeric literals using underscores should be 1_234.56789:HIGH +bad-float-notation:71:28:None:None::Non standard grouping of numeric literals using underscores should be 0_b10_101_010:HIGH +bad-float-notation:73:23:None:None::Non standard grouping of numeric literals using underscores should be 4.5345e7:HIGH +bad-float-notation:73:23:None:None::Scientific notation should be '4.5345e1' instead:HIGH +bad-float-notation:75:25:None:None::Non standard grouping of numeric literals using underscores should be 1.2e-30:HIGH +bad-float-notation:75:25:None:None::Scientific notation should be '1.2e-4' instead:HIGH +bad-float-notation:77:37:None:None::Non standard grouping of numeric literals using underscores should be 1.2e9:HIGH +bad-float-notation:77:37:None:None::Scientific notation should be '1.2e1' instead:HIGH +bad-float-notation:79:37:None:None::Non standard grouping of numeric literals using underscores should be 1.23e5:HIGH +bad-float-notation:79:37:None:None::Scientific notation should be '1.23e2' instead:HIGH +bad-float-notation:81:25:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH +bad-float-notation:81:25:None:None::Scientific notation should be '1.23456789e3' instead:HIGH +bad-float-notation:82:29:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH +bad-float-notation:84:34:None:None::Non standard grouping of numeric literals using underscores should be 1.234e57:HIGH +bad-float-notation:84:34:None:None::Scientific notation should be '1.234e1' instead:HIGH +bad-float-notation:86:24:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH +bad-float-notation:86:24:None:None::Scientific notation should be '1.23456789e3' instead:HIGH +bad-float-notation:87:20:None:None::Non standard grouping of numeric literals using underscores should be 1e7:HIGH +bad-float-notation:87:20:None:None::Scientific notation should be '1e1' instead:HIGH +bad-float-notation:88:21:None:None::Scientific notation should be '1e6' instead:HIGH +bad-float-notation:90:21:None:None::Non standard grouping of numeric literals using underscores should be 1e-3:HIGH +bad-float-notation:90:21:None:None::Scientific notation should be '1e-6' instead:HIGH +bad-float-notation:91:21:None:None::Non standard grouping of numeric literals using underscores should be 1e3:HIGH +bad-float-notation:91:21:None:None::Scientific notation should be '1e1' instead:HIGH +bad-float-notation:94:21:None:None::Non standard grouping of numeric literals using underscores should be 1.56e3:HIGH +bad-float-notation:96:27:None:None::Non standard grouping of numeric literals using underscores should be 1.56e4:HIGH +bad-float-notation:96:27:None:None::Scientific notation should be '1.56e2' instead:HIGH +bad-float-notation:100:35:None:None::Non standard grouping of numeric literals using underscores should be 1e4:HIGH +bad-float-notation:100:57:None:None::Non standard grouping of numeric literals using underscores should be 2e4:HIGH +bad-float-notation:100:35:None:None::Scientific notation should be '1e1' instead:HIGH +bad-float-notation:100:57:None:None::Scientific notation should be '2e1' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_pep515.py b/tests/functional/b/bad_float/bad_float_pep515.py new file mode 100644 index 0000000000..28b10a0efd --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_pep515.py @@ -0,0 +1,8 @@ +# pylint: disable=missing-docstring,invalid-name + +not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] +mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] +above_threshold_without_grouping = 123456789 # [bad-float-notation] +proper_grouping = 123_456_789 +scientific_notation = 1.2345678e16 +engineering_notation = 12.345678e15 diff --git a/tests/functional/b/bad_float/bad_float_pep515.rc b/tests/functional/b/bad_float/bad_float_pep515.rc new file mode 100644 index 0000000000..7dd3df4483 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_pep515.rc @@ -0,0 +1,4 @@ +[main] +strict-engineering-notation = false +strict-scientific-notation = false +strict-underscore-notation = true diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.py b/tests/functional/b/bad_float/bad_float_scientific_notation.py new file mode 100644 index 0000000000..2ad34a3cd1 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.py @@ -0,0 +1,8 @@ +# pylint: disable=missing-docstring,invalid-name + +base_not_between_one_and_ten = 10e3 # [bad-float-notation] +above_threshold_without_exponent = 10000000 # [bad-float-notation] +under_ten_with_exponent = 9.9e0 # [bad-float-notation] +base_between_one_and_ten = 1e4 +above_threshold_with_exponent = 1e7 +under_ten = 9.9 diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.rc b/tests/functional/b/bad_float/bad_float_scientific_notation.rc new file mode 100644 index 0000000000..7620420d59 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.rc @@ -0,0 +1,4 @@ +[tool.pylint.format] +strict-engineering-notation = false +strict-scientific-notation = true +strict-underscore-notation = false diff --git a/tests/functional/u/use/use_standard_scientific_notation.py b/tests/functional/u/use/use_standard_scientific_notation.py deleted file mode 100644 index 727961f1d0..0000000000 --- a/tests/functional/u/use/use_standard_scientific_notation.py +++ /dev/null @@ -1,101 +0,0 @@ -# pylint: disable=missing-docstring,invalid-name - -wrong_big = 45.3e6 # [use-standard-scientific-notation] -uppercase_e_wrong = 45.3E6 # [use-standard-scientific-notation] -wrong_small = 0.00012e-26 # [use-standard-scientific-notation] -uppercase_e_wrong_small = 0.00012E-26 # [use-standard-scientific-notation] -wrong_negative_and_big = -10e3 # [use-standard-scientific-notation] -actual_trolling = 11000e26 # [use-standard-scientific-notation] -scientific_double_digit = 12e8 # [use-standard-scientific-notation] -scientific_triple_digit = 123e3 # [use-standard-scientific-notation] -zero_before_decimal_small = 0.0001e-5 # [use-standard-scientific-notation] -zero_before_decimal_big = 0.0001e5 # [use-standard-scientific-notation] -negative_decimal = -0.5e10 # [use-standard-scientific-notation] -zero_only = 0e10 # [use-standard-scientific-notation] - -one_only = 1e6 -correct_1 = 4.53e7 -uppercase_e_correct = 4.53E7 -uppercase_e_with_plus = 1.2E+10 -uppercase_e_with_minus = 5.67E-8 -correct_2 = 1.2e-28 -correct_3 = -1.0e4 -correct_4 = 1.1E30 -correct_with_digits = 4.567e8 -correct_with_plus = 1.2e+10 -correct_decimal_only = 3.14 -negative_correct = -5.67e-8 -correct_small_exponent = 1.5e1 -actually_nine = 9e0 -actually_one = 1.0e0 - - -hex_constant = 0x1e4 # Hexadecimal, not scientific notation -hex_constant_bad = 0x10e4 -binary_constant = 0b1010 -octal_constant = 0o1234 -inside_string = "Temperature: 10e3 degrees" -inside_multiline = """ -This is a test with 45.3e6 inside -""" -inside_comment = 1.0 # This comment has 12e4 in it -in_variable_name = measurement_10e3 = 45 -inside_f_string = f"Value is {1.0} not 10e6" - -complex_number = 1.5e3 + 2.5e3j # Complex number with scientific notation -# false negative for complex numbers: -complex_number_wrong = 15e3 + 25e3j # [use-standard-scientific-notation] - - -#+1: [use-standard-scientific-notation, use-standard-scientific-notation] -def function_with_sci(param=10.0e3, other_param=20.0e3): - return param, other_param - -#+1: [use-standard-scientific-notation, use-standard-scientific-notation] -result = function_with_sci(20.0e3, 10.0e3) - -valid_underscore_int = 1_000_000 -valid_underscore_float = 1_000_000.12345 -valid_underscore_float_exp = 123_000_000.12345e12_000_000 # [use-standard-scientific-notation] -valid_underscore_float_exp_cap = 123_000_000.12345E123_000_000 # [use-standard-scientific-notation] - -invalid_underscore_octal = 0o123_456 # octal with underscores bypassed -invalid_underscore_hexa = 0x12c_456 # hexa with underscores bypassed - -invalid_underscore_float_no_int = .123_456 # [esoteric-underscore-grouping] -invalid_underscore_float_no_frac = 123_456.123_456 # [esoteric-underscore-grouping] -incorrect_sci_underscore = 1.234_567e6 # [esoteric-underscore-grouping] -incorrect_sci_uppercase = 1.234_567E6 # [esoteric-underscore-grouping] -incorrect_sci_underscore_exp = 1.2e1_0 # [esoteric-underscore-grouping] -invalid_underscore_float = 1_234.567_89 # [esoteric-underscore-grouping] -invalid_underscore_binary = 0b1010_1010 # [esoteric-underscore-grouping] -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -wrong_big_underscore = 45.3_45e6 -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -wrong_small_underscore = 0.000_12e-26 -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -scientific_double_digit_underscore = 1_2e8 -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -scientific_triple_digit_underscore = 12_3e3 -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -invalid_underscore_sci = 1_234.567_89e10 -invalid_underscore_sci_exp = 1.2e1_0 # [esoteric-underscore-grouping] -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -invalid_underscore_sci_combined = 1_2.3_4e5_6 -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -invalid_uppercase_sci = 1_234.567_89E10 -edge_underscore_1 = 1_0e6 # [use-standard-scientific-notation, esoteric-underscore-grouping] -mixed_underscore_1 = 1_000_000.0e-3 # [use-standard-scientific-notation] -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -mixed_underscore_2 = 0.000_001e3 -mixed_underscore_3 = 1_0.0e2 # [use-standard-scientific-notation, esoteric-underscore-grouping] - -# Complex numbers with underscores -complex_underscore = 1.5_6e3 + 2.5_6e3j # [esoteric-underscore-grouping] -#+1: [use-standard-scientific-notation, esoteric-underscore-grouping] -complex_underscore_wrong = 15_6e2 + 25_6e2j - -#+2: [esoteric-underscore-grouping, esoteric-underscore-grouping] -#+1: [use-standard-scientific-notation, use-standard-scientific-notation] -def function_with_underscore(param=10.0_0e3, other_param=20.0_0e3): - return param, other_param diff --git a/tests/functional/u/use/use_standard_scientific_notation.txt b/tests/functional/u/use/use_standard_scientific_notation.txt deleted file mode 100644 index 4414ffeb8f..0000000000 --- a/tests/functional/u/use/use_standard_scientific_notation.txt +++ /dev/null @@ -1,55 +0,0 @@ -use-standard-scientific-notation:3:12:None:None::Scientific notation should be '4.53e1' instead:HIGH -use-standard-scientific-notation:4:20:None:None::Scientific notation should be '4.53e1' instead:HIGH -use-standard-scientific-notation:5:14:None:None::Scientific notation should be '1.2e-4' instead:HIGH -use-standard-scientific-notation:6:26:None:None::Scientific notation should be '1.2e-4' instead:HIGH -use-standard-scientific-notation:7:26:None:None::Scientific notation should be '1e1' instead:HIGH -use-standard-scientific-notation:8:18:None:None::Scientific notation should be '1.1e4' instead:HIGH -use-standard-scientific-notation:9:26:None:None::Scientific notation should be '1.2e1' instead:HIGH -use-standard-scientific-notation:10:26:None:None::Scientific notation should be '1.23e2' instead:HIGH -use-standard-scientific-notation:11:28:None:None::Scientific notation should be '1e-4' instead:HIGH -use-standard-scientific-notation:12:26:None:None::Scientific notation should be '1e-4' instead:HIGH -use-standard-scientific-notation:13:20:None:None::Scientific notation should be '5e-1' instead:HIGH -use-standard-scientific-notation:14:12:None:None::Scientific notation should be '0e0' instead:HIGH -use-standard-scientific-notation:47:23:None:None::Scientific notation should be '1.5e1' instead:HIGH -use-standard-scientific-notation:51:28:None:None::Scientific notation should be '1e1' instead:HIGH -use-standard-scientific-notation:51:48:None:None::Scientific notation should be '2e1' instead:HIGH -use-standard-scientific-notation:55:35:None:None::Scientific notation should be '1e1' instead:HIGH -use-standard-scientific-notation:55:27:None:None::Scientific notation should be '2e1' instead:HIGH -use-standard-scientific-notation:59:29:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH -use-standard-scientific-notation:60:33:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH -esoteric-underscore-grouping:65:34:None:None::Non standard grouping of numeric literals using underscores should be .123456:HIGH -esoteric-underscore-grouping:66:35:None:None::Non standard grouping of numeric literals using underscores should be 123_456.123456:HIGH -esoteric-underscore-grouping:67:27:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH -esoteric-underscore-grouping:68:26:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH -esoteric-underscore-grouping:69:31:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH -esoteric-underscore-grouping:70:27:None:None::Non standard grouping of numeric literals using underscores should be 1_234.56789:HIGH -esoteric-underscore-grouping:71:28:None:None::Non standard grouping of numeric literals using underscores should be 0_b10_101_010:HIGH -esoteric-underscore-grouping:73:23:None:None::Non standard grouping of numeric literals using underscores should be 4.5345e7:HIGH -use-standard-scientific-notation:73:23:None:None::Scientific notation should be '4.5345e1' instead:HIGH -esoteric-underscore-grouping:75:25:None:None::Non standard grouping of numeric literals using underscores should be 1.2e-30:HIGH -use-standard-scientific-notation:75:25:None:None::Scientific notation should be '1.2e-4' instead:HIGH -esoteric-underscore-grouping:77:37:None:None::Non standard grouping of numeric literals using underscores should be 1.2e9:HIGH -use-standard-scientific-notation:77:37:None:None::Scientific notation should be '1.2e1' instead:HIGH -esoteric-underscore-grouping:79:37:None:None::Non standard grouping of numeric literals using underscores should be 1.23e5:HIGH -use-standard-scientific-notation:79:37:None:None::Scientific notation should be '1.23e2' instead:HIGH -esoteric-underscore-grouping:81:25:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH -use-standard-scientific-notation:81:25:None:None::Scientific notation should be '1.23456789e3' instead:HIGH -esoteric-underscore-grouping:82:29:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH -esoteric-underscore-grouping:84:34:None:None::Non standard grouping of numeric literals using underscores should be 1.234e57:HIGH -use-standard-scientific-notation:84:34:None:None::Scientific notation should be '1.234e1' instead:HIGH -esoteric-underscore-grouping:86:24:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH -use-standard-scientific-notation:86:24:None:None::Scientific notation should be '1.23456789e3' instead:HIGH -esoteric-underscore-grouping:87:20:None:None::Non standard grouping of numeric literals using underscores should be 1e7:HIGH -use-standard-scientific-notation:87:20:None:None::Scientific notation should be '1e1' instead:HIGH -use-standard-scientific-notation:88:21:None:None::Scientific notation should be '1e6' instead:HIGH -esoteric-underscore-grouping:90:21:None:None::Non standard grouping of numeric literals using underscores should be 1e-3:HIGH -use-standard-scientific-notation:90:21:None:None::Scientific notation should be '1e-6' instead:HIGH -esoteric-underscore-grouping:91:21:None:None::Non standard grouping of numeric literals using underscores should be 1e3:HIGH -use-standard-scientific-notation:91:21:None:None::Scientific notation should be '1e1' instead:HIGH -esoteric-underscore-grouping:94:21:None:None::Non standard grouping of numeric literals using underscores should be 1.56e3:HIGH -esoteric-underscore-grouping:96:27:None:None::Non standard grouping of numeric literals using underscores should be 1.56e4:HIGH -use-standard-scientific-notation:96:27:None:None::Scientific notation should be '1.56e2' instead:HIGH -esoteric-underscore-grouping:100:35:None:None::Non standard grouping of numeric literals using underscores should be 1e4:HIGH -esoteric-underscore-grouping:100:57:None:None::Non standard grouping of numeric literals using underscores should be 2e4:HIGH -use-standard-scientific-notation:100:35:None:None::Scientific notation should be '1e1' instead:HIGH -use-standard-scientific-notation:100:57:None:None::Scientific notation should be '2e1' instead:HIGH From 10a91880b8ffe24726c0b697668ba6487ab647cf Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 19 Jun 2025 06:50:41 +0200 Subject: [PATCH 04/15] Fix bad initialization of class --- pylint/checkers/format.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 2c90be414c..7b4d2c8bea 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -307,20 +307,13 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker): ), ) - def __init__(self, linter: PyLinter) -> None: - super().__init__(linter) + def open(self) -> None: self._lines: dict[int, str] = {} self._visited_lines: dict[int, Literal[1, 2]] = {} scientific = self.linter.config.strict_scientific_notation engineering = self.linter.config.strict_engineering_notation underscore = self.linter.config.strict_underscore_notation float_config = sum([scientific, engineering, underscore]) - print( - f"scientific: {scientific}, " - f"engineering: {engineering}," - f" underscore; {underscore}" - f" float_config; {float_config}" - ) if float_config > 1: raise ValueError( "Only one of strict-scientific-notation, " @@ -336,11 +329,6 @@ def __init__(self, linter: PyLinter) -> None: self.should_check_scientific_notation = scientific self.should_check_engineering_notation = engineering self.should_check_underscore_notation = underscore - print( - self.should_check_scientific_notation, - self.should_check_engineering_notation, - self.should_check_underscore_notation, - ) def new_line(self, tokens: TokenWrapper, line_end: int, line_start: int) -> None: """A new line has been encountered, process it if necessary.""" From 9a577d2445e0a69e665f671094ff6f66038c8541 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 20 Jun 2025 23:54:05 +0200 Subject: [PATCH 05/15] Fix the strict notation functional tests --- .../b/bad-float-notation/good/pep515.py | 2 - pylint/checkers/format.py | 191 +++++++++++------- .../bad_float_engineering_notation.txt | 4 + .../b/bad_float/bad_float_notation_default.py | 25 +-- .../b/bad_float/bad_float_pep515.py | 5 +- .../b/bad_float/bad_float_pep515.txt | 5 + .../bad_float_scientific_notation.txt | 3 + 7 files changed, 142 insertions(+), 93 deletions(-) create mode 100644 tests/functional/b/bad_float/bad_float_engineering_notation.txt create mode 100644 tests/functional/b/bad_float/bad_float_pep515.txt create mode 100644 tests/functional/b/bad_float/bad_float_scientific_notation.txt diff --git a/doc/data/messages/b/bad-float-notation/good/pep515.py b/doc/data/messages/b/bad-float-notation/good/pep515.py index 47c96c4b48..df2b8469b0 100644 --- a/doc/data/messages/b/bad-float-notation/good/pep515.py +++ b/doc/data/messages/b/bad-float-notation/good/pep515.py @@ -1,3 +1 @@ proper_grouping = 123_456_789 -scientific_notation = 1.2345678e16 -engineering_notation = 12.345678e15 diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 7b4d2c8bea..46821e71e4 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -313,22 +313,29 @@ def open(self) -> None: scientific = self.linter.config.strict_scientific_notation engineering = self.linter.config.strict_engineering_notation underscore = self.linter.config.strict_underscore_notation - float_config = sum([scientific, engineering, underscore]) - if float_config > 1: + number_of_strict_float_notation = sum([scientific, engineering, underscore]) + if number_of_strict_float_notation > 1: raise ValueError( "Only one of strict-scientific-notation, " - "strict-engineering-notation, or strict-underscore-notation " + "'strict-engineering-notation', or 'strict-underscore-notation' " "can be set to True at a time." ) - if float_config == 0: - # i.e. nothing is strict so we should check all - self.should_check_scientific_notation = True - self.should_check_engineering_notation = True - self.should_check_underscore_notation = True - else: - self.should_check_scientific_notation = scientific - self.should_check_engineering_notation = engineering - self.should_check_underscore_notation = underscore + self.all_float_notation_allowed = number_of_strict_float_notation == 0 + if ( + self.linter.config.float_notation_threshold < 10 + and self.linter.config.strict_scientific_notation + ): + raise ValueError( + "'float-notation-threshold' must be at least 10 " + "when 'strict-scientific-notation' is enabled, got " + f"{self.linter.config.float_notation_threshold}." + ) + if self.linter.config.float_notation_threshold < 1000: + raise ValueError( + "'float-notation-threshold' must be at least 1000 " + f"when 'strict-scientific-notation' is disabled, got " + f"{self.linter.config.float_notation_threshold}." + ) def new_line(self, tokens: TokenWrapper, line_end: int, line_start: int) -> None: """A new line has been encountered, process it if necessary.""" @@ -557,6 +564,9 @@ def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]: """ if number == 0: return "0", "0" + if number == math.inf: + return "math.inf", "0" + print(f"Converting {number} to standard or engineering base") exponent = math.floor(math.log10(abs(number))) if exponent == 0: return str(number), "0" @@ -570,6 +580,8 @@ def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]: @classmethod def to_standard_scientific_notation(cls, number: float) -> str: base, exp = cls.to_standard_or_engineering_base(number) + if base == "math.inf": + return "math.inf" if exp != "0": return f"{base}e{int(exp)}" if "." in base: @@ -579,6 +591,8 @@ def to_standard_scientific_notation(cls, number: float) -> str: @classmethod def to_standard_engineering_notation(cls, number: float) -> str: base, exp = cls.to_standard_or_engineering_base(number) + if base == "math.inf": + return "math.inf" exp_value = int(exp) remainder = exp_value % 3 # For negative exponents, the adjustment is different @@ -623,91 +637,114 @@ def to_standard_underscore_grouping(cls, number: float) -> str: grouped_int_part = digit + grouped_int_part return f"{grouped_int_part}.{dec_part}" - def _check_bad_float_notation( + def _check_bad_float_notation( # pylint: disable=too-many-locals self, line_num: int, start: tuple[int, int], string: str ) -> None: + value = float(string) + engineering = ( + self.all_float_notation_allowed + or self.linter.config.strict_engineering_notation + ) + scientific = ( + self.all_float_notation_allowed + or self.linter.config.strict_scientific_notation + ) + pep515 = ( + self.all_float_notation_allowed + or self.linter.config.strict_underscore_notation + ) - def raise_bad_float_notation(suggested: set[str]) -> None: + def raise_bad_float_notation() -> None: + suggested = set() + if scientific: + suggested.add(self.to_standard_scientific_notation(value)) + if engineering: + suggested.add(self.to_standard_engineering_notation(value)) + if pep515: + suggested.add(self.to_standard_underscore_grouping(value)) + print("\tBad float notation, suggesting:", suggested) return self.add_message( "bad-float-notation", - args=("' or '".join(suggested)), + args=("' or '".join(sorted(suggested))), line=line_num, + end_lineno=line_num, col_offset=start[1], + end_col_offset=start[1] + len(string), confidence=HIGH, ) - value = float(string) - threshold = self.linter.config.float_notation_threshold - abs_value = abs(value) has_underscore = "_" in string has_exponent = "e" in string or "E" in string + should_be_written_simply = ( + 1 <= value < 10 and self.linter.config.strict_scientific_notation + ) or 1 <= value < 1000 + is_written_complexly = has_underscore or has_exponent + print(f"Checking {string} line {line_num}") + if should_be_written_simply and is_written_complexly: + print(f"\t{value} is a simple number") + # If the value does not deserve a complex notation then write it in a simple way. + # The threshold is guaranteed to be higher than those value. + # When 1 <= value < 10 the engineering notation is equivalent to the scientific notation + return raise_bad_float_notation() + + abs_value = abs(value) + should_not_be_checked_because_of_threshold = ( + abs_value < self.linter.config.float_notation_threshold # under threshold + and ( # use scientific or engineering notation and under 1/threshold + self.linter.config.strict_underscore_notation + or abs_value > 1 / self.linter.config.float_notation_threshold + ) + ) print( - f"Checking {string} {line_num}: " - f"s:{self.should_check_scientific_notation} " - f"e:{self.should_check_engineering_notation} " - f"u;{self.should_check_underscore_notation} " - f"has_underscore: {has_underscore} " - f"has_exponent: {has_exponent} " - f"abs_value: {abs_value} " - f"threshold: {threshold} " - f"strict_scientific_notation: {self.linter.config.strict_scientific_notation} " + "\tshould_not_be_checked_because_of_threshold=", + should_not_be_checked_because_of_threshold, ) + if not is_written_complexly: + if should_not_be_checked_because_of_threshold: + # This number is free style, we do not have to check it, unless it's + # written complexly, then it could be badly written + print("\tFree style number, no need to check") + return None + return raise_bad_float_notation() if has_exponent: if self.linter.config.strict_underscore_notation: # If we have exponent it means it's not proper underscore - return raise_bad_float_notation( - {self.to_standard_underscore_grouping(value)} - ) - if abs_value > threshold or abs_value < 1 / threshold: - value_as_str, exponent_as_str = string.lower().split("e") - value = float(value_as_str) - wrong_scientific_notation = ( - self.should_check_scientific_notation and not (1 <= value < 10) - ) - if ( - self.linter.config.strict_scientific_notation - and wrong_scientific_notation - ): - return raise_bad_float_notation( - {self.to_standard_scientific_notation(value)} - ) - wrong_engineering_notation = ( - self.should_check_engineering_notation - and not (1 <= value < 1000 and int(exponent_as_str) % 3 == 0) - ) - if ( - self.linter.config.strict_engineering_notation - and wrong_engineering_notation - ): - return raise_bad_float_notation( - {self.to_standard_engineering_notation(value)} - ) - if wrong_scientific_notation and wrong_engineering_notation: - return raise_bad_float_notation( - { - self.to_standard_scientific_notation(value), - self.to_standard_engineering_notation(value), - self.to_standard_underscore_grouping(value), - } - ) - if has_underscore: + return raise_bad_float_notation() + base_as_str, exponent_as_str = string.lower().split("e") + base = float(base_as_str) + print("\tBase:", base, "Exponent:", exponent_as_str) + wrong_scientific_notation = not (1 <= base < 10) + print(f"\twrong_scientific_notation:{wrong_scientific_notation}") + if ( + self.linter.config.strict_scientific_notation + and wrong_scientific_notation + ): + return raise_bad_float_notation() + wrong_engineering_notation = not ( + 1 <= base < 1000 and int(exponent_as_str) % 3 == 0 + ) + print(f"\twrong_engineering_notation:{wrong_engineering_notation}") + if ( + self.linter.config.strict_engineering_notation + and wrong_engineering_notation + ) or (wrong_scientific_notation and wrong_engineering_notation): + return raise_bad_float_notation() + elif has_underscore: # If we have underscore and exponent, we suggest exponent by default - if self.linter.config.strict_scientific_notation: - return raise_bad_float_notation( - {self.to_standard_scientific_notation(value)} - ) - if self.linter.config.strict_engineering_notation: - return raise_bad_float_notation( - {self.to_standard_engineering_notation(value)} - ) - underscore_notation = has_underscore and re.match( + print("\tHas underscore, checking underscore grouping") + if ( + self.linter.config.strict_scientific_notation + or self.linter.config.strict_engineering_notation + ): + return raise_bad_float_notation() + wrong_underscore_notation = not re.match( r"^\d{0,3}(_\d{3})*\.?\d*([eE]-?\d{0,3}(_\d{3})*)?$", string ) - if not underscore_notation: - return raise_bad_float_notation( - {self.to_standard_underscore_grouping(value)} - ) - return None + print(f"\twrong_underscore_notation:{wrong_underscore_notation}") + if pep515 and wrong_underscore_notation: + return raise_bad_float_notation() + else: + return raise_bad_float_notation() return None def _check_line_ending(self, line_ending: str, line_num: int) -> None: diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.txt b/tests/functional/b/bad_float/bad_float_engineering_notation.txt new file mode 100644 index 0000000000..ec4a52dbe0 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.txt @@ -0,0 +1,4 @@ +bad-float-notation:3:33:3:38::float literal should be written as '1.23e6' instead:HIGH +bad-float-notation:4:38:4:45::float literal should be written as '12.344999999999999e9' instead:HIGH +bad-float-notation:5:35:5:43::float literal should be written as '10e6' instead:HIGH +bad-float-notation:6:33:6:38::float literal should be written as '990.0' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index 97abfdd6be..b1476c576a 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -24,7 +24,7 @@ under_a_thousand = 990 # Content of bad_float_scientific_notation strict tests tested with default configuration -base_not_between_one_and_ten = 10e3 # [bad-float-notation] +base_not_between_one_and_ten = 10e3 above_threshold_without_exponent_2 = 10000000 # [bad-float-notation] under_ten_with_exponent = 9.9e0 # [bad-float-notation] base_between_one_and_ten = 1e4 @@ -32,14 +32,14 @@ under_ten = 9.9 -wrong_big = 45.3e6 # [bad-float-notation] -uppercase_e_wrong = 45.3E6 # [bad-float-notation] +wrong_big = 45.3e7 # [bad-float-notation] +uppercase_e_wrong = 45.3E7 # [bad-float-notation] wrong_small = 0.00012e-26 # [bad-float-notation] uppercase_e_wrong_small = 0.00012E-26 # [bad-float-notation] -wrong_negative_and_big = -10e3 # [bad-float-notation] -actual_trolling = 11000e26 # [bad-float-notation] +wrong_negative_and_big = -10e5 # [bad-float-notation] +actual_trolling = 11000e27 # [bad-float-notation] scientific_double_digit = 12e8 # [bad-float-notation] -scientific_triple_digit = 123e3 # [bad-float-notation] +scientific_triple_digit = 123e3 zero_before_decimal_small = 0.0001e-5 # [bad-float-notation] zero_before_decimal_big = 0.0001e5 # [bad-float-notation] negative_decimal = -0.5e10 # [bad-float-notation] @@ -57,9 +57,10 @@ correct_with_plus = 1.2e+10 correct_decimal_only = 3.14 negative_correct = -5.67e-8 -correct_small_exponent = 1.5e1 -actually_nine = 9e0 -actually_one = 1.0e0 +correct_small_exponent = 1.5e1 # [bad-float-notation] +actually_nine = 9e0 # [bad-float-notation] +actually_one = 1.0e0 # [bad-float-notation] + hex_constant = 0x1e4 # Hexadecimal, not scientific notation @@ -76,15 +77,15 @@ complex_number = 1.5e3 + 2.5e3j # Complex number with scientific notation # false negative for complex numbers: -complex_number_wrong = 15e3 + 25e3j # [bad-float-notation] +complex_number_wrong = 15e4 + 25e7j # [bad-float-notation] #+1: [bad-float-notation, bad-float-notation] -def function_with_sci(param=10.0e3, other_param=20.0e3): +def function_with_sci(param=10.0e4, other_param=20.0e5): return param, other_param #+1: [bad-float-notation, bad-float-notation] -result = function_with_sci(20.0e3, 10.0e3) +result = function_with_sci(20.0e4, 10.0e7) valid_underscore_int = 1_000_000 valid_underscore_float = 1_000_000.12345 diff --git a/tests/functional/b/bad_float/bad_float_pep515.py b/tests/functional/b/bad_float/bad_float_pep515.py index 28b10a0efd..a65d65640a 100644 --- a/tests/functional/b/bad_float/bad_float_pep515.py +++ b/tests/functional/b/bad_float/bad_float_pep515.py @@ -3,6 +3,7 @@ not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] above_threshold_without_grouping = 123456789 # [bad-float-notation] +scientific_notation = 1.2345678e16 # [bad-float-notation] +engineering_notation = 12.345678e15 # [bad-float-notation] + proper_grouping = 123_456_789 -scientific_notation = 1.2345678e16 -engineering_notation = 12.345678e15 diff --git a/tests/functional/b/bad_float/bad_float_pep515.txt b/tests/functional/b/bad_float/bad_float_pep515.txt new file mode 100644 index 0000000000..f1bbd73f00 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_pep515.txt @@ -0,0 +1,5 @@ +bad-float-notation:3:23:3:36::float literal should be written as '123_456_789.0' instead:HIGH +bad-float-notation:4:23:4:38::float literal should be written as '1.2345678e+16' instead:HIGH +bad-float-notation:5:35:5:44::float literal should be written as '123_456_789.0' instead:HIGH +bad-float-notation:6:22:6:34::float literal should be written as '1.2345678e+16' instead:HIGH +bad-float-notation:7:23:7:35::float literal should be written as '1.2345678e+16' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.txt b/tests/functional/b/bad_float/bad_float_scientific_notation.txt new file mode 100644 index 0000000000..72b1a4c5c8 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.txt @@ -0,0 +1,3 @@ +bad-float-notation:3:31:3:35::float literal should be written as '1e4' instead:HIGH +bad-float-notation:4:35:4:43::float literal should be written as '1e7' instead:HIGH +bad-float-notation:5:26:5:31::float literal should be written as '9.9' instead:HIGH From 2886dc70aefd163a252d28429e7c5542bc5b4f16 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 21 Jun 2025 08:26:28 +0200 Subject: [PATCH 06/15] Fix the 0 case and mixed underscore and exponent case --- pylint/checkers/format.py | 23 ++-- .../b/bad_float/bad_float_notation_default.py | 44 ++++--- .../bad_float/bad_float_notation_default.txt | 108 +++++++++--------- .../b/bad_float/bad_float_pep515.py | 2 + .../bad_float_scientific_notation.py | 1 + 5 files changed, 83 insertions(+), 95 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 46821e71e4..fd8d441702 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -566,7 +566,6 @@ def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]: return "0", "0" if number == math.inf: return "math.inf", "0" - print(f"Converting {number} to standard or engineering base") exponent = math.floor(math.log10(abs(number))) if exponent == 0: return str(number), "0" @@ -662,7 +661,6 @@ def raise_bad_float_notation() -> None: suggested.add(self.to_standard_engineering_notation(value)) if pep515: suggested.add(self.to_standard_underscore_grouping(value)) - print("\tBad float notation, suggesting:", suggested) return self.add_message( "bad-float-notation", args=("' or '".join(sorted(suggested))), @@ -679,9 +677,8 @@ def raise_bad_float_notation() -> None: 1 <= value < 10 and self.linter.config.strict_scientific_notation ) or 1 <= value < 1000 is_written_complexly = has_underscore or has_exponent - print(f"Checking {string} line {line_num}") + # print(f"Checking {string} line {line_num}") if should_be_written_simply and is_written_complexly: - print(f"\t{value} is a simple number") # If the value does not deserve a complex notation then write it in a simple way. # The threshold is guaranteed to be higher than those value. # When 1 <= value < 10 the engineering notation is equivalent to the scientific notation @@ -692,29 +689,26 @@ def raise_bad_float_notation() -> None: abs_value < self.linter.config.float_notation_threshold # under threshold and ( # use scientific or engineering notation and under 1/threshold self.linter.config.strict_underscore_notation - or abs_value > 1 / self.linter.config.float_notation_threshold + or ( + value == 0 + or abs_value > 1 / self.linter.config.float_notation_threshold + ) ) ) - print( - "\tshould_not_be_checked_because_of_threshold=", - should_not_be_checked_because_of_threshold, - ) if not is_written_complexly: if should_not_be_checked_because_of_threshold: # This number is free style, we do not have to check it, unless it's # written complexly, then it could be badly written - print("\tFree style number, no need to check") return None return raise_bad_float_notation() if has_exponent: - if self.linter.config.strict_underscore_notation: + if self.linter.config.strict_underscore_notation or has_underscore: # If we have exponent it means it's not proper underscore return raise_bad_float_notation() base_as_str, exponent_as_str = string.lower().split("e") base = float(base_as_str) - print("\tBase:", base, "Exponent:", exponent_as_str) + # print("\tBase:", base, "Exponent:", exponent_as_str) wrong_scientific_notation = not (1 <= base < 10) - print(f"\twrong_scientific_notation:{wrong_scientific_notation}") if ( self.linter.config.strict_scientific_notation and wrong_scientific_notation @@ -723,7 +717,6 @@ def raise_bad_float_notation() -> None: wrong_engineering_notation = not ( 1 <= base < 1000 and int(exponent_as_str) % 3 == 0 ) - print(f"\twrong_engineering_notation:{wrong_engineering_notation}") if ( self.linter.config.strict_engineering_notation and wrong_engineering_notation @@ -731,7 +724,6 @@ def raise_bad_float_notation() -> None: return raise_bad_float_notation() elif has_underscore: # If we have underscore and exponent, we suggest exponent by default - print("\tHas underscore, checking underscore grouping") if ( self.linter.config.strict_scientific_notation or self.linter.config.strict_engineering_notation @@ -740,7 +732,6 @@ def raise_bad_float_notation() -> None: wrong_underscore_notation = not re.match( r"^\d{0,3}(_\d{3})*\.?\d*([eE]-?\d{0,3}(_\d{3})*)?$", string ) - print(f"\twrong_underscore_notation:{wrong_underscore_notation}") if pep515 and wrong_underscore_notation: return raise_bad_float_notation() else: diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index b1476c576a..851581f914 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -61,8 +61,6 @@ actually_nine = 9e0 # [bad-float-notation] actually_one = 1.0e0 # [bad-float-notation] - - hex_constant = 0x1e4 # Hexadecimal, not scientific notation hex_constant_bad = 0x10e4 binary_constant = 0b1010 @@ -78,6 +76,7 @@ complex_number = 1.5e3 + 2.5e3j # Complex number with scientific notation # false negative for complex numbers: complex_number_wrong = 15e4 + 25e7j # [bad-float-notation] +underscore_binary = 0b1010_1010 #+1: [bad-float-notation, bad-float-notation] @@ -101,34 +100,31 @@ def function_with_sci(param=10.0e4, other_param=20.0e5): incorrect_sci_uppercase = 1.234_567E6 # [bad-float-notation] incorrect_sci_underscore_exp = 1.2e1_0 # [bad-float-notation] invalid_underscore_float = 1_234.567_89 # [bad-float-notation] -invalid_underscore_binary = 0b1010_1010 # [bad-float-notation] -#+1: [bad-float-notation, bad-float-notation] -wrong_big_underscore = 45.3_45e6 -#+1: [bad-float-notation, bad-float-notation] -wrong_small_underscore = 0.000_12e-26 -#+1: [bad-float-notation, bad-float-notation] -scientific_double_digit_underscore = 1_2e8 -#+1: [bad-float-notation, bad-float-notation] -scientific_triple_digit_underscore = 12_3e3 -#+1: [bad-float-notation, bad-float-notation] -invalid_underscore_sci = 1_234.567_89e10 +wrong_big_underscore = 45.3_45e6 # [bad-float-notation] +wrong_small_underscore = 0.000_12e-26 # [bad-float-notation] +scientific_double_digit_underscore = 1_2e8 # [bad-float-notation] +scientific_triple_digit_underscore = 12_3e3 # [bad-float-notation] +invalid_underscore_sci = 1_234.567_89e10 # [bad-float-notation] invalid_underscore_sci_exp = 1.2e1_0 # [bad-float-notation] -#+1: [bad-float-notation, bad-float-notation] -invalid_underscore_sci_combined = 1_2.3_4e5_6 -#+1: [bad-float-notation, bad-float-notation] -invalid_uppercase_sci = 1_234.567_89E10 -edge_underscore_1 = 1_0e6 # [bad-float-notation, bad-float-notation] +invalid_underscore_sci_combined = 1_2.3_4e5_6 # [bad-float-notation] +invalid_uppercase_sci = 1_234.567_89E10 # [bad-float-notation] +edge_underscore_1 = 1_0e6 # [bad-float-notation] mixed_underscore_1 = 1_000_000.0e-3 # [bad-float-notation] -#+1: [bad-float-notation, bad-float-notation] -mixed_underscore_2 = 0.000_001e3 -mixed_underscore_3 = 1_0.0e2 # [bad-float-notation, bad-float-notation] +mixed_underscore_2 = 0.000_001e3 # [bad-float-notation] +mixed_underscore_3 = 1_0.0e2 # [bad-float-notation] # Complex numbers with underscores complex_underscore = 1.5_6e3 + 2.5_6e3j # [bad-float-notation] -#+1: [bad-float-notation, bad-float-notation] -complex_underscore_wrong = 15_6e2 + 25_6e2j +complex_underscore_wrong = 15_6e2 + 25_6e2j # [bad-float-notation] -#+2: [bad-float-notation, bad-float-notation] #+1: [bad-float-notation, bad-float-notation] def function_with_underscore(param=10.0_0e3, other_param=20.0_0e3): return param, other_param + +int_under_ten = 9 +int_under_a_thousand = 998 + +for i in range(10): + if i < 0: + continue + print("Let's not be really annoying.") diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index d63c74fc1f..d4a1b6dac9 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -1,55 +1,53 @@ -bad-float-notation:3:12:None:None::Scientific notation should be '4.53e1' instead:HIGH -bad-float-notation:4:20:None:None::Scientific notation should be '4.53e1' instead:HIGH -bad-float-notation:5:14:None:None::Scientific notation should be '1.2e-4' instead:HIGH -bad-float-notation:6:26:None:None::Scientific notation should be '1.2e-4' instead:HIGH -bad-float-notation:7:26:None:None::Scientific notation should be '1e1' instead:HIGH -bad-float-notation:8:18:None:None::Scientific notation should be '1.1e4' instead:HIGH -bad-float-notation:9:26:None:None::Scientific notation should be '1.2e1' instead:HIGH -bad-float-notation:10:26:None:None::Scientific notation should be '1.23e2' instead:HIGH -bad-float-notation:11:28:None:None::Scientific notation should be '1e-4' instead:HIGH -bad-float-notation:12:26:None:None::Scientific notation should be '1e-4' instead:HIGH -bad-float-notation:13:20:None:None::Scientific notation should be '5e-1' instead:HIGH -bad-float-notation:14:12:None:None::Scientific notation should be '0e0' instead:HIGH -bad-float-notation:47:23:None:None::Scientific notation should be '1.5e1' instead:HIGH -bad-float-notation:51:28:None:None::Scientific notation should be '1e1' instead:HIGH -bad-float-notation:51:48:None:None::Scientific notation should be '2e1' instead:HIGH -bad-float-notation:55:35:None:None::Scientific notation should be '1e1' instead:HIGH -bad-float-notation:55:27:None:None::Scientific notation should be '2e1' instead:HIGH -bad-float-notation:59:29:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH -bad-float-notation:60:33:None:None::Scientific notation should be '1.2300000012345e8' instead:HIGH -bad-float-notation:65:34:None:None::Non standard grouping of numeric literals using underscores should be .123456:HIGH -bad-float-notation:66:35:None:None::Non standard grouping of numeric literals using underscores should be 123_456.123456:HIGH -bad-float-notation:67:27:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH -bad-float-notation:68:26:None:None::Non standard grouping of numeric literals using underscores should be 1.234567e6:HIGH -bad-float-notation:69:31:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH -bad-float-notation:70:27:None:None::Non standard grouping of numeric literals using underscores should be 1_234.56789:HIGH -bad-float-notation:71:28:None:None::Non standard grouping of numeric literals using underscores should be 0_b10_101_010:HIGH -bad-float-notation:73:23:None:None::Non standard grouping of numeric literals using underscores should be 4.5345e7:HIGH -bad-float-notation:73:23:None:None::Scientific notation should be '4.5345e1' instead:HIGH -bad-float-notation:75:25:None:None::Non standard grouping of numeric literals using underscores should be 1.2e-30:HIGH -bad-float-notation:75:25:None:None::Scientific notation should be '1.2e-4' instead:HIGH -bad-float-notation:77:37:None:None::Non standard grouping of numeric literals using underscores should be 1.2e9:HIGH -bad-float-notation:77:37:None:None::Scientific notation should be '1.2e1' instead:HIGH -bad-float-notation:79:37:None:None::Non standard grouping of numeric literals using underscores should be 1.23e5:HIGH -bad-float-notation:79:37:None:None::Scientific notation should be '1.23e2' instead:HIGH -bad-float-notation:81:25:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH -bad-float-notation:81:25:None:None::Scientific notation should be '1.23456789e3' instead:HIGH -bad-float-notation:82:29:None:None::Non standard grouping of numeric literals using underscores should be 1.2e10:HIGH -bad-float-notation:84:34:None:None::Non standard grouping of numeric literals using underscores should be 1.234e57:HIGH -bad-float-notation:84:34:None:None::Scientific notation should be '1.234e1' instead:HIGH -bad-float-notation:86:24:None:None::Non standard grouping of numeric literals using underscores should be 1.23456789e13:HIGH -bad-float-notation:86:24:None:None::Scientific notation should be '1.23456789e3' instead:HIGH -bad-float-notation:87:20:None:None::Non standard grouping of numeric literals using underscores should be 1e7:HIGH -bad-float-notation:87:20:None:None::Scientific notation should be '1e1' instead:HIGH -bad-float-notation:88:21:None:None::Scientific notation should be '1e6' instead:HIGH -bad-float-notation:90:21:None:None::Non standard grouping of numeric literals using underscores should be 1e-3:HIGH -bad-float-notation:90:21:None:None::Scientific notation should be '1e-6' instead:HIGH -bad-float-notation:91:21:None:None::Non standard grouping of numeric literals using underscores should be 1e3:HIGH -bad-float-notation:91:21:None:None::Scientific notation should be '1e1' instead:HIGH -bad-float-notation:94:21:None:None::Non standard grouping of numeric literals using underscores should be 1.56e3:HIGH -bad-float-notation:96:27:None:None::Non standard grouping of numeric literals using underscores should be 1.56e4:HIGH -bad-float-notation:96:27:None:None::Scientific notation should be '1.56e2' instead:HIGH -bad-float-notation:100:35:None:None::Non standard grouping of numeric literals using underscores should be 1e4:HIGH -bad-float-notation:100:57:None:None::Non standard grouping of numeric literals using underscores should be 2e4:HIGH -bad-float-notation:100:35:None:None::Scientific notation should be '1e1' instead:HIGH -bad-float-notation:100:57:None:None::Scientific notation should be '2e1' instead:HIGH +bad-float-notation:4:19:4:25::float literal should be written as '1.504e8' or '150.4e6' or '150_400_000.0' instead:HIGH +bad-float-notation:10:23:10:36::float literal should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH +bad-float-notation:11:23:11:38::float literal should be written as '1.2345678e+16' or '1.2345678e16' or '12.345678e15' instead:HIGH +bad-float-notation:12:35:12:44::float literal should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH +bad-float-notation:18:33:18:38::float literal should be written as '1.23e6' or '1_230_000.0' instead:HIGH +bad-float-notation:19:38:19:45::float literal should be written as '1.2345e10' or '12.344999999999999e9' or '12_345_000_000.0' instead:HIGH +bad-float-notation:20:35:20:43::float literal should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:21:33:21:38::float literal should be written as '9.9e2' or '990.0' instead:HIGH +bad-float-notation:28:37:28:45::float literal should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:29:26:29:31::float literal should be written as '9.9' instead:HIGH +bad-float-notation:35:12:35:18::float literal should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH +bad-float-notation:36:20:36:26::float literal should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH +bad-float-notation:37:14:37:25::float literal should be written as '1.2e-30' instead:HIGH +bad-float-notation:38:26:38:37::float literal should be written as '1.2e-30' instead:HIGH +bad-float-notation:39:26:39:30::float literal should be written as '1_000_000.0' or '1e6' instead:HIGH +bad-float-notation:40:18:40:26::float literal should be written as '1.1e+31' or '1.1e31' or '11e30' instead:HIGH +bad-float-notation:41:26:41:30::float literal should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH +bad-float-notation:43:28:43:37::float literal should be written as '1e-09' or '1e-9' instead:HIGH +bad-float-notation:44:26:44:34::float literal should be written as '10.0' or '1e1' instead:HIGH +bad-float-notation:45:20:45:26::float literal should be written as '5_000_000_000.0' or '5e9' instead:HIGH +bad-float-notation:46:12:46:16::float literal should be written as '0.0' instead:HIGH +bad-float-notation:60:25:60:30::float literal should be written as '1.5e1' or '15.0' instead:HIGH +bad-float-notation:61:16:61:19::float literal should be written as '9.0' instead:HIGH +bad-float-notation:62:15:62:20::float literal should be written as '1.0' instead:HIGH +bad-float-notation:78:23:78:27::float literal should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH +bad-float-notation:83:28:83:34::float literal should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH +bad-float-notation:83:48:83:54::float literal should be written as '2_000_000.0' or '2e6' instead:HIGH +bad-float-notation:87:35:87:41::float literal should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH +bad-float-notation:87:27:87:33::float literal should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH +bad-float-notation:91:29:91:57::float literal should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:92:33:92:62::float literal should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:97:34:97:42::float literal should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH +bad-float-notation:98:35:98:50::float literal should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH +bad-float-notation:99:27:99:38::float literal should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:100:26:100:37::float literal should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:101:31:101:38::float literal should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:102:27:102:39::float literal should be written as '1.23456789e3' or '1_234.56789' instead:HIGH +bad-float-notation:103:23:103:32::float literal should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH +bad-float-notation:104:25:104:37::float literal should be written as '1.2e-30' instead:HIGH +bad-float-notation:105:37:105:42::float literal should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH +bad-float-notation:106:37:106:43::float literal should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH +bad-float-notation:107:25:107:40::float literal should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:108:29:108:36::float literal should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:109:34:109:45::float literal should be written as '1.234e+57' or '1.234e57' instead:HIGH +bad-float-notation:110:24:110:39::float literal should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:111:20:111:25::float literal should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:112:21:112:35::float literal should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:113:21:113:32::float literal should be written as '0.001' or '1e-3' instead:HIGH +bad-float-notation:114:21:114:28::float literal should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:117:21:117:28::float literal should be written as '1.56e3' or '1_560.0' instead:HIGH +bad-float-notation:118:27:118:33::float literal should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH +bad-float-notation:121:35:121:43::float literal should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH +bad-float-notation:121:57:121:65::float literal should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_pep515.py b/tests/functional/b/bad_float/bad_float_pep515.py index a65d65640a..00ba8b6c6e 100644 --- a/tests/functional/b/bad_float/bad_float_pep515.py +++ b/tests/functional/b/bad_float/bad_float_pep515.py @@ -7,3 +7,5 @@ engineering_notation = 12.345678e15 # [bad-float-notation] proper_grouping = 123_456_789 + +int_under_ten = 9 diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.py b/tests/functional/b/bad_float/bad_float_scientific_notation.py index 2ad34a3cd1..3e337a7cd4 100644 --- a/tests/functional/b/bad_float/bad_float_scientific_notation.py +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.py @@ -6,3 +6,4 @@ base_between_one_and_ten = 1e4 above_threshold_with_exponent = 1e7 under_ten = 9.9 +int_under_ten = 9 From 7d5672da353662f64354fd582ec6316eee320483 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 21 Jun 2025 13:36:13 +0200 Subject: [PATCH 07/15] Give a reason for why a notation is bad and display the float (temporarily for easier primer reading) --- pylint/checkers/format.py | 57 +++++++--- .../bad_float_engineering_notation.txt | 8 +- .../bad_float/bad_float_notation_default.txt | 106 +++++++++--------- .../b/bad_float/bad_float_pep515.txt | 10 +- .../bad_float_scientific_notation.txt | 6 +- 5 files changed, 107 insertions(+), 80 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index fd8d441702..018485142d 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -114,7 +114,7 @@ "Used when there is different newline than expected.", ), "C0329": ( - "float literal should be written as '%s' instead", + "'%s' %s, and it should be written as '%s' instead", "bad-float-notation", "Emitted when a number is written in a non-standard notation. The three " "allowed notation above the threshold are the scientific notation, the " @@ -516,10 +516,9 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: if tok_type == tokenize.NUMBER: if ( self.linter.is_message_enabled("bad-float-notation") - and # You don't deserve a linter if you mix non-decimal notation and # exponential or underscore, - "x" not in string # not a hexadecimal + and "x" not in string # not a hexadecimal and "o" not in string # not an octal and "j" not in string # not a complex and "b" not in string # not a binary @@ -653,7 +652,7 @@ def _check_bad_float_notation( # pylint: disable=too-many-locals or self.linter.config.strict_underscore_notation ) - def raise_bad_float_notation() -> None: + def raise_bad_float_notation(reason: str) -> None: suggested = set() if scientific: suggested.add(self.to_standard_scientific_notation(value)) @@ -663,7 +662,7 @@ def raise_bad_float_notation() -> None: suggested.add(self.to_standard_underscore_grouping(value)) return self.add_message( "bad-float-notation", - args=("' or '".join(sorted(suggested))), + args=(string, reason, "' or '".join(sorted(suggested))), line=line_num, end_lineno=line_num, col_offset=start[1], @@ -682,11 +681,12 @@ def raise_bad_float_notation() -> None: # If the value does not deserve a complex notation then write it in a simple way. # The threshold is guaranteed to be higher than those value. # When 1 <= value < 10 the engineering notation is equivalent to the scientific notation - return raise_bad_float_notation() + return raise_bad_float_notation("has underscore or exponent") abs_value = abs(value) + under_threshold = abs_value < self.linter.config.float_notation_threshold should_not_be_checked_because_of_threshold = ( - abs_value < self.linter.config.float_notation_threshold # under threshold + under_threshold # under threshold and ( # use scientific or engineering notation and under 1/threshold self.linter.config.strict_underscore_notation or ( @@ -700,11 +700,22 @@ def raise_bad_float_notation() -> None: # This number is free style, we do not have to check it, unless it's # written complexly, then it could be badly written return None - return raise_bad_float_notation() + threshold = self.linter.config.float_notation_threshold + close_to_zero_threshold = self.to_standard_scientific_notation( + 1 / threshold + ) + threshold = self.to_standard_scientific_notation(threshold) + return raise_bad_float_notation( + f"is smaller than {close_to_zero_threshold}" + if under_threshold + else f"is bigger than {threshold}" + ) if has_exponent: if self.linter.config.strict_underscore_notation or has_underscore: # If we have exponent it means it's not proper underscore - return raise_bad_float_notation() + return raise_bad_float_notation( + "has exponent and underscore at the same time" + ) base_as_str, exponent_as_str = string.lower().split("e") base = float(base_as_str) # print("\tBase:", base, "Exponent:", exponent_as_str) @@ -713,7 +724,11 @@ def raise_bad_float_notation() -> None: self.linter.config.strict_scientific_notation and wrong_scientific_notation ): - return raise_bad_float_notation() + return raise_bad_float_notation( + f"has a base, '{base}', that is not strictly inferior to 10" + if base == 10 + else f"has a base, '{base}', that is not between 1 and 10" + ) wrong_engineering_notation = not ( 1 <= base < 1000 and int(exponent_as_str) % 3 == 0 ) @@ -721,21 +736,33 @@ def raise_bad_float_notation() -> None: self.linter.config.strict_engineering_notation and wrong_engineering_notation ) or (wrong_scientific_notation and wrong_engineering_notation): - return raise_bad_float_notation() + return raise_bad_float_notation( + f"has an exponent '{exponent_as_str}' that is not a multiple of 3" + if 1 <= base < 1000 + else ( + f"has a base, '{base}', that is not strictly inferior to 1000" + if base == 1000 + else f"has a base, '{base}', that is not between 1 and 1000" + ) + ) elif has_underscore: # If we have underscore and exponent, we suggest exponent by default if ( self.linter.config.strict_scientific_notation or self.linter.config.strict_engineering_notation ): - return raise_bad_float_notation() + return raise_bad_float_notation( + "use underscore instead of exponents" + "" + if self.linter.config.strict_scientific_notation + else " that are multiple of 3" + ) wrong_underscore_notation = not re.match( r"^\d{0,3}(_\d{3})*\.?\d*([eE]-?\d{0,3}(_\d{3})*)?$", string ) if pep515 and wrong_underscore_notation: - return raise_bad_float_notation() - else: - return raise_bad_float_notation() + return raise_bad_float_notation( + "has underscores that are not delimiting packs of three digits" + ) return None def _check_line_ending(self, line_ending: str, line_num: int) -> None: diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.txt b/tests/functional/b/bad_float/bad_float_engineering_notation.txt index ec4a52dbe0..09b8cef7a4 100644 --- a/tests/functional/b/bad_float/bad_float_engineering_notation.txt +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.txt @@ -1,4 +1,4 @@ -bad-float-notation:3:33:3:38::float literal should be written as '1.23e6' instead:HIGH -bad-float-notation:4:38:4:45::float literal should be written as '12.344999999999999e9' instead:HIGH -bad-float-notation:5:35:5:43::float literal should be written as '10e6' instead:HIGH -bad-float-notation:6:33:6:38::float literal should be written as '990.0' instead:HIGH +bad-float-notation:3:33:3:38::'123e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.23e6' instead:HIGH +bad-float-notation:4:38:4:45::'12345e6' has a base, '12345.0', that is not between 1 and 1000, and it should be written as '12.344999999999999e9' instead:HIGH +bad-float-notation:5:35:5:43::'10000000' is bigger than 1e6, and it should be written as '10e6' instead:HIGH +bad-float-notation:6:33:6:38::'9.9e2' has underscore or exponent, and it should be written as '990.0' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index d4a1b6dac9..ed33ce0755 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -1,53 +1,53 @@ -bad-float-notation:4:19:4:25::float literal should be written as '1.504e8' or '150.4e6' or '150_400_000.0' instead:HIGH -bad-float-notation:10:23:10:36::float literal should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH -bad-float-notation:11:23:11:38::float literal should be written as '1.2345678e+16' or '1.2345678e16' or '12.345678e15' instead:HIGH -bad-float-notation:12:35:12:44::float literal should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH -bad-float-notation:18:33:18:38::float literal should be written as '1.23e6' or '1_230_000.0' instead:HIGH -bad-float-notation:19:38:19:45::float literal should be written as '1.2345e10' or '12.344999999999999e9' or '12_345_000_000.0' instead:HIGH -bad-float-notation:20:35:20:43::float literal should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH -bad-float-notation:21:33:21:38::float literal should be written as '9.9e2' or '990.0' instead:HIGH -bad-float-notation:28:37:28:45::float literal should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH -bad-float-notation:29:26:29:31::float literal should be written as '9.9' instead:HIGH -bad-float-notation:35:12:35:18::float literal should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH -bad-float-notation:36:20:36:26::float literal should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH -bad-float-notation:37:14:37:25::float literal should be written as '1.2e-30' instead:HIGH -bad-float-notation:38:26:38:37::float literal should be written as '1.2e-30' instead:HIGH -bad-float-notation:39:26:39:30::float literal should be written as '1_000_000.0' or '1e6' instead:HIGH -bad-float-notation:40:18:40:26::float literal should be written as '1.1e+31' or '1.1e31' or '11e30' instead:HIGH -bad-float-notation:41:26:41:30::float literal should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH -bad-float-notation:43:28:43:37::float literal should be written as '1e-09' or '1e-9' instead:HIGH -bad-float-notation:44:26:44:34::float literal should be written as '10.0' or '1e1' instead:HIGH -bad-float-notation:45:20:45:26::float literal should be written as '5_000_000_000.0' or '5e9' instead:HIGH -bad-float-notation:46:12:46:16::float literal should be written as '0.0' instead:HIGH -bad-float-notation:60:25:60:30::float literal should be written as '1.5e1' or '15.0' instead:HIGH -bad-float-notation:61:16:61:19::float literal should be written as '9.0' instead:HIGH -bad-float-notation:62:15:62:20::float literal should be written as '1.0' instead:HIGH -bad-float-notation:78:23:78:27::float literal should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH -bad-float-notation:83:28:83:34::float literal should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH -bad-float-notation:83:48:83:54::float literal should be written as '2_000_000.0' or '2e6' instead:HIGH -bad-float-notation:87:35:87:41::float literal should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH -bad-float-notation:87:27:87:33::float literal should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH -bad-float-notation:91:29:91:57::float literal should be written as 'inf.0' or 'math.inf' instead:HIGH -bad-float-notation:92:33:92:62::float literal should be written as 'inf.0' or 'math.inf' instead:HIGH -bad-float-notation:97:34:97:42::float literal should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH -bad-float-notation:98:35:98:50::float literal should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH -bad-float-notation:99:27:99:38::float literal should be written as '1.234567e6' or '1_234_567.0' instead:HIGH -bad-float-notation:100:26:100:37::float literal should be written as '1.234567e6' or '1_234_567.0' instead:HIGH -bad-float-notation:101:31:101:38::float literal should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH -bad-float-notation:102:27:102:39::float literal should be written as '1.23456789e3' or '1_234.56789' instead:HIGH -bad-float-notation:103:23:103:32::float literal should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH -bad-float-notation:104:25:104:37::float literal should be written as '1.2e-30' instead:HIGH -bad-float-notation:105:37:105:42::float literal should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH -bad-float-notation:106:37:106:43::float literal should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH -bad-float-notation:107:25:107:40::float literal should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH -bad-float-notation:108:29:108:36::float literal should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH -bad-float-notation:109:34:109:45::float literal should be written as '1.234e+57' or '1.234e57' instead:HIGH -bad-float-notation:110:24:110:39::float literal should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH -bad-float-notation:111:20:111:25::float literal should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH -bad-float-notation:112:21:112:35::float literal should be written as '1_000.0' or '1e3' instead:HIGH -bad-float-notation:113:21:113:32::float literal should be written as '0.001' or '1e-3' instead:HIGH -bad-float-notation:114:21:114:28::float literal should be written as '1_000.0' or '1e3' instead:HIGH -bad-float-notation:117:21:117:28::float literal should be written as '1.56e3' or '1_560.0' instead:HIGH -bad-float-notation:118:27:118:33::float literal should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH -bad-float-notation:121:35:121:43::float literal should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH -bad-float-notation:121:57:121:65::float literal should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH +bad-float-notation:4:19:4:25::'1504e5' has a base, '1504.0', that is not between 1 and 1000, and it should be written as '1.504e8' or '150.4e6' or '150_400_000.0' instead:HIGH +bad-float-notation:10:23:10:36::'1_23_456_7_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH +bad-float-notation:11:23:11:38::'1_23_4_5_67_8e9' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' or '1.2345678e16' or '12.345678e15' instead:HIGH +bad-float-notation:12:35:12:44::'123456789' is bigger than 1e6, and it should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH +bad-float-notation:18:33:18:38::'123e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.23e6' or '1_230_000.0' instead:HIGH +bad-float-notation:19:38:19:45::'12345e6' has a base, '12345.0', that is not between 1 and 1000, and it should be written as '1.2345e10' or '12.344999999999999e9' or '12_345_000_000.0' instead:HIGH +bad-float-notation:20:35:20:43::'10000000' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:21:33:21:38::'9.9e2' has underscore or exponent, and it should be written as '9.9e2' or '990.0' instead:HIGH +bad-float-notation:28:37:28:45::'10000000' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:29:26:29:31::'9.9e0' has underscore or exponent, and it should be written as '9.9' instead:HIGH +bad-float-notation:35:12:35:18::'45.3e7' has an exponent '7' that is not a multiple of 3, and it should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH +bad-float-notation:36:20:36:26::'45.3E7' has an exponent '7' that is not a multiple of 3, and it should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH +bad-float-notation:37:14:37:25::'0.00012e-26' has a base, '0.00012', that is not between 1 and 1000, and it should be written as '1.2e-30' instead:HIGH +bad-float-notation:38:26:38:37::'0.00012E-26' has a base, '0.00012', that is not between 1 and 1000, and it should be written as '1.2e-30' instead:HIGH +bad-float-notation:39:26:39:30::'10e5' has an exponent '5' that is not a multiple of 3, and it should be written as '1_000_000.0' or '1e6' instead:HIGH +bad-float-notation:40:18:40:26::'11000e27' has a base, '11000.0', that is not between 1 and 1000, and it should be written as '1.1e+31' or '1.1e31' or '11e30' instead:HIGH +bad-float-notation:41:26:41:30::'12e8' has an exponent '8' that is not a multiple of 3, and it should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH +bad-float-notation:43:28:43:37::'0.0001e-5' has a base, '0.0001', that is not between 1 and 1000, and it should be written as '1e-09' or '1e-9' instead:HIGH +bad-float-notation:44:26:44:34::'0.0001e5' has underscore or exponent, and it should be written as '10.0' or '1e1' instead:HIGH +bad-float-notation:45:20:45:26::'0.5e10' has a base, '0.5', that is not between 1 and 1000, and it should be written as '5_000_000_000.0' or '5e9' instead:HIGH +bad-float-notation:46:12:46:16::'0e10' has a base, '0.0', that is not between 1 and 1000, and it should be written as '0.0' instead:HIGH +bad-float-notation:60:25:60:30::'1.5e1' has underscore or exponent, and it should be written as '1.5e1' or '15.0' instead:HIGH +bad-float-notation:61:16:61:19::'9e0' has underscore or exponent, and it should be written as '9.0' instead:HIGH +bad-float-notation:62:15:62:20::'1.0e0' has underscore or exponent, and it should be written as '1.0' instead:HIGH +bad-float-notation:78:23:78:27::'15e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH +bad-float-notation:83:28:83:34::'10.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH +bad-float-notation:83:48:83:54::'20.0e5' has an exponent '5' that is not a multiple of 3, and it should be written as '2_000_000.0' or '2e6' instead:HIGH +bad-float-notation:87:35:87:41::'10.0e7' has an exponent '7' that is not a multiple of 3, and it should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH +bad-float-notation:87:27:87:33::'20.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH +bad-float-notation:91:29:91:57::'123_000_000.12345e12_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:92:33:92:62::'123_000_000.12345E123_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:97:34:97:42::'.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH +bad-float-notation:98:35:98:50::'123_456.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH +bad-float-notation:99:27:99:38::'1.234_567e6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:100:26:100:37::'1.234_567E6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:101:31:101:38::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:102:27:102:39::'1_234.567_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e3' or '1_234.56789' instead:HIGH +bad-float-notation:103:23:103:32::'45.3_45e6' has exponent and underscore at the same time, and it should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH +bad-float-notation:104:25:104:37::'0.000_12e-26' has exponent and underscore at the same time, and it should be written as '1.2e-30' instead:HIGH +bad-float-notation:105:37:105:42::'1_2e8' has exponent and underscore at the same time, and it should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH +bad-float-notation:106:37:106:43::'12_3e3' has exponent and underscore at the same time, and it should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH +bad-float-notation:107:25:107:40::'1_234.567_89e10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:108:29:108:36::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:109:34:109:45::'1_2.3_4e5_6' has exponent and underscore at the same time, and it should be written as '1.234e+57' or '1.234e57' instead:HIGH +bad-float-notation:110:24:110:39::'1_234.567_89E10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:111:20:111:25::'1_0e6' has exponent and underscore at the same time, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:112:21:112:35::'1_000_000.0e-3' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:113:21:113:32::'0.000_001e3' has exponent and underscore at the same time, and it should be written as '0.001' or '1e-3' instead:HIGH +bad-float-notation:114:21:114:28::'1_0.0e2' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:117:21:117:28::'1.5_6e3' has exponent and underscore at the same time, and it should be written as '1.56e3' or '1_560.0' instead:HIGH +bad-float-notation:118:27:118:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH +bad-float-notation:121:35:121:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH +bad-float-notation:121:57:121:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_pep515.txt b/tests/functional/b/bad_float/bad_float_pep515.txt index f1bbd73f00..5f9cf6de71 100644 --- a/tests/functional/b/bad_float/bad_float_pep515.txt +++ b/tests/functional/b/bad_float/bad_float_pep515.txt @@ -1,5 +1,5 @@ -bad-float-notation:3:23:3:36::float literal should be written as '123_456_789.0' instead:HIGH -bad-float-notation:4:23:4:38::float literal should be written as '1.2345678e+16' instead:HIGH -bad-float-notation:5:35:5:44::float literal should be written as '123_456_789.0' instead:HIGH -bad-float-notation:6:22:6:34::float literal should be written as '1.2345678e+16' instead:HIGH -bad-float-notation:7:23:7:35::float literal should be written as '1.2345678e+16' instead:HIGH +bad-float-notation:3:23:3:36::'1_23_456_7_89' has underscores that are not delimiting packs of three digits, and it should be written as '123_456_789.0' instead:HIGH +bad-float-notation:4:23:4:38::'1_23_4_5_67_8e9' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' instead:HIGH +bad-float-notation:5:35:5:44::'123456789' is bigger than 1e6, and it should be written as '123_456_789.0' instead:HIGH +bad-float-notation:6:22:6:34::'1.2345678e16' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' instead:HIGH +bad-float-notation:7:23:7:35::'12.345678e15' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.txt b/tests/functional/b/bad_float/bad_float_scientific_notation.txt index 72b1a4c5c8..eb03b7f40e 100644 --- a/tests/functional/b/bad_float/bad_float_scientific_notation.txt +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.txt @@ -1,3 +1,3 @@ -bad-float-notation:3:31:3:35::float literal should be written as '1e4' instead:HIGH -bad-float-notation:4:35:4:43::float literal should be written as '1e7' instead:HIGH -bad-float-notation:5:26:5:31::float literal should be written as '9.9' instead:HIGH +bad-float-notation:3:31:3:35::'10e3' has a base, '10.0', that is not strictly inferior to 10, and it should be written as '1e4' instead:HIGH +bad-float-notation:4:35:4:43::'10000000' is bigger than 1e6, and it should be written as '1e7' instead:HIGH +bad-float-notation:5:26:5:31::'9.9e0' has underscore or exponent, and it should be written as '9.9' instead:HIGH From 0e2f2dc1d42acbe73614c81f80d63818c6103517 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 21 Jun 2025 15:30:43 +0200 Subject: [PATCH 08/15] Optimize 0 --- pylint/checkers/format.py | 11 ++-- .../b/bad_float/bad_float_notation_default.py | 4 ++ .../bad_float/bad_float_notation_default.txt | 66 ++++++++++--------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 018485142d..349ef0290d 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -670,6 +670,11 @@ def raise_bad_float_notation(reason: str) -> None: confidence=HIGH, ) + if string in {"0", "0.0"}: + # 0 is a special case because it is used very often, and float approximation + # being what they are it needs to be special cased anyway for scientific and + # engineering notation when checking if a number is under 1/threshold + return None has_underscore = "_" in string has_exponent = "e" in string or "E" in string should_be_written_simply = ( @@ -689,10 +694,8 @@ def raise_bad_float_notation(reason: str) -> None: under_threshold # under threshold and ( # use scientific or engineering notation and under 1/threshold self.linter.config.strict_underscore_notation - or ( - value == 0 - or abs_value > 1 / self.linter.config.float_notation_threshold - ) + or abs_value != 0 + or abs_value >= 1 / self.linter.config.float_notation_threshold ) ) if not is_written_complexly: diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index 851581f914..b2d38e4a69 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -44,6 +44,10 @@ zero_before_decimal_big = 0.0001e5 # [bad-float-notation] negative_decimal = -0.5e10 # [bad-float-notation] zero_only = 0e10 # [bad-float-notation] +zero_int = 0 +zero_float = 0.0 +annoying_zero = 00 # [bad-float-notation] +another_annoying_zero = 0. # [bad-float-notation] one_only = 1e6 correct_1 = 4.53e7 diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index ed33ce0755..393bdfd4ab 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -19,35 +19,37 @@ bad-float-notation:43:28:43:37::'0.0001e-5' has a base, '0.0001', that is not be bad-float-notation:44:26:44:34::'0.0001e5' has underscore or exponent, and it should be written as '10.0' or '1e1' instead:HIGH bad-float-notation:45:20:45:26::'0.5e10' has a base, '0.5', that is not between 1 and 1000, and it should be written as '5_000_000_000.0' or '5e9' instead:HIGH bad-float-notation:46:12:46:16::'0e10' has a base, '0.0', that is not between 1 and 1000, and it should be written as '0.0' instead:HIGH -bad-float-notation:60:25:60:30::'1.5e1' has underscore or exponent, and it should be written as '1.5e1' or '15.0' instead:HIGH -bad-float-notation:61:16:61:19::'9e0' has underscore or exponent, and it should be written as '9.0' instead:HIGH -bad-float-notation:62:15:62:20::'1.0e0' has underscore or exponent, and it should be written as '1.0' instead:HIGH -bad-float-notation:78:23:78:27::'15e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH -bad-float-notation:83:28:83:34::'10.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH -bad-float-notation:83:48:83:54::'20.0e5' has an exponent '5' that is not a multiple of 3, and it should be written as '2_000_000.0' or '2e6' instead:HIGH -bad-float-notation:87:35:87:41::'10.0e7' has an exponent '7' that is not a multiple of 3, and it should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH -bad-float-notation:87:27:87:33::'20.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH -bad-float-notation:91:29:91:57::'123_000_000.12345e12_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH -bad-float-notation:92:33:92:62::'123_000_000.12345E123_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH -bad-float-notation:97:34:97:42::'.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH -bad-float-notation:98:35:98:50::'123_456.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH -bad-float-notation:99:27:99:38::'1.234_567e6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH -bad-float-notation:100:26:100:37::'1.234_567E6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH -bad-float-notation:101:31:101:38::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH -bad-float-notation:102:27:102:39::'1_234.567_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e3' or '1_234.56789' instead:HIGH -bad-float-notation:103:23:103:32::'45.3_45e6' has exponent and underscore at the same time, and it should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH -bad-float-notation:104:25:104:37::'0.000_12e-26' has exponent and underscore at the same time, and it should be written as '1.2e-30' instead:HIGH -bad-float-notation:105:37:105:42::'1_2e8' has exponent and underscore at the same time, and it should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH -bad-float-notation:106:37:106:43::'12_3e3' has exponent and underscore at the same time, and it should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH -bad-float-notation:107:25:107:40::'1_234.567_89e10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH -bad-float-notation:108:29:108:36::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH -bad-float-notation:109:34:109:45::'1_2.3_4e5_6' has exponent and underscore at the same time, and it should be written as '1.234e+57' or '1.234e57' instead:HIGH -bad-float-notation:110:24:110:39::'1_234.567_89E10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH -bad-float-notation:111:20:111:25::'1_0e6' has exponent and underscore at the same time, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH -bad-float-notation:112:21:112:35::'1_000_000.0e-3' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH -bad-float-notation:113:21:113:32::'0.000_001e3' has exponent and underscore at the same time, and it should be written as '0.001' or '1e-3' instead:HIGH -bad-float-notation:114:21:114:28::'1_0.0e2' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH -bad-float-notation:117:21:117:28::'1.5_6e3' has exponent and underscore at the same time, and it should be written as '1.56e3' or '1_560.0' instead:HIGH -bad-float-notation:118:27:118:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH -bad-float-notation:121:35:121:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH -bad-float-notation:121:57:121:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH +bad-float-notation:49:16:49:18::'00' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH +bad-float-notation:50:24:50:26::'0.' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH +bad-float-notation:64:25:64:30::'1.5e1' has underscore or exponent, and it should be written as '1.5e1' or '15.0' instead:HIGH +bad-float-notation:65:16:65:19::'9e0' has underscore or exponent, and it should be written as '9.0' instead:HIGH +bad-float-notation:66:15:66:20::'1.0e0' has underscore or exponent, and it should be written as '1.0' instead:HIGH +bad-float-notation:82:23:82:27::'15e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH +bad-float-notation:87:28:87:34::'10.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH +bad-float-notation:87:48:87:54::'20.0e5' has an exponent '5' that is not a multiple of 3, and it should be written as '2_000_000.0' or '2e6' instead:HIGH +bad-float-notation:91:35:91:41::'10.0e7' has an exponent '7' that is not a multiple of 3, and it should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH +bad-float-notation:91:27:91:33::'20.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH +bad-float-notation:95:29:95:57::'123_000_000.12345e12_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:96:33:96:62::'123_000_000.12345E123_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:101:34:101:42::'.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH +bad-float-notation:102:35:102:50::'123_456.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH +bad-float-notation:103:27:103:38::'1.234_567e6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:104:26:104:37::'1.234_567E6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:105:31:105:38::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:106:27:106:39::'1_234.567_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e3' or '1_234.56789' instead:HIGH +bad-float-notation:107:23:107:32::'45.3_45e6' has exponent and underscore at the same time, and it should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH +bad-float-notation:108:25:108:37::'0.000_12e-26' has exponent and underscore at the same time, and it should be written as '1.2e-30' instead:HIGH +bad-float-notation:109:37:109:42::'1_2e8' has exponent and underscore at the same time, and it should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH +bad-float-notation:110:37:110:43::'12_3e3' has exponent and underscore at the same time, and it should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH +bad-float-notation:111:25:111:40::'1_234.567_89e10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:112:29:112:36::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:113:34:113:45::'1_2.3_4e5_6' has exponent and underscore at the same time, and it should be written as '1.234e+57' or '1.234e57' instead:HIGH +bad-float-notation:114:24:114:39::'1_234.567_89E10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:115:20:115:25::'1_0e6' has exponent and underscore at the same time, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:116:21:116:35::'1_000_000.0e-3' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:117:21:117:32::'0.000_001e3' has exponent and underscore at the same time, and it should be written as '0.001' or '1e-3' instead:HIGH +bad-float-notation:118:21:118:28::'1_0.0e2' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:121:21:121:28::'1.5_6e3' has exponent and underscore at the same time, and it should be written as '1.56e3' or '1_560.0' instead:HIGH +bad-float-notation:122:27:122:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH +bad-float-notation:125:35:125:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH +bad-float-notation:125:57:125:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH From 35898def0289d8f79ff4d6bb8553e019bde41b43 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 21 Jun 2025 15:38:54 +0200 Subject: [PATCH 09/15] Handle time better --- pylint/checkers/format.py | 35 ++++++++++++++++--- tests/checkers/unittest_format.py | 2 +- .../b/bad_float/bad_float_notation_default.py | 8 +++++ .../bad_float/bad_float_notation_default.txt | 3 ++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 349ef0290d..15855dc3c3 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -586,6 +586,25 @@ def to_standard_scientific_notation(cls, number: float) -> str: return base return f"{base}.0" + @classmethod + def to_understandable_time(cls, number: float) -> str: + if number % 3600 != 0: + return "" # Not a suspected time + parts: list[int] = [3600] + number //= 3600 + for divisor in ( + 24, + 7, + 365, + ): + if number % divisor == 0: + parts.append(divisor) + number //= divisor + remainder = int(number) + if remainder != 1: + parts.append(remainder) + return " * ".join([str(p) for p in parts]) + @classmethod def to_standard_engineering_notation(cls, number: float) -> str: base, exp = cls.to_standard_or_engineering_base(number) @@ -652,7 +671,9 @@ def _check_bad_float_notation( # pylint: disable=too-many-locals or self.linter.config.strict_underscore_notation ) - def raise_bad_float_notation(reason: str) -> None: + def raise_bad_float_notation( + reason: str, time_suggestion: bool = False + ) -> None: suggested = set() if scientific: suggested.add(self.to_standard_scientific_notation(value)) @@ -660,6 +681,10 @@ def raise_bad_float_notation(reason: str) -> None: suggested.add(self.to_standard_engineering_notation(value)) if pep515: suggested.add(self.to_standard_underscore_grouping(value)) + if time_suggestion: + maybe_a_time = self.to_understandable_time(value) + if maybe_a_time: + suggested.add(maybe_a_time) return self.add_message( "bad-float-notation", args=(string, reason, "' or '".join(sorted(suggested))), @@ -708,10 +733,12 @@ def raise_bad_float_notation(reason: str) -> None: 1 / threshold ) threshold = self.to_standard_scientific_notation(threshold) + if under_threshold: + return raise_bad_float_notation( + f"is smaller than {close_to_zero_threshold}" + ) return raise_bad_float_notation( - f"is smaller than {close_to_zero_threshold}" - if under_threshold - else f"is bigger than {threshold}" + f"is bigger than {threshold}", time_suggestion=True ) if has_exponent: if self.linter.config.strict_underscore_notation or has_underscore: diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 502fc3c175..f8b6e87da5 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -213,7 +213,7 @@ def test_disable_global_option_end_of_line() -> None: ("20e-9", "2e-8", "20e-9", "2e-08"), # 2e-08 is what python offer on str(float) ( # 15 significant digits because we get rounding error otherwise - # and 15 seems enough especially since we don't autofix + # and 15 seems enough especially since we don't auto-fix "10_5415_456_465498.16354698489", "1.05415456465498e14", "105.415456465498e12", diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index b2d38e4a69..a5bb17ab40 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -132,3 +132,11 @@ def function_with_underscore(param=10.0_0e3, other_param=20.0_0e3): if i < 0: continue print("Let's not be really annoying.") + + +#+3: [bad-float-notation] +#+3: [bad-float-notation] +#+3: [bad-float-notation] +time_in_s_since_two_week_ago_at_16 = 1180800 # 2 * 24 * 3600 - (8 * 3600) +time_in_s_since_last_year = 31536000 # 365 * 24 * 3600 +time_in_s_since_last_month = 2592000 # 30 * 24 * 3600 diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index 393bdfd4ab..5db4d48a5a 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -53,3 +53,6 @@ bad-float-notation:121:21:121:28::'1.5_6e3' has exponent and underscore at the s bad-float-notation:122:27:122:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH bad-float-notation:125:35:125:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH bad-float-notation:125:57:125:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH +bad-float-notation:140:37:140:44::'1180800' is bigger than 1e6, and it should be written as '1.1808e6' or '1_180_800.0' or '3600 * 328' instead:HIGH +bad-float-notation:141:28:141:36::'31536000' is bigger than 1e6, and it should be written as '3.1536e7' or '31.536e6' or '31_536_000.0' or '3600 * 24 * 365' instead:HIGH +bad-float-notation:142:29:142:36::'2592000' is bigger than 1e6, and it should be written as '2.592e6' or '2_592_000.0' or '3600 * 24 * 30' instead:HIGH From 2cbc302a6f344da096f79b084704e41a04259628 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sat, 21 Jun 2025 22:27:51 +0200 Subject: [PATCH 10/15] Higher default value for the threshold --- pylint/checkers/format.py | 2 +- tests/functional/b/bad_float/bad_float_engineering_notation.rc | 1 + tests/functional/b/bad_float/bad_float_notation_default.rc | 1 + tests/functional/b/bad_float/bad_float_pep515.rc | 1 + tests/functional/b/bad_float/bad_float_scientific_notation.rc | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 15855dc3c3..c56dce7cf8 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -261,7 +261,7 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker): { # default big enough to not trigger on pixel perfect web design # on big screen - "default": "1e6", + "default": "1e8", "type": "float", "metavar": "", "help": ( diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.rc b/tests/functional/b/bad_float/bad_float_engineering_notation.rc index 0aeb017a55..26a95a2e25 100644 --- a/tests/functional/b/bad_float/bad_float_engineering_notation.rc +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.rc @@ -2,3 +2,4 @@ strict-engineering-notation = true strict-scientific-notation = false strict-underscore-notation = false +float-notation-threshold=1e6 diff --git a/tests/functional/b/bad_float/bad_float_notation_default.rc b/tests/functional/b/bad_float/bad_float_notation_default.rc index dc39dc8f42..d9d87684ea 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.rc +++ b/tests/functional/b/bad_float/bad_float_notation_default.rc @@ -2,3 +2,4 @@ strict-engineering-notation = false strict-scientific-notation = false strict-underscore-notation = false +float-notation-threshold=1e6 diff --git a/tests/functional/b/bad_float/bad_float_pep515.rc b/tests/functional/b/bad_float/bad_float_pep515.rc index 7dd3df4483..d5ab40983f 100644 --- a/tests/functional/b/bad_float/bad_float_pep515.rc +++ b/tests/functional/b/bad_float/bad_float_pep515.rc @@ -2,3 +2,4 @@ strict-engineering-notation = false strict-scientific-notation = false strict-underscore-notation = true +float-notation-threshold=1e6 diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.rc b/tests/functional/b/bad_float/bad_float_scientific_notation.rc index 7620420d59..217d0ce9ff 100644 --- a/tests/functional/b/bad_float/bad_float_scientific_notation.rc +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.rc @@ -2,3 +2,4 @@ strict-engineering-notation = false strict-scientific-notation = true strict-underscore-notation = false +float-notation-threshold=1e6 From 399f6bd30dab25a29a010b7fe89ad26923975bcd Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 22 Jun 2025 13:24:33 +0200 Subject: [PATCH 11/15] [refactor] Create a float formatter helper class --- pylint/checkers/format.py | 249 ++++++++++++++++-------------- tests/checkers/unittest_format.py | 12 +- 2 files changed, 141 insertions(+), 120 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index c56dce7cf8..614bc68043 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -53,6 +53,132 @@ _JUNK_TOKENS = {tokenize.COMMENT, tokenize.NL} +class FloatFormatterHelper: + + @classmethod + def standardize( + cls, + number: float, + scientific: bool = True, + engineering: bool = True, + pep515: bool = True, + time_suggestion: bool = False, + ) -> str: + suggested = set() + if scientific: + suggested.add(cls.to_standard_scientific_notation(number)) + if engineering: + suggested.add(cls.to_standard_engineering_notation(number)) + if pep515: + suggested.add(cls.to_standard_underscore_grouping(number)) + if time_suggestion: + maybe_a_time = cls.to_understandable_time(number) + if maybe_a_time: + suggested.add(maybe_a_time) + return "' or '".join(sorted(suggested)) + + @classmethod + def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]: + """Calculate scientific notation components (base, exponent) for a number. + + Returns a tuple (base, exponent) where: + - base is a number between 1 and 10 (or exact 0) + - exponent is the power of 10 needed to represent the original number + """ + if number == 0: + return "0", "0" + if number == math.inf: + return "math.inf", "0" + exponent = math.floor(math.log10(abs(number))) + if exponent == 0: + return str(number), "0" + base_value = number / (10**exponent) + # 15 significant digits because if we add more precision then + # we get into rounding errors territory + base_str = f"{base_value:.15g}".rstrip("0").rstrip(".") + exp_str = str(exponent) + return base_str, exp_str + + @classmethod + def to_standard_scientific_notation(cls, number: float) -> str: + base, exp = cls.to_standard_or_engineering_base(number) + if base == "math.inf": + return "math.inf" + if exp != "0": + return f"{base}e{int(exp)}" + if "." in base: + return base + return f"{base}.0" + + @classmethod + def to_understandable_time(cls, number: float) -> str: + if number == 0.0 or number % 3600 != 0: + return "" # Not a suspected time + parts: list[int] = [3600] + number //= 3600 + for divisor in ( + 24, + 7, + 365, + ): + if number % divisor == 0: + parts.append(divisor) + number //= divisor + remainder = int(number) + if remainder != 1: + parts.append(remainder) + return " * ".join([str(p) for p in parts]) + + @classmethod + def to_standard_engineering_notation(cls, number: float) -> str: + base, exp = cls.to_standard_or_engineering_base(number) + if base == "math.inf": + return "math.inf" + exp_value = int(exp) + remainder = exp_value % 3 + # For negative exponents, the adjustment is different + if exp_value < 0: + # For negative exponents, we need to round down to the next multiple of 3 + # e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2 + adjustment = 3 - ((-exp_value) % 3) + if adjustment == 3: + adjustment = 0 + exp_value = exp_value - adjustment + base_value = float(base) * (10**adjustment) + elif remainder != 0: + # For positive exponents, keep the existing logic + exp_value = exp_value - remainder + base_value = float(base) * (10**remainder) + else: + base_value = float(base) + base = str(base_value).rstrip("0").rstrip(".") + if exp_value != 0: + return f"{base}e{exp_value}" + if "." in base: + return base + return f"{base}.0" + + @classmethod + def to_standard_underscore_grouping(cls, number: float) -> str: + number_str = str(number) + if "e" in number_str or "E" in number_str: + # python itself want to display this as exponential there's no reason to + # not use exponential notation for very small number even for strict + # underscore grouping notation + return number_str + if "." in number_str: + int_part, dec_part = number_str.split(".") + else: + int_part = number_str + dec_part = "0" + grouped_int_part = "" + for i, digit in enumerate(reversed(int_part)): + if i > 0 and i % 3 == 0: + grouped_int_part = "_" + grouped_int_part + grouped_int_part = digit + grouped_int_part + return f"{grouped_int_part}.{dec_part}" + + MSGS: dict[str, MessageDefinitionTuple] = { "C0301": ( "Line too long (%s/%s)", @@ -553,107 +679,6 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: if line_num == last_blank_line_num and line_num > 0: self.add_message("trailing-newlines", line=line_num) - @classmethod - def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]: - """Calculate scientific notation components (base, exponent) for a number. - - Returns a tuple (base, exponent) where: - - base is a number between 1 and 10 (or exact 0) - - exponent is the power of 10 needed to represent the original number - """ - if number == 0: - return "0", "0" - if number == math.inf: - return "math.inf", "0" - exponent = math.floor(math.log10(abs(number))) - if exponent == 0: - return str(number), "0" - base_value = number / (10**exponent) - # 15 significant digits because if we add more precision then - # we get into rounding errors territory - base_str = f"{base_value:.15g}".rstrip("0").rstrip(".") - exp_str = str(exponent) - return base_str, exp_str - - @classmethod - def to_standard_scientific_notation(cls, number: float) -> str: - base, exp = cls.to_standard_or_engineering_base(number) - if base == "math.inf": - return "math.inf" - if exp != "0": - return f"{base}e{int(exp)}" - if "." in base: - return base - return f"{base}.0" - - @classmethod - def to_understandable_time(cls, number: float) -> str: - if number % 3600 != 0: - return "" # Not a suspected time - parts: list[int] = [3600] - number //= 3600 - for divisor in ( - 24, - 7, - 365, - ): - if number % divisor == 0: - parts.append(divisor) - number //= divisor - remainder = int(number) - if remainder != 1: - parts.append(remainder) - return " * ".join([str(p) for p in parts]) - - @classmethod - def to_standard_engineering_notation(cls, number: float) -> str: - base, exp = cls.to_standard_or_engineering_base(number) - if base == "math.inf": - return "math.inf" - exp_value = int(exp) - remainder = exp_value % 3 - # For negative exponents, the adjustment is different - if exp_value < 0: - # For negative exponents, we need to round down to the next multiple of 3 - # e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2 - adjustment = 3 - ((-exp_value) % 3) - if adjustment == 3: - adjustment = 0 - exp_value = exp_value - adjustment - base_value = float(base) * (10**adjustment) - elif remainder != 0: - # For positive exponents, keep the existing logic - exp_value = exp_value - remainder - base_value = float(base) * (10**remainder) - else: - base_value = float(base) - base = str(base_value).rstrip("0").rstrip(".") - if exp_value != 0: - return f"{base}e{exp_value}" - if "." in base: - return base - return f"{base}.0" - - @classmethod - def to_standard_underscore_grouping(cls, number: float) -> str: - number_str = str(number) - if "e" in number_str or "E" in number_str: - # python itself want to display this as exponential there's no reason to - # not use exponential notation for very small number even for strict - # underscore grouping notation - return number_str - if "." in number_str: - int_part, dec_part = number_str.split(".") - else: - int_part = number_str - dec_part = "0" - grouped_int_part = "" - for i, digit in enumerate(reversed(int_part)): - if i > 0 and i % 3 == 0: - grouped_int_part = "_" + grouped_int_part - grouped_int_part = digit + grouped_int_part - return f"{grouped_int_part}.{dec_part}" - def _check_bad_float_notation( # pylint: disable=too-many-locals self, line_num: int, start: tuple[int, int], string: str ) -> None: @@ -674,20 +699,12 @@ def _check_bad_float_notation( # pylint: disable=too-many-locals def raise_bad_float_notation( reason: str, time_suggestion: bool = False ) -> None: - suggested = set() - if scientific: - suggested.add(self.to_standard_scientific_notation(value)) - if engineering: - suggested.add(self.to_standard_engineering_notation(value)) - if pep515: - suggested.add(self.to_standard_underscore_grouping(value)) - if time_suggestion: - maybe_a_time = self.to_understandable_time(value) - if maybe_a_time: - suggested.add(maybe_a_time) + suggestion = FloatFormatterHelper.standardize( + value, scientific, engineering, pep515, time_suggestion + ) return self.add_message( "bad-float-notation", - args=(string, reason, "' or '".join(sorted(suggested))), + args=(string, reason, suggestion), line=line_num, end_lineno=line_num, col_offset=start[1], @@ -729,10 +746,10 @@ def raise_bad_float_notation( # written complexly, then it could be badly written return None threshold = self.linter.config.float_notation_threshold - close_to_zero_threshold = self.to_standard_scientific_notation( - 1 / threshold + close_to_zero_threshold = ( + FloatFormatterHelper.to_standard_scientific_notation(1 / threshold) ) - threshold = self.to_standard_scientific_notation(threshold) + threshold = FloatFormatterHelper.to_standard_scientific_notation(threshold) if under_threshold: return raise_bad_float_notation( f"is smaller than {close_to_zero_threshold}" diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index f8b6e87da5..9b4d7f0cc7 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -13,7 +13,7 @@ from pylint import lint, reporters from pylint.checkers.base.basic_checker import BasicChecker -from pylint.checkers.format import FormatChecker +from pylint.checkers.format import FloatFormatterHelper, FormatChecker from pylint.testutils import CheckerTestCase, MessageTest, _tokenize_str @@ -229,15 +229,19 @@ def test_to_another_standard_notation( ) -> None: """Test the conversion of numbers to all possible notations.""" float_value = float(value) - scientific = FormatChecker.to_standard_scientific_notation(float_value) + scientific = FloatFormatterHelper.to_standard_scientific_notation(float_value) assert ( scientific == expected_scientific ), f"Scientific notation mismatch expected {expected_scientific}, got {scientific}" - engineering = FormatChecker.to_standard_engineering_notation(float_value) + engineering = FloatFormatterHelper.to_standard_engineering_notation(float_value) assert ( engineering == expected_engineering ), f"Engineering notation mismatch expected {expected_engineering}, got {engineering}" - underscore = FormatChecker.to_standard_underscore_grouping(float_value) + underscore = FloatFormatterHelper.to_standard_underscore_grouping(float_value) assert ( underscore == expected_underscore ), f"Underscore grouping mismatch expected {expected_underscore}, got {underscore}" + time = FloatFormatterHelper.to_understandable_time(float_value) + assert ( + time == "" + ), f"Time notation mismatch expected {expected_underscore}, got {time}" From eee086585038c6188e430f5d880b04c74093a2e2 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 22 Jun 2025 13:34:05 +0200 Subject: [PATCH 12/15] Do not raise on int --- pylint/checkers/format.py | 11 ++++++++--- .../bad_float/bad_float_engineering_notation.py | 4 ++-- .../bad_float/bad_float_engineering_notation.txt | 2 +- .../b/bad_float/bad_float_notation_default.py | 16 ++++++++-------- .../b/bad_float/bad_float_notation_default.txt | 16 ++++++++-------- tests/functional/b/bad_float/bad_float_pep515.py | 6 +++--- .../functional/b/bad_float/bad_float_pep515.txt | 4 ++-- .../b/bad_float/bad_float_scientific_notation.py | 2 +- .../bad_float/bad_float_scientific_notation.txt | 2 +- 9 files changed, 34 insertions(+), 29 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 614bc68043..6d982826cd 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -387,7 +387,7 @@ class FormatChecker(BaseTokenChecker, BaseRawFileChecker): { # default big enough to not trigger on pixel perfect web design # on big screen - "default": "1e8", + "default": "1e6", "type": "float", "metavar": "", "help": ( @@ -679,9 +679,15 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None: if line_num == last_blank_line_num and line_num > 0: self.add_message("trailing-newlines", line=line_num) - def _check_bad_float_notation( # pylint: disable=too-many-locals + def _check_bad_float_notation( # pylint: disable=too-many-locals,too-many-return-statements self, line_num: int, start: tuple[int, int], string: str ) -> None: + has_dot = "." in string + has_exponent = "e" in string or "E" in string + if not (has_dot or has_exponent): + # it's an int, need special treatment later on + return None + value = float(string) engineering = ( self.all_float_notation_allowed @@ -718,7 +724,6 @@ def raise_bad_float_notation( # engineering notation when checking if a number is under 1/threshold return None has_underscore = "_" in string - has_exponent = "e" in string or "E" in string should_be_written_simply = ( 1 <= value < 10 and self.linter.config.strict_scientific_notation ) or 1 <= value < 1000 diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.py b/tests/functional/b/bad_float/bad_float_engineering_notation.py index 0ed7d9b8b1..48f06936c9 100644 --- a/tests/functional/b/bad_float/bad_float_engineering_notation.py +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.py @@ -2,8 +2,8 @@ exponent_not_multiple_of_three = 123e4 # [bad-float-notation] base_not_between_one_and_a_thousand = 12345e6 # [bad-float-notation] -above_threshold_without_exponent = 10000000 # [bad-float-notation] +above_threshold_without_exponent = 10000000.0 # [bad-float-notation] under_a_thousand_with_exponent = 9.9e2 # [bad-float-notation] exponent_multiple_of_three = 1.23e6 base_between_one_and_a_thousand = 12.345e9 -under_a_thousand = 990 +under_a_thousand = 990.0 diff --git a/tests/functional/b/bad_float/bad_float_engineering_notation.txt b/tests/functional/b/bad_float/bad_float_engineering_notation.txt index 09b8cef7a4..7fd0e27e4f 100644 --- a/tests/functional/b/bad_float/bad_float_engineering_notation.txt +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.txt @@ -1,4 +1,4 @@ bad-float-notation:3:33:3:38::'123e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.23e6' instead:HIGH bad-float-notation:4:38:4:45::'12345e6' has a base, '12345.0', that is not between 1 and 1000, and it should be written as '12.344999999999999e9' instead:HIGH -bad-float-notation:5:35:5:43::'10000000' is bigger than 1e6, and it should be written as '10e6' instead:HIGH +bad-float-notation:5:35:5:45::'10000000.0' is bigger than 1e6, and it should be written as '10e6' instead:HIGH bad-float-notation:6:33:6:38::'9.9e2' has underscore or exponent, and it should be written as '990.0' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index a5bb17ab40..8d66dd8951 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -7,9 +7,9 @@ underscore_notation = 150_400_000 # Content of pep515 strict tests tested with default configuration -not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] +not_grouped_by_three = 1_23_456_7_89.0 # [bad-float-notation] mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] -above_threshold_without_grouping = 123456789 # [bad-float-notation] +above_threshold_without_grouping = 123456789.0 # [bad-float-notation] proper_grouping = 123_456_789 scientific_notation_2 = 1.2345678e16 engineering_notation_2 = 12.345678e15 @@ -17,7 +17,7 @@ # Content of bad_float_engineering_notation.py strict tests tested with default configuration exponent_not_multiple_of_three = 123e4 # [bad-float-notation] base_not_between_one_and_a_thousand = 12345e6 # [bad-float-notation] -above_threshold_without_exponent = 10000000 # [bad-float-notation] +above_threshold_without_exponent = 10000000.0 # [bad-float-notation] under_a_thousand_with_exponent = 9.9e2 # [bad-float-notation] exponent_multiple_of_three = 1.23e6 base_between_one_and_a_thousand = 12.345e9 @@ -25,7 +25,7 @@ # Content of bad_float_scientific_notation strict tests tested with default configuration base_not_between_one_and_ten = 10e3 -above_threshold_without_exponent_2 = 10000000 # [bad-float-notation] +above_threshold_without_exponent_2 = 10000000.0 # [bad-float-notation] under_ten_with_exponent = 9.9e0 # [bad-float-notation] base_between_one_and_ten = 1e4 above_threshold_with_exponent = 1e7 @@ -46,7 +46,7 @@ zero_only = 0e10 # [bad-float-notation] zero_int = 0 zero_float = 0.0 -annoying_zero = 00 # [bad-float-notation] +annoying_zero = 00.0 # [bad-float-notation] another_annoying_zero = 0. # [bad-float-notation] one_only = 1e6 @@ -137,6 +137,6 @@ def function_with_underscore(param=10.0_0e3, other_param=20.0_0e3): #+3: [bad-float-notation] #+3: [bad-float-notation] #+3: [bad-float-notation] -time_in_s_since_two_week_ago_at_16 = 1180800 # 2 * 24 * 3600 - (8 * 3600) -time_in_s_since_last_year = 31536000 # 365 * 24 * 3600 -time_in_s_since_last_month = 2592000 # 30 * 24 * 3600 +time_in_s_since_two_week_ago_at_16 = 1180800.0 # 2 * 24 * 3600 - (8 * 3600) +time_in_s_since_last_year = 31536000.0 # 365 * 24 * 3600 +time_in_s_since_last_month = 2592000.0 # 30 * 24 * 3600 diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index 5db4d48a5a..61991ac365 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -1,12 +1,12 @@ bad-float-notation:4:19:4:25::'1504e5' has a base, '1504.0', that is not between 1 and 1000, and it should be written as '1.504e8' or '150.4e6' or '150_400_000.0' instead:HIGH -bad-float-notation:10:23:10:36::'1_23_456_7_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH +bad-float-notation:10:23:10:38::'1_23_456_7_89.0' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH bad-float-notation:11:23:11:38::'1_23_4_5_67_8e9' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' or '1.2345678e16' or '12.345678e15' instead:HIGH -bad-float-notation:12:35:12:44::'123456789' is bigger than 1e6, and it should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH +bad-float-notation:12:35:12:46::'123456789.0' is bigger than 1e6, and it should be written as '1.23456789e8' or '123.45678899999999e6' or '123_456_789.0' instead:HIGH bad-float-notation:18:33:18:38::'123e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.23e6' or '1_230_000.0' instead:HIGH bad-float-notation:19:38:19:45::'12345e6' has a base, '12345.0', that is not between 1 and 1000, and it should be written as '1.2345e10' or '12.344999999999999e9' or '12_345_000_000.0' instead:HIGH -bad-float-notation:20:35:20:43::'10000000' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:20:35:20:45::'10000000.0' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH bad-float-notation:21:33:21:38::'9.9e2' has underscore or exponent, and it should be written as '9.9e2' or '990.0' instead:HIGH -bad-float-notation:28:37:28:45::'10000000' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:28:37:28:47::'10000000.0' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH bad-float-notation:29:26:29:31::'9.9e0' has underscore or exponent, and it should be written as '9.9' instead:HIGH bad-float-notation:35:12:35:18::'45.3e7' has an exponent '7' that is not a multiple of 3, and it should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH bad-float-notation:36:20:36:26::'45.3E7' has an exponent '7' that is not a multiple of 3, and it should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH @@ -19,7 +19,7 @@ bad-float-notation:43:28:43:37::'0.0001e-5' has a base, '0.0001', that is not be bad-float-notation:44:26:44:34::'0.0001e5' has underscore or exponent, and it should be written as '10.0' or '1e1' instead:HIGH bad-float-notation:45:20:45:26::'0.5e10' has a base, '0.5', that is not between 1 and 1000, and it should be written as '5_000_000_000.0' or '5e9' instead:HIGH bad-float-notation:46:12:46:16::'0e10' has a base, '0.0', that is not between 1 and 1000, and it should be written as '0.0' instead:HIGH -bad-float-notation:49:16:49:18::'00' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH +bad-float-notation:49:16:49:20::'00.0' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH bad-float-notation:50:24:50:26::'0.' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH bad-float-notation:64:25:64:30::'1.5e1' has underscore or exponent, and it should be written as '1.5e1' or '15.0' instead:HIGH bad-float-notation:65:16:65:19::'9e0' has underscore or exponent, and it should be written as '9.0' instead:HIGH @@ -53,6 +53,6 @@ bad-float-notation:121:21:121:28::'1.5_6e3' has exponent and underscore at the s bad-float-notation:122:27:122:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH bad-float-notation:125:35:125:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH bad-float-notation:125:57:125:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH -bad-float-notation:140:37:140:44::'1180800' is bigger than 1e6, and it should be written as '1.1808e6' or '1_180_800.0' or '3600 * 328' instead:HIGH -bad-float-notation:141:28:141:36::'31536000' is bigger than 1e6, and it should be written as '3.1536e7' or '31.536e6' or '31_536_000.0' or '3600 * 24 * 365' instead:HIGH -bad-float-notation:142:29:142:36::'2592000' is bigger than 1e6, and it should be written as '2.592e6' or '2_592_000.0' or '3600 * 24 * 30' instead:HIGH +bad-float-notation:140:37:140:46::'1180800.0' is bigger than 1e6, and it should be written as '1.1808e6' or '1_180_800.0' or '3600 * 328' instead:HIGH +bad-float-notation:141:28:141:38::'31536000.0' is bigger than 1e6, and it should be written as '3.1536e7' or '31.536e6' or '31_536_000.0' or '3600 * 24 * 365' instead:HIGH +bad-float-notation:142:29:142:38::'2592000.0' is bigger than 1e6, and it should be written as '2.592e6' or '2_592_000.0' or '3600 * 24 * 30' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_pep515.py b/tests/functional/b/bad_float/bad_float_pep515.py index 00ba8b6c6e..b32ed09994 100644 --- a/tests/functional/b/bad_float/bad_float_pep515.py +++ b/tests/functional/b/bad_float/bad_float_pep515.py @@ -1,11 +1,11 @@ # pylint: disable=missing-docstring,invalid-name -not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] +not_grouped_by_three = 1_23_456_7_89.0 # [bad-float-notation] mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] -above_threshold_without_grouping = 123456789 # [bad-float-notation] +above_threshold_without_grouping = 123456789.0 # [bad-float-notation] scientific_notation = 1.2345678e16 # [bad-float-notation] engineering_notation = 12.345678e15 # [bad-float-notation] -proper_grouping = 123_456_789 +proper_grouping = 123_456_789.0 int_under_ten = 9 diff --git a/tests/functional/b/bad_float/bad_float_pep515.txt b/tests/functional/b/bad_float/bad_float_pep515.txt index 5f9cf6de71..71fbb3a6d8 100644 --- a/tests/functional/b/bad_float/bad_float_pep515.txt +++ b/tests/functional/b/bad_float/bad_float_pep515.txt @@ -1,5 +1,5 @@ -bad-float-notation:3:23:3:36::'1_23_456_7_89' has underscores that are not delimiting packs of three digits, and it should be written as '123_456_789.0' instead:HIGH +bad-float-notation:3:23:3:38::'1_23_456_7_89.0' has underscores that are not delimiting packs of three digits, and it should be written as '123_456_789.0' instead:HIGH bad-float-notation:4:23:4:38::'1_23_4_5_67_8e9' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' instead:HIGH -bad-float-notation:5:35:5:44::'123456789' is bigger than 1e6, and it should be written as '123_456_789.0' instead:HIGH +bad-float-notation:5:35:5:46::'123456789.0' is bigger than 1e6, and it should be written as '123_456_789.0' instead:HIGH bad-float-notation:6:22:6:34::'1.2345678e16' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' instead:HIGH bad-float-notation:7:23:7:35::'12.345678e15' has exponent and underscore at the same time, and it should be written as '1.2345678e+16' instead:HIGH diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.py b/tests/functional/b/bad_float/bad_float_scientific_notation.py index 3e337a7cd4..fff5a50a65 100644 --- a/tests/functional/b/bad_float/bad_float_scientific_notation.py +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.py @@ -1,7 +1,7 @@ # pylint: disable=missing-docstring,invalid-name base_not_between_one_and_ten = 10e3 # [bad-float-notation] -above_threshold_without_exponent = 10000000 # [bad-float-notation] +above_threshold_without_exponent = 10000000.0 # [bad-float-notation] under_ten_with_exponent = 9.9e0 # [bad-float-notation] base_between_one_and_ten = 1e4 above_threshold_with_exponent = 1e7 diff --git a/tests/functional/b/bad_float/bad_float_scientific_notation.txt b/tests/functional/b/bad_float/bad_float_scientific_notation.txt index eb03b7f40e..92e1f997b6 100644 --- a/tests/functional/b/bad_float/bad_float_scientific_notation.txt +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.txt @@ -1,3 +1,3 @@ bad-float-notation:3:31:3:35::'10e3' has a base, '10.0', that is not strictly inferior to 10, and it should be written as '1e4' instead:HIGH -bad-float-notation:4:35:4:43::'10000000' is bigger than 1e6, and it should be written as '1e7' instead:HIGH +bad-float-notation:4:35:4:45::'10000000.0' is bigger than 1e6, and it should be written as '1e7' instead:HIGH bad-float-notation:5:26:5:31::'9.9e0' has underscore or exponent, and it should be written as '9.9' instead:HIGH From 292fc366ecddbb827a7cbb9ac54e22557a111b57 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 22 Jun 2025 17:38:28 +0200 Subject: [PATCH 13/15] Special case 0. too --- pylint/checkers/format.py | 2 +- .../b/bad_float/bad_float_notation_default.py | 5 +- .../bad_float/bad_float_notation_default.txt | 73 +++++++++---------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 6d982826cd..9a7de9b065 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -718,7 +718,7 @@ def raise_bad_float_notation( confidence=HIGH, ) - if string in {"0", "0.0"}: + if string in {"0", "0.0", "0."}: # 0 is a special case because it is used very often, and float approximation # being what they are it needs to be special cased anyway for scientific and # engineering notation when checking if a number is under 1/threshold diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index 8d66dd8951..c959a1a98f 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -46,8 +46,9 @@ zero_only = 0e10 # [bad-float-notation] zero_int = 0 zero_float = 0.0 -annoying_zero = 00.0 # [bad-float-notation] -another_annoying_zero = 0. # [bad-float-notation] +zero_float_v2 = 0. +annoying_zero_int = 00 +annoying_zero_float = 00.0 # [bad-float-notation] one_only = 1e6 correct_1 = 4.53e7 diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index 61991ac365..ba7eb37ce7 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -19,40 +19,39 @@ bad-float-notation:43:28:43:37::'0.0001e-5' has a base, '0.0001', that is not be bad-float-notation:44:26:44:34::'0.0001e5' has underscore or exponent, and it should be written as '10.0' or '1e1' instead:HIGH bad-float-notation:45:20:45:26::'0.5e10' has a base, '0.5', that is not between 1 and 1000, and it should be written as '5_000_000_000.0' or '5e9' instead:HIGH bad-float-notation:46:12:46:16::'0e10' has a base, '0.0', that is not between 1 and 1000, and it should be written as '0.0' instead:HIGH -bad-float-notation:49:16:49:20::'00.0' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH -bad-float-notation:50:24:50:26::'0.' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH -bad-float-notation:64:25:64:30::'1.5e1' has underscore or exponent, and it should be written as '1.5e1' or '15.0' instead:HIGH -bad-float-notation:65:16:65:19::'9e0' has underscore or exponent, and it should be written as '9.0' instead:HIGH -bad-float-notation:66:15:66:20::'1.0e0' has underscore or exponent, and it should be written as '1.0' instead:HIGH -bad-float-notation:82:23:82:27::'15e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH -bad-float-notation:87:28:87:34::'10.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH -bad-float-notation:87:48:87:54::'20.0e5' has an exponent '5' that is not a multiple of 3, and it should be written as '2_000_000.0' or '2e6' instead:HIGH -bad-float-notation:91:35:91:41::'10.0e7' has an exponent '7' that is not a multiple of 3, and it should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH -bad-float-notation:91:27:91:33::'20.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH -bad-float-notation:95:29:95:57::'123_000_000.12345e12_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH -bad-float-notation:96:33:96:62::'123_000_000.12345E123_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH -bad-float-notation:101:34:101:42::'.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH -bad-float-notation:102:35:102:50::'123_456.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH -bad-float-notation:103:27:103:38::'1.234_567e6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH -bad-float-notation:104:26:104:37::'1.234_567E6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH -bad-float-notation:105:31:105:38::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH -bad-float-notation:106:27:106:39::'1_234.567_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e3' or '1_234.56789' instead:HIGH -bad-float-notation:107:23:107:32::'45.3_45e6' has exponent and underscore at the same time, and it should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH -bad-float-notation:108:25:108:37::'0.000_12e-26' has exponent and underscore at the same time, and it should be written as '1.2e-30' instead:HIGH -bad-float-notation:109:37:109:42::'1_2e8' has exponent and underscore at the same time, and it should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH -bad-float-notation:110:37:110:43::'12_3e3' has exponent and underscore at the same time, and it should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH -bad-float-notation:111:25:111:40::'1_234.567_89e10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH -bad-float-notation:112:29:112:36::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH -bad-float-notation:113:34:113:45::'1_2.3_4e5_6' has exponent and underscore at the same time, and it should be written as '1.234e+57' or '1.234e57' instead:HIGH -bad-float-notation:114:24:114:39::'1_234.567_89E10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH -bad-float-notation:115:20:115:25::'1_0e6' has exponent and underscore at the same time, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH -bad-float-notation:116:21:116:35::'1_000_000.0e-3' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH -bad-float-notation:117:21:117:32::'0.000_001e3' has exponent and underscore at the same time, and it should be written as '0.001' or '1e-3' instead:HIGH -bad-float-notation:118:21:118:28::'1_0.0e2' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH -bad-float-notation:121:21:121:28::'1.5_6e3' has exponent and underscore at the same time, and it should be written as '1.56e3' or '1_560.0' instead:HIGH -bad-float-notation:122:27:122:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH -bad-float-notation:125:35:125:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH -bad-float-notation:125:57:125:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH -bad-float-notation:140:37:140:46::'1180800.0' is bigger than 1e6, and it should be written as '1.1808e6' or '1_180_800.0' or '3600 * 328' instead:HIGH -bad-float-notation:141:28:141:38::'31536000.0' is bigger than 1e6, and it should be written as '3.1536e7' or '31.536e6' or '31_536_000.0' or '3600 * 24 * 365' instead:HIGH -bad-float-notation:142:29:142:38::'2592000.0' is bigger than 1e6, and it should be written as '2.592e6' or '2_592_000.0' or '3600 * 24 * 30' instead:HIGH +bad-float-notation:51:22:51:26::'00.0' is smaller than 1e-6, and it should be written as '0.0' instead:HIGH +bad-float-notation:65:25:65:30::'1.5e1' has underscore or exponent, and it should be written as '1.5e1' or '15.0' instead:HIGH +bad-float-notation:66:16:66:19::'9e0' has underscore or exponent, and it should be written as '9.0' instead:HIGH +bad-float-notation:67:15:67:20::'1.0e0' has underscore or exponent, and it should be written as '1.0' instead:HIGH +bad-float-notation:83:23:83:27::'15e4' has an exponent '4' that is not a multiple of 3, and it should be written as '1.5e5' or '150_000.0' or '150e3' instead:HIGH +bad-float-notation:88:28:88:34::'10.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '100_000.0' or '100e3' or '1e5' instead:HIGH +bad-float-notation:88:48:88:54::'20.0e5' has an exponent '5' that is not a multiple of 3, and it should be written as '2_000_000.0' or '2e6' instead:HIGH +bad-float-notation:92:35:92:41::'10.0e7' has an exponent '7' that is not a multiple of 3, and it should be written as '100_000_000.0' or '100e6' or '1e8' instead:HIGH +bad-float-notation:92:27:92:33::'20.0e4' has an exponent '4' that is not a multiple of 3, and it should be written as '200_000.0' or '200e3' or '2e5' instead:HIGH +bad-float-notation:96:29:96:57::'123_000_000.12345e12_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:97:33:97:62::'123_000_000.12345E123_000_000' has exponent and underscore at the same time, and it should be written as 'inf.0' or 'math.inf' instead:HIGH +bad-float-notation:102:34:102:42::'.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '0.123456' or '1.23456e-1' or '123.45600000000002e-3' instead:HIGH +bad-float-notation:103:35:103:50::'123_456.123_456' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456123456e5' or '123.45612345600001e3' or '123_456.123456' instead:HIGH +bad-float-notation:104:27:104:38::'1.234_567e6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:105:26:105:37::'1.234_567E6' has exponent and underscore at the same time, and it should be written as '1.234567e6' or '1_234_567.0' instead:HIGH +bad-float-notation:106:31:106:38::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:107:27:107:39::'1_234.567_89' has underscores that are not delimiting packs of three digits, and it should be written as '1.23456789e3' or '1_234.56789' instead:HIGH +bad-float-notation:108:23:108:32::'45.3_45e6' has exponent and underscore at the same time, and it should be written as '4.5345e7' or '45.345000000000006e6' or '45_345_000.0' instead:HIGH +bad-float-notation:109:25:109:37::'0.000_12e-26' has exponent and underscore at the same time, and it should be written as '1.2e-30' instead:HIGH +bad-float-notation:110:37:110:42::'1_2e8' has exponent and underscore at the same time, and it should be written as '1.2e9' or '1_200_000_000.0' instead:HIGH +bad-float-notation:111:37:111:43::'12_3e3' has exponent and underscore at the same time, and it should be written as '1.23e5' or '123_000.0' or '123e3' instead:HIGH +bad-float-notation:112:25:112:40::'1_234.567_89e10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:113:29:113:36::'1.2e1_0' has exponent and underscore at the same time, and it should be written as '1.2e10' or '12_000_000_000.0' or '12e9' instead:HIGH +bad-float-notation:114:34:114:45::'1_2.3_4e5_6' has exponent and underscore at the same time, and it should be written as '1.234e+57' or '1.234e57' instead:HIGH +bad-float-notation:115:24:115:39::'1_234.567_89E10' has exponent and underscore at the same time, and it should be written as '1.23456789e13' or '12.3456789e12' or '12_345_678_900_000.0' instead:HIGH +bad-float-notation:116:20:116:25::'1_0e6' has exponent and underscore at the same time, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH +bad-float-notation:117:21:117:35::'1_000_000.0e-3' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:118:21:118:32::'0.000_001e3' has exponent and underscore at the same time, and it should be written as '0.001' or '1e-3' instead:HIGH +bad-float-notation:119:21:119:28::'1_0.0e2' has exponent and underscore at the same time, and it should be written as '1_000.0' or '1e3' instead:HIGH +bad-float-notation:122:21:122:28::'1.5_6e3' has exponent and underscore at the same time, and it should be written as '1.56e3' or '1_560.0' instead:HIGH +bad-float-notation:123:27:123:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH +bad-float-notation:126:35:126:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH +bad-float-notation:126:57:126:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH +bad-float-notation:141:37:141:46::'1180800.0' is bigger than 1e6, and it should be written as '1.1808e6' or '1_180_800.0' or '3600 * 328' instead:HIGH +bad-float-notation:142:28:142:38::'31536000.0' is bigger than 1e6, and it should be written as '3.1536e7' or '31.536e6' or '31_536_000.0' or '3600 * 24 * 365' instead:HIGH +bad-float-notation:143:29:143:38::'2592000.0' is bigger than 1e6, and it should be written as '2.592e6' or '2_592_000.0' or '3600 * 24 * 30' instead:HIGH From 53749406d66f4e03d691376975337231dfdab3f1 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 22 Jun 2025 17:49:56 +0200 Subject: [PATCH 14/15] Handle the case where the number is just 3600 better --- pylint/checkers/format.py | 8 ++------ tests/checkers/unittest_format.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 9a7de9b065..a9cc737431 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -112,15 +112,11 @@ def to_standard_scientific_notation(cls, number: float) -> str: @classmethod def to_understandable_time(cls, number: float) -> str: - if number == 0.0 or number % 3600 != 0: + if number == 0.0 or number % 3600 != 0 or number // 3600 == 1: return "" # Not a suspected time parts: list[int] = [3600] number //= 3600 - for divisor in ( - 24, - 7, - 365, - ): + for divisor in (24, 7, 365): if number % divisor == 0: parts.append(divisor) number //= divisor diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 9b4d7f0cc7..50d4f26a04 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -245,3 +245,20 @@ def test_to_another_standard_notation( assert ( time == "" ), f"Time notation mismatch expected {expected_underscore}, got {time}" + + +@pytest.mark.parametrize( + "value,expected", + [ + (3600, ""), + (86400, "3600 * 24"), + (604800, "3600 * 24 * 7"), + (2592000, "3600 * 24 * 30"), + (31536000, "3600 * 24 * 365"), + (1180800, "3600 * 24 * 14 - (8 * 3600)"), + (315360000, "3600 * 24 * 365 * 10"), + ], +) +def test_to_understandable_time(value: int, expected: str) -> None: + actual = FloatFormatterHelper.to_understandable_time(value) + assert actual == expected, f"Expected {expected} for {value}, got {actual}" From dc7592dd9c02812523de9fe097c12f3ced5ba743 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 22 Jun 2025 21:51:39 +0200 Subject: [PATCH 15/15] Handle the case where the number is a number of hours in second better --- doc/data/messages/b/bad-float-notation/bad.py | 5 ++ .../messages/b/bad-float-notation/bad/bad.py | 1 - .../bad/engineering_notation.py | 4 -- .../bad/engineering_notation.rc | 2 - .../b/bad-float-notation/bad/pep515.py | 3 -- .../b/bad-float-notation/bad/pep515.rc | 2 - .../bad/scientific_notation.py | 3 -- .../bad/scientific_notation.rc | 2 - .../messages/b/bad-float-notation/good.py | 7 +++ .../good/engineering_notation.py | 3 -- .../b/bad-float-notation/good/good.py | 3 -- .../b/bad-float-notation/good/pep515.py | 1 - .../good/scientific_notation.py | 3 -- doc/user_guide/checkers/features.rst | 8 +-- pylint/checkers/format.py | 50 ++++++++++++++++--- tests/checkers/unittest_format.py | 21 +++++--- .../b/bad_float/bad_float_notation_default.py | 2 +- .../bad_float/bad_float_notation_default.txt | 3 +- 18 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 doc/data/messages/b/bad-float-notation/bad.py delete mode 100644 doc/data/messages/b/bad-float-notation/bad/bad.py delete mode 100644 doc/data/messages/b/bad-float-notation/bad/engineering_notation.py delete mode 100644 doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc delete mode 100644 doc/data/messages/b/bad-float-notation/bad/pep515.py delete mode 100644 doc/data/messages/b/bad-float-notation/bad/pep515.rc delete mode 100644 doc/data/messages/b/bad-float-notation/bad/scientific_notation.py delete mode 100644 doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc create mode 100644 doc/data/messages/b/bad-float-notation/good.py delete mode 100644 doc/data/messages/b/bad-float-notation/good/engineering_notation.py delete mode 100644 doc/data/messages/b/bad-float-notation/good/good.py delete mode 100644 doc/data/messages/b/bad-float-notation/good/pep515.py delete mode 100644 doc/data/messages/b/bad-float-notation/good/scientific_notation.py diff --git a/doc/data/messages/b/bad-float-notation/bad.py b/doc/data/messages/b/bad-float-notation/bad.py new file mode 100644 index 0000000000..616ae47483 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/bad.py @@ -0,0 +1,5 @@ +mindless_anarchy = 15_04e05 # [bad-float-notation] + +this_checker_creation_time = 1751760000.0 # [bad-float-notation] + +small_float = 9.0e2 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/bad.py b/doc/data/messages/b/bad-float-notation/bad/bad.py deleted file mode 100644 index dec6d44520..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/bad.py +++ /dev/null @@ -1 +0,0 @@ -mindless_anarchy = 1504e5 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/engineering_notation.py b/doc/data/messages/b/bad-float-notation/bad/engineering_notation.py deleted file mode 100644 index 5c6506a9ea..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/engineering_notation.py +++ /dev/null @@ -1,4 +0,0 @@ -exponent_not_multiple_of_three = 123e4 # [bad-float-notation] -base_not_between_one_and_a_thousand = 12345e6 # [bad-float-notation] -above_threshold_without_exponent = 10000000 # [bad-float-notation] -under_a_thousand_with_exponent = 9.9e2 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc b/doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc deleted file mode 100644 index c7f36b28ce..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/engineering_notation.rc +++ /dev/null @@ -1,2 +0,0 @@ -[format] -strict-engineering-notation = true diff --git a/doc/data/messages/b/bad-float-notation/bad/pep515.py b/doc/data/messages/b/bad-float-notation/bad/pep515.py deleted file mode 100644 index ae2d485b13..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/pep515.py +++ /dev/null @@ -1,3 +0,0 @@ -not_grouped_by_three = 1_23_456_7_89 # [bad-float-notation] -mixing_with_exponent = 1_23_4_5_67_8e9 # [bad-float-notation] -above_threshold_without_grouping = 123456789 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/pep515.rc b/doc/data/messages/b/bad-float-notation/bad/pep515.rc deleted file mode 100644 index a76dc794c2..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/pep515.rc +++ /dev/null @@ -1,2 +0,0 @@ -[main] -strict-underscore-notation = true diff --git a/doc/data/messages/b/bad-float-notation/bad/scientific_notation.py b/doc/data/messages/b/bad-float-notation/bad/scientific_notation.py deleted file mode 100644 index 146d7f0c5a..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/scientific_notation.py +++ /dev/null @@ -1,3 +0,0 @@ -base_not_between_one_and_ten = 10e3 # [bad-float-notation] -above_threshold_without_exponent = 10000000 # [bad-float-notation] -under_ten_with_exponent = 9.9e0 # [bad-float-notation] diff --git a/doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc b/doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc deleted file mode 100644 index ba8b10546f..0000000000 --- a/doc/data/messages/b/bad-float-notation/bad/scientific_notation.rc +++ /dev/null @@ -1,2 +0,0 @@ -[format] -strict-scientific-notation = true diff --git a/doc/data/messages/b/bad-float-notation/good.py b/doc/data/messages/b/bad-float-notation/good.py new file mode 100644 index 0000000000..063b5a8371 --- /dev/null +++ b/doc/data/messages/b/bad-float-notation/good.py @@ -0,0 +1,7 @@ +engineering_notation = 150.4e6 +scientific_notation = 1.504e8 +pep515_notation = 150_400_000.0 + +this_checker_creation_time = (3600 * 24 * 365 * 55) + ((6 * 30 + 20) * 3600 * 24) + +small_float = 90.0 diff --git a/doc/data/messages/b/bad-float-notation/good/engineering_notation.py b/doc/data/messages/b/bad-float-notation/good/engineering_notation.py deleted file mode 100644 index df1671a01c..0000000000 --- a/doc/data/messages/b/bad-float-notation/good/engineering_notation.py +++ /dev/null @@ -1,3 +0,0 @@ -exponent_multiple_of_three = 1.23e6 -base_between_one_and_a_thousand = 12.345e9 -under_a_thousand = 990 diff --git a/doc/data/messages/b/bad-float-notation/good/good.py b/doc/data/messages/b/bad-float-notation/good/good.py deleted file mode 100644 index a786589f03..0000000000 --- a/doc/data/messages/b/bad-float-notation/good/good.py +++ /dev/null @@ -1,3 +0,0 @@ -scientific_notation = 1.504e8 -engineering_notation = 150.4e6 -underscore_notation = 150_400_000 diff --git a/doc/data/messages/b/bad-float-notation/good/pep515.py b/doc/data/messages/b/bad-float-notation/good/pep515.py deleted file mode 100644 index df2b8469b0..0000000000 --- a/doc/data/messages/b/bad-float-notation/good/pep515.py +++ /dev/null @@ -1 +0,0 @@ -proper_grouping = 123_456_789 diff --git a/doc/data/messages/b/bad-float-notation/good/scientific_notation.py b/doc/data/messages/b/bad-float-notation/good/scientific_notation.py deleted file mode 100644 index 6202a4550b..0000000000 --- a/doc/data/messages/b/bad-float-notation/good/scientific_notation.py +++ /dev/null @@ -1,3 +0,0 @@ -base_between_one_and_ten = 1e4 -above_threshold_with_exponent = 1e7 -under_ten = 9.9 diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 576aec51f0..8c5e391225 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -539,6 +539,10 @@ Format checker Messages :unnecessary-semicolon (W0301): *Unnecessary semicolon* Used when a statement is ended by a semi-colon (";"), which isn't necessary (that's python, not C ;). +:bad-float-notation (C0329): *'%s' %s, and it should be written as '%s' instead* + Emitted when a number is written in a non-standard notation. The three + allowed notation above the threshold are the scientific notation, the + engineering notation, and the underscore grouping notation defined in PEP515. :missing-final-newline (C0304): *Final newline missing* Used when the last line in a file is missing a newline. :line-too-long (C0301): *Line too long (%s/%s)* @@ -557,10 +561,6 @@ Format checker Messages Used when there is different newline than expected. :superfluous-parens (C0325): *Unnecessary parens after %r keyword* Used when a single item in parentheses follows an if, for, or other keyword. -:bad-float-notation (C0329): *float literal should be written as '%s' instead* - Emitted when a number is written in a non-standard notation. The three - allowed notation above the threshold are the scientific notation, the - engineering notation, and the underscore grouping notation defined in PEP515. Imports checker diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index a9cc737431..9c16401d46 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -112,18 +112,56 @@ def to_standard_scientific_notation(cls, number: float) -> str: @classmethod def to_understandable_time(cls, number: float) -> str: - if number == 0.0 or number % 3600 != 0 or number // 3600 == 1: - return "" # Not a suspected time - parts: list[int] = [3600] + if ( + number % 3600 != 0 # not a multiple of the number of second in an hour + or number == 0.0 # 0 is congruent to 3600, but not a time + ): + return "" + if number // 3600 == 1: + # If we have to advise a proper time for 3600, it means the threshold + # was under 3600, so we advise even more decomposition + return "60 * 60" + parts: list[int | str] = [3600] number //= 3600 - for divisor in (24, 7, 365): + for divisor in (24, 365): if number % divisor == 0: parts.append(divisor) number //= divisor remainder = int(number) + days_to_add = None + hours_to_add = None + exp = 0 + while remainder > 1 and remainder % 1000 == 0: + # suspected watt hour remove prior to decomposition + remainder //= 1000 + exp += 3 + if exp: + parts.append(f"1e{exp}") + else: + days = int(remainder // 24) + hours_to_remove = remainder % 24 + if days > 1 and hours_to_remove != 0 and 24 not in parts: + parts += [24] + hours_to_add = f" + ({hours_to_remove} * 3600)" + remainder -= hours_to_remove + remainder //= 24 + year = int(remainder // 365) + days_to_remove = remainder % 365 + if year > 1 and days_to_remove != 0 and 365 not in parts: + parts += [365] + days_to_add = f" + ({days_to_remove} * 3600 * 24)" + remainder -= days_to_remove + remainder //= 365 if remainder != 1: parts.append(remainder) - return " * ".join([str(p) for p in parts]) + result = " * ".join([str(p) for p in parts]) + if days_to_add: + result = f"({result}){days_to_add}" + if hours_to_add: + result += hours_to_add + elif hours_to_add: + result = f"({result}){hours_to_add}" + return result @classmethod def to_standard_engineering_notation(cls, number: float) -> str: @@ -724,7 +762,6 @@ def raise_bad_float_notation( 1 <= value < 10 and self.linter.config.strict_scientific_notation ) or 1 <= value < 1000 is_written_complexly = has_underscore or has_exponent - # print(f"Checking {string} line {line_num}") if should_be_written_simply and is_written_complexly: # If the value does not deserve a complex notation then write it in a simple way. # The threshold is guaranteed to be higher than those value. @@ -766,7 +803,6 @@ def raise_bad_float_notation( ) base_as_str, exponent_as_str = string.lower().split("e") base = float(base_as_str) - # print("\tBase:", base, "Exponent:", exponent_as_str) wrong_scientific_notation = not (1 <= base < 10) if ( self.linter.config.strict_scientific_notation diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 50d4f26a04..710090e08f 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -250,15 +250,20 @@ def test_to_another_standard_notation( @pytest.mark.parametrize( "value,expected", [ - (3600, ""), - (86400, "3600 * 24"), - (604800, "3600 * 24 * 7"), - (2592000, "3600 * 24 * 30"), - (31536000, "3600 * 24 * 365"), - (1180800, "3600 * 24 * 14 - (8 * 3600)"), - (315360000, "3600 * 24 * 365 * 10"), + (3600, "60 * 60"), + (3600 * 1e12, "3600 * 1e12"), + (3600 * 1e6 * 24, "3600 * 24 * 1e6"), + (3600 * 24, "3600 * 24"), + (3600 * 24 * 7, "3600 * 24 * 7"), + (3600 * 24 * 30, "3600 * 24 * 30"), + (3600 * 24 * 365, "3600 * 24 * 365"), + (3600 * 24 * 14 - (8 * 3600), "(3600 * 24 * 13) + (16 * 3600)"), + (3600 * 24 * 365 * 10, "3600 * 24 * 365 * 10"), + (428182 * 3600, "(3600 * 24 * 365 * 48) + (320 * 3600 * 24) + (22 * 3600)"), + (428184 * 3600, "(3600 * 24 * 365 * 48) + (321 * 3600 * 24)"), + (1541484000, "(3600 * 24 * 365 * 48) + (321 * 3600 * 24) + (6 * 3600)"), ], ) def test_to_understandable_time(value: int, expected: str) -> None: actual = FloatFormatterHelper.to_understandable_time(value) - assert actual == expected, f"Expected {expected} for {value}, got {actual}" + assert actual == expected, f"Expected {expected!r} for {value!r}, got {actual!r}" diff --git a/tests/functional/b/bad_float/bad_float_notation_default.py b/tests/functional/b/bad_float/bad_float_notation_default.py index c959a1a98f..f861346655 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.py +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -31,7 +31,7 @@ above_threshold_with_exponent = 1e7 under_ten = 9.9 - +under_threshold_bad = 1504e2 # [bad-float-notation] wrong_big = 45.3e7 # [bad-float-notation] uppercase_e_wrong = 45.3E7 # [bad-float-notation] wrong_small = 0.00012e-26 # [bad-float-notation] diff --git a/tests/functional/b/bad_float/bad_float_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt index ba7eb37ce7..1dd003592d 100644 --- a/tests/functional/b/bad_float/bad_float_notation_default.txt +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -8,6 +8,7 @@ bad-float-notation:20:35:20:45::'10000000.0' is bigger than 1e6, and it should b bad-float-notation:21:33:21:38::'9.9e2' has underscore or exponent, and it should be written as '9.9e2' or '990.0' instead:HIGH bad-float-notation:28:37:28:47::'10000000.0' is bigger than 1e6, and it should be written as '10_000_000.0' or '10e6' or '1e7' instead:HIGH bad-float-notation:29:26:29:31::'9.9e0' has underscore or exponent, and it should be written as '9.9' instead:HIGH +bad-float-notation:34:22:34:28::'1504e2' has a base, '1504.0', that is not between 1 and 1000, and it should be written as '1.504e5' or '150.4e3' or '150_400.0' instead:HIGH bad-float-notation:35:12:35:18::'45.3e7' has an exponent '7' that is not a multiple of 3, and it should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH bad-float-notation:36:20:36:26::'45.3E7' has an exponent '7' that is not a multiple of 3, and it should be written as '4.53e8' or '453_000_000.0' or '453e6' instead:HIGH bad-float-notation:37:14:37:25::'0.00012e-26' has a base, '0.00012', that is not between 1 and 1000, and it should be written as '1.2e-30' instead:HIGH @@ -52,6 +53,6 @@ bad-float-notation:122:21:122:28::'1.5_6e3' has exponent and underscore at the s bad-float-notation:123:27:123:33::'15_6e2' has exponent and underscore at the same time, and it should be written as '1.56e4' or '15.600000000000001e3' or '15_600.0' instead:HIGH bad-float-notation:126:35:126:43::'10.0_0e3' has exponent and underscore at the same time, and it should be written as '10_000.0' or '10e3' or '1e4' instead:HIGH bad-float-notation:126:57:126:65::'20.0_0e3' has exponent and underscore at the same time, and it should be written as '20_000.0' or '20e3' or '2e4' instead:HIGH -bad-float-notation:141:37:141:46::'1180800.0' is bigger than 1e6, and it should be written as '1.1808e6' or '1_180_800.0' or '3600 * 328' instead:HIGH +bad-float-notation:141:37:141:46::'1180800.0' is bigger than 1e6, and it should be written as '(3600 * 24 * 13) + (16 * 3600)' or '1.1808e6' or '1_180_800.0' instead:HIGH bad-float-notation:142:28:142:38::'31536000.0' is bigger than 1e6, and it should be written as '3.1536e7' or '31.536e6' or '31_536_000.0' or '3600 * 24 * 365' instead:HIGH bad-float-notation:143:29:143:38::'2592000.0' is bigger than 1e6, and it should be written as '2.592e6' or '2_592_000.0' or '3600 * 24 * 30' instead:HIGH