@@ -115,7 +115,7 @@ fn clean_int_str(mut s: &str) -> Option<Cow<str>> {
115
115
s = s. trim ( ) ;
116
116
117
117
// strip loading zeros
118
- s = s . trim_start_matches ( '0' ) ;
118
+ s = strip_leading_zeros ( s ) ? ;
119
119
120
120
// we don't want to parse as f64 then call `float_as_int` as it can lose precision for large ints, therefore
121
121
// we strip `.0+` manually instead
@@ -137,6 +137,37 @@ fn clean_int_str(mut s: &str) -> Option<Cow<str>> {
137
137
}
138
138
}
139
139
140
+ /// strip leading zeros from a string, we can't simple use `s.trim_start_matches('0')`, because:
141
+ /// - we need to keep one zero if the string is only zeros e.g. `000` -> `0`
142
+ /// - we need to keep one zero if the string is a float which is an exact int e.g. `00.0` -> `0.0`
143
+ /// - underscores within leading zeros should also be stripped e.g. `0_000` -> `0`, but not `_000`
144
+ fn strip_leading_zeros ( s : & str ) -> Option < & str > {
145
+ let mut char_iter = s. char_indices ( ) ;
146
+ match char_iter. next ( ) {
147
+ // if we get a leading zero we continue
148
+ Some ( ( _, '0' ) ) => ( ) ,
149
+ // if we get another digit we return the whole string
150
+ Some ( ( _, c) ) if ( '1' ..='9' ) . contains ( & c) => return Some ( s) ,
151
+ // anything else is invalid, we return None
152
+ _ => return None ,
153
+ } ;
154
+ for ( i, c) in char_iter {
155
+ match c {
156
+ // continue on more leading zeros or if we get an underscore we continue - we're "within the number"
157
+ '0' | '_' => ( ) ,
158
+ // any other digit we return the rest of the string
159
+ '1' ..='9' => return Some ( & s[ i..] ) ,
160
+ // if we get a dot we return the rest of the string but include the last zero
161
+ '.' => return Some ( & s[ ( i - 1 ) ..] ) ,
162
+ // anything else is invalid, we return None
163
+ _ => return None ,
164
+ }
165
+ }
166
+ // if the string is all zeros (or underscores), we return the last character
167
+ // generally this will be zero, but could be an underscore, which will fail
168
+ Some ( & s[ s. len ( ) - 1 ..] )
169
+ }
170
+
140
171
pub fn float_as_int < ' py > ( input : & ( impl Input < ' py > + ?Sized ) , float : f64 ) -> ValResult < EitherInt < ' py > > {
141
172
if float. is_infinite ( ) || float. is_nan ( ) {
142
173
Err ( ValError :: new ( ErrorTypeDefaults :: FiniteNumber , input) )
0 commit comments