Skip to content

Commit 77a3d6b

Browse files
Fix the scientific, pep515 and engineering suggestions
1 parent 7553c8c commit 77a3d6b

File tree

4 files changed

+95
-35
lines changed

4 files changed

+95
-35
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
base_not_between_one_and_ten = 10e3 # [bad-float-notation]
22
above_threshold_without_exponent = 10000000 # [bad-float-notation]
3-
under_ten_with_exponent = 9e0 # [bad-float-notation]
3+
under_ten_with_exponent = 9.9e0 # [bad-float-notation]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
base_between_one_and_ten = 1e4
22
above_threshold_with_exponent = 1e7
3-
under_ten = 9
3+
under_ten = 9.9

pylint/checkers/format.py

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from __future__ import annotations
1515

16+
import math
1617
import re
1718
import tokenize
1819
from functools import reduce
@@ -548,46 +549,81 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
548549

549550
@classmethod
550551
def to_standard_or_engineering_base(cls, number):
551-
# number is always going to be > 0 because node constants are always positive
552-
# Format with high precision to capture all digits
553-
s = f"{number:.15e}"
554-
base, exp = s.split("e")
555-
# Remove trailing zeros and possible trailing decimal point
556-
base = base.rstrip("0").rstrip(".")
557-
return base, exp
552+
"""Calculate scientific notation components (base, exponent) for a number.
553+
554+
Returns a tuple (base, exponent) where:
555+
- base is a number between 1 and 10 (or exact 0)
556+
- exponent is the power of 10 needed to represent the original number
557+
"""
558+
if number == 0:
559+
return "0", "0"
560+
exponent = math.floor(math.log10(abs(number)))
561+
if exponent == 0:
562+
return str(number), "0"
563+
base_value = number / (10**exponent)
564+
# 15 significant digits because if we add more precision then
565+
# we get into rounding errors territory
566+
base_str = f"{base_value:.15g}".rstrip("0").rstrip(".")
567+
exp_str = str(exponent)
568+
return base_str, exp_str
558569

559570
@classmethod
560571
def to_standard_scientific_notation(cls, number: float) -> str:
561572
base, exp = cls.to_standard_or_engineering_base(number)
562-
return f"{base}e{int(exp)}" if exp not in ["0", "1"] else str(base)
573+
if exp != "0":
574+
return f"{base}e{int(exp)}"
575+
if "." in base:
576+
return base
577+
return f"{base}.0"
563578

564579
@classmethod
565580
def to_standard_engineering_notation(cls, number: float) -> str:
566581
base, exp = cls.to_standard_or_engineering_base(number)
567-
result = f"{base}e{int(exp)}" if exp not in ["0", "1"] else str(base)
568582
exp_value = int(exp)
569583
remainder = exp_value % 3
570-
if remainder != 0:
584+
# For negative exponents, the adjustment is different
585+
if exp_value < 0:
586+
# For negative exponents, we need to round down to the next multiple of 3
587+
# e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2
588+
adjustment = 3 - ((-exp_value) % 3)
589+
if adjustment == 3:
590+
adjustment = 0
591+
exp_value = exp_value - adjustment
592+
base_value = float(base) * (10**adjustment)
593+
elif remainder != 0:
594+
# For positive exponents, keep the existing logic
571595
exp_value = exp_value - remainder
572596
base_value = float(base) * (10**remainder)
573-
base = str(base_value).rstrip("0").rstrip(".")
574-
return f"{base}e{exp_value}" if exp_value > 2 else base
597+
else:
598+
base_value = float(base)
599+
base = str(base_value).rstrip("0").rstrip(".")
600+
if exp_value != 0:
601+
return f"{base}e{exp_value}"
602+
if "." in base:
603+
return base
604+
return f"{base}.0"
575605

576606
@classmethod
577607
def to_standard_underscore_grouping(cls, number: float) -> str:
578-
dec_part = "0"
579-
if "." in str(number):
580-
int_part, dec_part = str(number).split(".")
608+
number_str = str(number)
609+
if "e" in number_str or "E" in number_str:
610+
# python itself want to display this as exponential there's no reason to
611+
# not use exponential notation for very small number even for strict
612+
# underscore grouping notation
613+
return number_str
614+
print(number_str)
615+
if "." in number_str:
616+
int_part, dec_part = number_str.split(".")
581617
else:
582-
int_part = str(number)
583-
if dec_part == "0":
584-
dec_part = ""
618+
int_part = number_str
619+
dec_part = "0"
620+
print("int_part", int_part, "dec_part:", dec_part)
585621
grouped_int_part = ""
586622
for i, digit in enumerate(reversed(int_part)):
587623
if i > 0 and i % 3 == 0:
588624
grouped_int_part = "_" + grouped_int_part
589625
grouped_int_part = digit + grouped_int_part
590-
return f"{grouped_int_part}.{dec_part}" if dec_part else grouped_int_part
626+
return f"{grouped_int_part}.{dec_part}"
591627

