@@ -115,7 +115,7 @@ fn clean_int_str(mut s: &str) -> Option<Cow<str>> {
115115 s = s. trim ( ) ;
116116
117117 // strip loading zeros
118- s = s . trim_start_matches ( '0' ) ;
118+ s = strip_leading_zeros ( s ) ? ;
119119
120120 // we don't want to parse as f64 then call `float_as_int` as it can lose precision for large ints, therefore
121121 // we strip `.0+` manually instead
@@ -137,6 +137,37 @@ fn clean_int_str(mut s: &str) -> Option<Cow<str>> {
137137 }
138138}
139139
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+
140171pub fn float_as_int < ' py > ( input : & ( impl Input < ' py > + ?Sized ) , float : f64 ) -> ValResult < EitherInt < ' py > > {
141172 if float. is_infinite ( ) || float. is_nan ( ) {
142173 Err ( ValError :: new ( ErrorTypeDefaults :: FiniteNumber , input) )
0 commit comments