53
53
_JUNK_TOKENS = {tokenize .COMMENT , tokenize .NL }
54
54
55
55
56
+ class FloatFormatterHelper :
57
+
58
+ @classmethod
59
+ def standardize (
60
+ cls ,
61
+ number : float ,
62
+ scientific : bool = True ,
63
+ engineering : bool = True ,
64
+ pep515 : bool = True ,
65
+ time_suggestion : bool = False ,
66
+ ) -> str :
67
+ suggested = set ()
68
+ if scientific :
69
+ suggested .add (cls .to_standard_scientific_notation (number ))
70
+ if engineering :
71
+ suggested .add (cls .to_standard_engineering_notation (number ))
72
+ if pep515 :
73
+ suggested .add (cls .to_standard_underscore_grouping (number ))
74
+ if time_suggestion :
75
+ maybe_a_time = cls .to_understandable_time (number )
76
+ if maybe_a_time :
77
+ suggested .add (maybe_a_time )
78
+ return "' or '" .join (sorted (suggested ))
79
+
80
+ @classmethod
81
+ def to_standard_or_engineering_base (cls , number : float ) -> tuple [str , str ]:
82
+ """Calculate scientific notation components (base, exponent) for a number.
83
+
84
+ Returns a tuple (base, exponent) where:
85
+ - base is a number between 1 and 10 (or exact 0)
86
+ - exponent is the power of 10 needed to represent the original number
87
+ """
88
+ if number == 0 :
89
+ return "0" , "0"
90
+ if number == math .inf :
91
+ return "math.inf" , "0"
92
+ exponent = math .floor (math .log10 (abs (number )))
93
+ if exponent == 0 :
94
+ return str (number ), "0"
95
+ base_value = number / (10 ** exponent )
96
+ # 15 significant digits because if we add more precision then
97
+ # we get into rounding errors territory
98
+ base_str = f"{ base_value :.15g} " .rstrip ("0" ).rstrip ("." )
99
+ exp_str = str (exponent )
100
+ return base_str , exp_str
101
+
102
+ @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" :
106
+ 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"
112
+
113
+ @classmethod
114
+ def to_understandable_time (cls , number : float ) -> str :
115
+ if number == 0.0 or number % 3600 != 0 :
116
+ return "" # Not a suspected time
117
+ parts : list [int ] = [3600 ]
118
+ number //= 3600
119
+ for divisor in (
120
+ 24 ,
121
+ 7 ,
122
+ 365 ,
123
+ ):
124
+ if number % divisor == 0 :
125
+ parts .append (divisor )
126
+ number //= divisor
127
+ remainder = int (number )
128
+ if remainder != 1 :
129
+ parts .append (remainder )
130
+ return " * " .join ([str (p ) for p in parts ])
131
+
132
+ @classmethod
133
+ def to_standard_engineering_notation (cls , number : float ) -> str :
134
+ base , exp = cls .to_standard_or_engineering_base (number )
135
+ if base == "math.inf" :
136
+ return "math.inf"
137
+ exp_value = int (exp )
138
+ remainder = exp_value % 3
139
+ # For negative exponents, the adjustment is different
140
+ if exp_value < 0 :
141
+ # For negative exponents, we need to round down to the next multiple of 3
142
+ # e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2
143
+ adjustment = 3 - ((- exp_value ) % 3 )
144
+ if adjustment == 3 :
145
+ adjustment = 0
146
+ exp_value = exp_value - adjustment
147
+ base_value = float (base ) * (10 ** adjustment )
148
+ elif remainder != 0 :
149
+ # For positive exponents, keep the existing logic
150
+ exp_value = exp_value - remainder
151
+ base_value = float (base ) * (10 ** remainder )
152
+ else :
153
+ base_value = float (base )
154
+ base = str (base_value ).rstrip ("0" ).rstrip ("." )
155
+ if exp_value != 0 :
156
+ return f"{ base } e{ exp_value } "
157
+ if "." in base :
158
+ return base
159
+ return f"{ base } .0"
160
+
161
+ @classmethod
162
+ def to_standard_underscore_grouping (cls , number : float ) -> str :
163
+ number_str = str (number )
164
+ if "e" in number_str or "E" in number_str :
165
+ # python itself want to display this as exponential there's no reason to
166
+ # not use exponential notation for very small number even for strict
167
+ # underscore grouping notation
168
+ return number_str
169
+ if "." in number_str :
170
+ int_part , dec_part = number_str .split ("." )
171
+ else :
172
+ int_part = number_str
173
+ dec_part = "0"
174
+ grouped_int_part = ""
175
+ for i , digit in enumerate (reversed (int_part )):
176
+ if i > 0 and i % 3 == 0 :
177
+ grouped_int_part = "_" + grouped_int_part
178
+ grouped_int_part = digit + grouped_int_part
179
+ return f"{ grouped_int_part } .{ dec_part } "
180
+
181
+
56
182
MSGS : dict [str , MessageDefinitionTuple ] = {
57
183
"C0301" : (
58
184
"Line too long (%s/%s)" ,
@@ -553,107 +679,6 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
553
679
if line_num == last_blank_line_num and line_num > 0 :
554
680
self .add_message ("trailing-newlines" , line = line_num )
555
681
556
- @classmethod
557
- def to_standard_or_engineering_base (cls , number : float ) -> tuple [str , str ]:
558
- """Calculate scientific notation components (base, exponent) for a number.
559
-
560
- Returns a tuple (base, exponent) where:
561
- - base is a number between 1 and 10 (or exact 0)
562
- - exponent is the power of 10 needed to represent the original number
563
- """
564
- if number == 0 :
565
- return "0" , "0"
566
- if number == math .inf :
567
- return "math.inf" , "0"
568
- exponent = math .floor (math .log10 (abs (number )))
569
- if exponent == 0 :
570
- return str (number ), "0"
571
- base_value = number / (10 ** exponent )
572
- # 15 significant digits because if we add more precision then
573
- # we get into rounding errors territory
574
- base_str = f"{ base_value :.15g} " .rstrip ("0" ).rstrip ("." )
575
- exp_str = str (exponent )
576
- return base_str , exp_str
577
-
578
- @classmethod
579
- def to_standard_scientific_notation (cls , number : float ) -> str :
580
- base , exp = cls .to_standard_or_engineering_base (number )
581
- if base == "math.inf" :
582
- return "math.inf"
583
- if exp != "0" :
584
- return f"{ base } e{ int (exp )} "
585
- if "." in base :
586
- return base
587
- return f"{ base } .0"
588
-
589
- @classmethod
590
- def to_understandable_time (cls , number : float ) -> str :
591
- if number % 3600 != 0 :
592
- return "" # Not a suspected time
593
- parts : list [int ] = [3600 ]
594
- number //= 3600
595
- for divisor in (
596
- 24 ,
597
- 7 ,
598
- 365 ,
599
- ):
600
- if number % divisor == 0 :
601
- parts .append (divisor )
602
- number //= divisor
603
- remainder = int (number )
604
- if remainder != 1 :
605
- parts .append (remainder )
606
- return " * " .join ([str (p ) for p in parts ])
607
-
608
- @classmethod
609
- def to_standard_engineering_notation (cls , number : float ) -> str :
610
- base , exp = cls .to_standard_or_engineering_base (number )
611
- if base == "math.inf" :
612
- return "math.inf"
613
- exp_value = int (exp )
614
- remainder = exp_value % 3
615
- # For negative exponents, the adjustment is different
616
- if exp_value < 0 :
617
- # For negative exponents, we need to round down to the next multiple of 3
618
- # e.g., -5 should go to -6, so we get 3 - ((-5) % 3) = 3 - 1 = 2
619
- adjustment = 3 - ((- exp_value ) % 3 )
620
- if adjustment == 3 :
621
- adjustment = 0
622
- exp_value = exp_value - adjustment
623
- base_value = float (base ) * (10 ** adjustment )
624
- elif remainder != 0 :
625
- # For positive exponents, keep the existing logic
626
- exp_value = exp_value - remainder
627
- base_value = float (base ) * (10 ** remainder )
628
- else :
629
- base_value = float (base )
630
- base = str (base_value ).rstrip ("0" ).rstrip ("." )
631
- if exp_value != 0 :
632
- return f"{ base } e{ exp_value } "
633
- if "." in base :
634
- return base
635
- return f"{ base } .0"
636
-
637
- @classmethod
638
- def to_standard_underscore_grouping (cls , number : float ) -> str :
639
- number_str = str (number )
640
- if "e" in number_str or "E" in number_str :
641
- # python itself want to display this as exponential there's no reason to
642
- # not use exponential notation for very small number even for strict
643
- # underscore grouping notation
644
- return number_str
645
- if "." in number_str :
646
- int_part , dec_part = number_str .split ("." )
647
- else :
648
- int_part = number_str
649
- dec_part = "0"
650
- grouped_int_part = ""
651
- for i , digit in enumerate (reversed (int_part )):
652
- if i > 0 and i % 3 == 0 :
653
- grouped_int_part = "_" + grouped_int_part
654
- grouped_int_part = digit + grouped_int_part
655
- return f"{ grouped_int_part } .{ dec_part } "
656
-
657
682
def _check_bad_float_notation ( # pylint: disable=too-many-locals
658
683
self , line_num : int , start : tuple [int , int ], string : str
659
684
) -> None :
@@ -674,20 +699,12 @@ def _check_bad_float_notation( # pylint: disable=too-many-locals
674
699
def raise_bad_float_notation (
675
700
reason : str , time_suggestion : bool = False
676
701
) -> None :
677
- suggested = set ()
678
- if scientific :
679
- suggested .add (self .to_standard_scientific_notation (value ))
680
- if engineering :
681
- suggested .add (self .to_standard_engineering_notation (value ))
682
- if pep515 :
683
- suggested .add (self .to_standard_underscore_grouping (value ))
684
- if time_suggestion :
685
- maybe_a_time = self .to_understandable_time (value )
686
- if maybe_a_time :
687
- suggested .add (maybe_a_time )
702
+ suggestion = FloatFormatterHelper .standardize (
703
+ value , scientific , engineering , pep515 , time_suggestion
704
+ )
688
705
return self .add_message (
689
706
"bad-float-notation" ,
690
- args = (string , reason , "' or '" . join ( sorted ( suggested )) ),
707
+ args = (string , reason , suggestion ),
691
708
line = line_num ,
692
709
end_lineno = line_num ,
693
710
col_offset = start [1 ],
@@ -729,10 +746,10 @@ def raise_bad_float_notation(
729
746
# written complexly, then it could be badly written
730
747
return None
731
748
threshold = self .linter .config .float_notation_threshold
732
- close_to_zero_threshold = self . to_standard_scientific_notation (
733
- 1 / threshold
749
+ close_to_zero_threshold = (
750
+ FloatFormatterHelper . to_standard_scientific_notation ( 1 / threshold )
734
751
)
735
- threshold = self .to_standard_scientific_notation (threshold )
752
+ threshold = FloatFormatterHelper .to_standard_scientific_notation (threshold )
736
753
if under_threshold :
737
754
return raise_bad_float_notation (
738
755
f"is smaller than { close_to_zero_threshold } "
0 commit comments