592628
def _check_bad_float_notation(
593629
self, line_num: int, start: tuple[int, int], string: str

tests/checkers/unittest_format.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,19 +186,37 @@ def test_disable_global_option_end_of_line() -> None:
186186
@pytest.mark.parametrize(
187187
"value,expected_scientific,expected_engineering,expected_underscore",
188188
[
189-
("0e10", "0", "0", "0"),
190-
("3e1", "30", "30", "30"),
191-
("30e2", "3e3", "3e3", "3_000"),
192-
("1e6", "1e6", "1e6", "1_000_000"),
193-
("10e5", "1e6", "1e6", "1_000_000"),
194-
("1_000_000", "1e6", "1e6", "1_000_000"),
195-
("1000_000", "1e6", "1e6", "1_000_000"),
196-
("20e9", "2e10", "20e9", "20_000_000_000"),
197-
("1e-10", "1e-10", "100e-12", "0.0000000001"),
189+
("0", "0.0", "0.0", "0.0"),
190+
("0e10", "0.0", "0.0", "0.0"),
191+
("0e-10", "0.0", "0.0", "0.0"),
192+
("0.0e10", "0.0", "0.0", "0.0"),
193+
("1e0", "1.0", "1.0", "1.0"),
194+
("1e10", "1e10", "10e9", "10_000_000_000.0"),
195+
# no reason to not use exponential notation for very low number
196+
# even for strict underscore grouping notation
197+
("1e-10", "1e-10", "100e-12", "1e-10"),
198+
("2e1", "2e1", "20.0", "20.0"),
199+
("2e-1", "2e-1", "200e-3", "0.2"),
200+
("3.456e2", "3.456e2", "345.6", "345.6"),
201+
("3.456e-2", "3.456e-2", "34.56e-3", "0.03456"),
202+
("4e2", "4e2", "400.0", "400.0"),
203+
("4e-2", "4e-2", "40e-3", "0.04"),
204+
("50e2", "5e3", "5e3", "5_000.0"),
205+
("50e-2", "5e-1", "500e-3", "0.5"),
206+
("6e6", "6e6", "6e6", "6_000_000.0"),
207+
("6e-6", "6e-6", "6e-6", "6e-06"), # 6e-06 is what python offer on str(float)
208+
("10e5", "1e6", "1e6", "1_000_000.0"),
209+
("10e-5", "1e-4", "100e-6", "0.0001"),
210+
("1_000_000", "1e6", "1e6", "1_000_000.0"),
211+
("1000_000", "1e6", "1e6", "1_000_000.0"),
212+
("20e9", "2e10", "20e9", "20_000_000_000.0"),
213+
("20e-9", "2e-8", "20e-9", "2e-08"), # 2e-08 is what python offer on str(float)
198214
(
215+
# 15 significant digits because we get rounding error otherwise
216+
# and 15 seems enough especially since we don't autofix
199217
"10_5415_456_465498.16354698489",
200-
"1.054154564654982e14",
201-
"105.4154564654982e12",
218+
"1.05415456465498e14",
219+
"105.415456465498e12",
202220
"105_415_456_465_498.16",
203221
),
204222
],
@@ -212,8 +230,14 @@ def test_to_another_standard_notation(
212230
"""Test the conversion of numbers to all possible notations."""
213231
float_value = float(value)
214232
scientific = FormatChecker.to_standard_scientific_notation(float_value)
215-
assert scientific == expected_scientific
233+
assert (
234+
scientific == expected_scientific
235+
), f"Scientific notation mismatch expected {expected_scientific}, got {scientific}"
216236
engineering = FormatChecker.to_standard_engineering_notation(float_value)
217-
assert engineering == expected_engineering
237+
assert (
238+
engineering == expected_engineering
239+
), f"Engineering notation mismatch expected {expected_engineering}, got {engineering}"
218240
underscore = FormatChecker.to_standard_underscore_grouping(float_value)
219-
assert underscore == expected_underscore
241+
assert (
242+
underscore == expected_underscore
243+
), f"Underscore grouping mismatch expected {expected_underscore}, got {underscore}"

0 commit comments

Comments
 (0)