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/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.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/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..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)* 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 a7375859e3..9c16401d46 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -13,6 +13,8 @@ from __future__ import annotations +import math +import re import tokenize from functools import reduce from re import Match @@ -51,6 +53,166 @@ _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 % 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, 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) + 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: + 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)", @@ -111,6 +273,13 @@ "unexpected-line-ending-format", "Used when there is different newline than expected.", ), + "C0329": ( + "'%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 " + "engineering notation, and the underscore grouping notation defined in PEP515.", + ), } @@ -247,12 +416,86 @@ 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) + 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 + 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' " + "can be set to True at a time." + ) + 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.""" @@ -430,9 +673,19 @@ 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: + if ( + self.linter.is_message_enabled("bad-float-notation") + # You don't deserve a linter if you mix non-decimal notation and + # exponential or underscore, + 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 + ): + self._check_bad_float_notation(line_num, start, string) + if string.endswith("l"): + self.add_message("lowercase-l-suffix", line=line_num) if string in _KEYWORD_TOKENS: self._check_keyword_parentheses(tokens, idx) @@ -460,6 +713,142 @@ 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,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 + 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( + reason: str, time_suggestion: bool = False + ) -> None: + suggestion = FloatFormatterHelper.standardize( + value, scientific, engineering, pep515, time_suggestion + ) + return self.add_message( + "bad-float-notation", + args=(string, reason, suggestion), + line=line_num, + end_lineno=line_num, + col_offset=start[1], + end_col_offset=start[1] + len(string), + confidence=HIGH, + ) + + 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 + return None + has_underscore = "_" 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 + 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. + # When 1 <= value < 10 the engineering notation is equivalent to the scientific 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 = ( + under_threshold # under threshold + and ( # use scientific or engineering notation and under 1/threshold + self.linter.config.strict_underscore_notation + or abs_value != 0 + or abs_value >= 1 / self.linter.config.float_notation_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 + return None + threshold = self.linter.config.float_notation_threshold + close_to_zero_threshold = ( + FloatFormatterHelper.to_standard_scientific_notation(1 / threshold) + ) + threshold = FloatFormatterHelper.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 bigger than {threshold}", time_suggestion=True + ) + 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( + "has exponent and underscore at the same time" + ) + base_as_str, exponent_as_str = string.lower().split("e") + base = float(base_as_str) + wrong_scientific_notation = not (1 <= base < 10) + if ( + self.linter.config.strict_scientific_notation + and wrong_scientific_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 + ) + if ( + self.linter.config.strict_engineering_notation + and wrong_engineering_notation + ) or (wrong_scientific_notation and wrong_engineering_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( + "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( + "has underscores that are not delimiting packs of three digits" + ) + 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 c0659ad763..710090e08f 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -9,10 +9,11 @@ import tokenize import astroid +import pytest 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 @@ -180,3 +181,89 @@ def test_disable_global_option_end_of_line() -> None: assert not myreporter.messages finally: os.remove(file_.name) + + +@pytest.mark.parametrize( + "value,expected_scientific,expected_engineering,expected_underscore", + [ + ("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 auto-fix + "10_5415_456_465498.16354698489", + "1.05415456465498e14", + "105.415456465498e12", + "105_415_456_465_498.16", + ), + ], +) +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 = FloatFormatterHelper.to_standard_scientific_notation(float_value) + assert ( + scientific == expected_scientific + ), f"Scientific notation mismatch expected {expected_scientific}, got {scientific}" + engineering = FloatFormatterHelper.to_standard_engineering_notation(float_value) + assert ( + engineering == expected_engineering + ), f"Engineering notation mismatch expected {expected_engineering}, got {engineering}" + 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}" + + +@pytest.mark.parametrize( + "value,expected", + [ + (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!r} for {value!r}, got {actual!r}" 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..48f06936c9 --- /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.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.0 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..26a95a2e25 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.rc @@ -0,0 +1,5 @@ +[main] +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_engineering_notation.txt b/tests/functional/b/bad_float/bad_float_engineering_notation.txt new file mode 100644 index 0000000000..7fd0e27e4f --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_engineering_notation.txt @@ -0,0 +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: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 new file mode 100644 index 0000000000..f861346655 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_notation_default.py @@ -0,0 +1,143 @@ +# 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.0 # [bad-float-notation] +mixing_with_exponent = 1_23_4_5_67_8e9 # [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 + +# 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.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 + +# 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.0 # [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 + +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] +uppercase_e_wrong_small = 0.00012E-26 # [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 +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] +zero_int = 0 +zero_float = 0.0 +zero_float_v2 = 0. +annoying_zero_int = 00 +annoying_zero_float = 00.0 # [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 # [bad-float-notation] +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 +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 = 15e4 + 25e7j # [bad-float-notation] +underscore_binary = 0b1010_1010 + + +#+1: [bad-float-notation, bad-float-notation] +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.0e4, 10.0e7) + +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] +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] +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] +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] +complex_underscore_wrong = 15_6e2 + 25_6e2j # [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.") + + +#+3: [bad-float-notation] +#+3: [bad-float-notation] +#+3: [bad-float-notation] +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.rc b/tests/functional/b/bad_float/bad_float_notation_default.rc new file mode 100644 index 0000000000..d9d87684ea --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_notation_default.rc @@ -0,0 +1,5 @@ +[main] +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_notation_default.txt b/tests/functional/b/bad_float/bad_float_notation_default.txt new file mode 100644 index 0000000000..1dd003592d --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_notation_default.txt @@ -0,0 +1,58 @@ +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: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: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: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: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 +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: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 '(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 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..b32ed09994 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_pep515.py @@ -0,0 +1,11 @@ +# pylint: disable=missing-docstring,invalid-name + +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.0 # [bad-float-notation] +scientific_notation = 1.2345678e16 # [bad-float-notation] +engineering_notation = 12.345678e15 # [bad-float-notation] + +proper_grouping = 123_456_789.0 + +int_under_ten = 9 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..d5ab40983f --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_pep515.rc @@ -0,0 +1,5 @@ +[main] +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_pep515.txt b/tests/functional/b/bad_float/bad_float_pep515.txt new file mode 100644 index 0000000000..71fbb3a6d8 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_pep515.txt @@ -0,0 +1,5 @@ +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: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 new file mode 100644 index 0000000000..fff5a50a65 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.py @@ -0,0 +1,9 @@ +# pylint: disable=missing-docstring,invalid-name + +base_not_between_one_and_ten = 10e3 # [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 +under_ten = 9.9 +int_under_ten = 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..217d0ce9ff --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.rc @@ -0,0 +1,5 @@ +[tool.pylint.format] +strict-engineering-notation = false +strict-scientific-notation = true +strict-underscore-notation = false +float-notation-threshold=1e6 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..92e1f997b6 --- /dev/null +++ b/tests/functional/b/bad_float/bad_float_scientific_notation.txt @@ -0,0 +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: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