Skip to content

Commit c5c29e2

Browse files
wip
1 parent f8d3ccd commit c5c29e2

File tree

1 file changed

+76
-42
lines changed

1 file changed

+76
-42
lines changed

pylint/checkers/format.py

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import math
1717
import re
1818
import tokenize
19+
from decimal import Decimal, getcontext
1920
from functools import reduce
2021
from re import Match
2122
from typing import TYPE_CHECKING, Literal
@@ -32,6 +33,8 @@
3233
if TYPE_CHECKING:
3334
from pylint.lint import PyLinter
3435

36+
getcontext().prec = 50
37+
3538

3639
_KEYWORD_TOKENS = {
3740
"assert",
@@ -59,16 +62,20 @@ class FloatFormatterHelper:
5962
def standardize(
6063
cls,
6164
number: float,
65+
original_string: str, # ← ADD THIS PARAMETER
6266
scientific: bool = True,
6367
engineering: bool = True,
6468
pep515: bool = True,
6569
time_suggestion: bool = False,
6670
) -> str:
71+
dec_number = Decimal(original_string)
72+
sig_figs = len(dec_number.as_tuple().digits)
73+
6774
suggested = set()
6875
if scientific:
69-
suggested.add(cls.to_standard_scientific_notation(number))
76+
suggested.add(cls.to_standard_scientific_notation(dec_number, sig_figs))
7077
if engineering:
71-
suggested.add(cls.to_standard_engineering_notation(number))
78+
suggested.add(cls.to_standard_engineering_notation(dec_number, sig_figs))
7279
if pep515:
7380
suggested.add(cls.to_standard_underscore_grouping(number))
7481
if time_suggestion:
@@ -100,15 +107,33 @@ def to_standard_or_engineering_base(cls, number: float) -> tuple[str, str]:
100107
return base_str, exp_str
101108

102109
@classmethod
103-
def to_standard_scientific_notation(cls, number: float) -> str:
104-
base, exp = cls.to_standard_or_engineering_base(number)
105-
if base == "math.inf":
110+
def to_standard_scientific_notation(
111+
cls, dec_number: Decimal, sig_figs: int # ← CHANGE TYPE # ← ADD PARAMETER
112+
) -> str:
113+
if dec_number == 0:
114+
return "0.0"
115+
if dec_number == Decimal("Infinity"): # ← CHANGE
106116
return "math.inf"
107-
if exp != "0":
108-
return f"{base}e{int(exp)}"
109-
if "." in base:
110-
return base
111-
return f"{base}.0"
117+
118+
# ← USE Decimal.adjusted() instead of log10
119+
exponent = dec_number.adjusted()
120+
121+
if exponent == 0:
122+
base_str = f"{float(dec_number):.{sig_figs}g}"
123+
if "." not in base_str:
124+
base_str += ".0"
125+
return base_str
126+
127+
# ← EXACT DIVISION with Decimal
128+
base_value = dec_number / (Decimal(10) ** exponent)
129+
130+
# ← FORMAT with exact sig figs
131+
base_str = f"{float(base_value):.{sig_figs}g}"
132+
133+
if "." not in base_str and "e" not in base_str.lower():
134+
base_str += ".0"
135+
136+
return f"{base_str}e{exponent}"
112137

113138
@classmethod
114139
def to_understandable_time(cls, number: float) -> str:
@@ -164,33 +189,44 @@ def to_understandable_time(cls, number: float) -> str:
164189
return result
165190

166191
@classmethod
167-
def to_standard_engineering_notation(cls, number: float) -> str:
168-
base, exp = cls.to_standard_or_engineering_base(number)
169-
if base == "math.inf":
192+
def to_standard_engineering_notation(
193+
cls, dec_number: Decimal, sig_figs: int # ← CHANGE TYPE # ← ADD PARAMETER
194+
) -> str:
195+
if dec_number == 0:
196+
return "0.0"
197+
if dec_number == Decimal("Infinity"): # ← CHANGE
170198
return "math.inf"
171-
exp_value = int(exp)
172-
remainder = exp_value % 3
173-
# For negative exponents, the adjustment is different
174-
if exp_value < 0:
175-
# For negative exponents, we need to round down to the next multiple of 3
176-
# e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2
177-
adjustment = 3 - ((-exp_value) % 3)
199+
200+
# ← USE Decimal.adjusted()
201+
exponent = dec_number.adjusted()
202+
203+
remainder = exponent % 3
204+
205+
if exponent < 0:
206+
adjustment = 3 - ((-exponent) % 3)
178207
if adjustment == 3:
179208
adjustment = 0
180-
exp_value = exp_value - adjustment
181-
base_value = float(base) * (10**adjustment)
209+
exp_value = exponent - adjustment
210+
# ← EXACT DIVISION with Decimal
211+
base_value = dec_number / (Decimal(10) ** exp_value)
182212
elif remainder != 0:
183-
# For positive exponents, keep the existing logic
184-
exp_value = exp_value - remainder
185-
base_value = float(base) * (10**remainder)
213+
exp_value = exponent - remainder
214+
# ← EXACT DIVISION with Decimal
215+
base_value = dec_number / (Decimal(10) ** exp_value)
186216
else:
187-
base_value = float(base)
188-
base = str(base_value).rstrip("0").rstrip(".")
217+
exp_value = exponent
218+
# ← EXACT DIVISION with Decimal
219+
base_value = dec_number / (Decimal(10) ** exponent)
220+
221+
# ← FORMAT with exact sig figs (no .rstrip needed!)
222+
base_str = f"{float(base_value):.{sig_figs}g}"
223+
224+
if "." not in base_str and "e" not in base_str.lower():
225+
base_str += ".0"
226+
189227
if exp_value != 0:
190-
return f"{base}e{exp_value}"
191-
if "." in base:
192-
return base
193-
return f"{base}.0"
228+
return f"{base_str}e{exp_value}"
229+
return base_str
194230

195231
@classmethod
196232
def to_standard_underscore_grouping(cls, number: float) -> str:
@@ -725,13 +761,14 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
725761
def _check_bad_float_notation( # pylint: disable=too-many-locals,too-many-return-statements
726762
self, line_num: int, start: tuple[int, int], string: str
727763
) -> None:
764+
728765
has_dot = "." in string
729766
has_exponent = "e" in string or "E" in string
730767
if not (has_dot or has_exponent):
731768
# it's an int, need special treatment later on
732769
return None
733770

734-
value = float(string)
771+
suggestion = FloatFormatterHelper.standardize(string)
735772
engineering = (
736773
self.all_float_notation_allowed
737774
or self.linter.config.strict_engineering_notation
@@ -749,17 +786,14 @@ def raise_bad_float_notation(
749786
reason: str, time_suggestion: bool = False
750787
) -> None:
751788
suggestion = FloatFormatterHelper.standardize(
752-
value, scientific, engineering, pep515, time_suggestion
753-
)
754-
return self.add_message(
755-
"bad-float-notation",
756-
args=(string, reason, suggestion),
757-
line=line_num,
758-
end_lineno=line_num,
759-
col_offset=start[1],
760-
end_col_offset=start[1] + len(string),
761-
confidence=HIGH,
789+
value,
790+
string, # ← ADD THIS: Pass original string
791+
scientific,
792+
engineering,
793+
pep515,
794+
time_suggestion,
762795
)
796+
return self.add_message(...)
763797

764798
if string in {"0", "0.0", "0."}:
765799
# 0 is a special case because it is used very often, and float approximation

0 commit comments

Comments
 (0)