@@ -49,8 +49,64 @@ pub fn parse_decimal(value: &str) -> Option<f64> {
4949 if trimmed. is_empty ( ) {
5050 return None ;
5151 }
52- let normalized = trimmed. replace ( ',' , "." ) ;
53- normalized. parse :: < f64 > ( ) . ok ( ) . filter ( |v| v. is_finite ( ) )
52+ let normalized = trimmed
53+ . replace ( '\u{00a0}' , "" )
54+ . replace ( '\u{202f}' , "" )
55+ . replace ( '\u{2009}' , "" )
56+ . replace ( ' ' , "" )
57+ . replace ( '_' , "" )
58+ . replace ( '\'' , "" ) ;
59+ if normalized. is_empty ( ) {
60+ return None ;
61+ }
62+
63+ let parse = |raw : String | raw. parse :: < f64 > ( ) . ok ( ) . filter ( |v| v. is_finite ( ) ) ;
64+
65+ let comma_count = normalized. matches ( ',' ) . count ( ) ;
66+ let dot_count = normalized. matches ( '.' ) . count ( ) ;
67+
68+ if comma_count > 0 && dot_count > 0 {
69+ let last_comma = normalized. rfind ( ',' ) . unwrap_or ( 0 ) ;
70+ let last_dot = normalized. rfind ( '.' ) . unwrap_or ( 0 ) ;
71+ if last_comma > last_dot {
72+ // 1.234,56 -> 1234.56
73+ return parse ( normalized. replace ( '.' , "" ) . replace ( ',' , "." ) ) ;
74+ }
75+ // 1,234.56 -> 1234.56
76+ return parse ( normalized. replace ( ',' , "" ) ) ;
77+ }
78+
79+ if comma_count > 0 {
80+ if comma_count > 1 {
81+ // 1,234,567 -> 1234567
82+ return parse ( normalized. replace ( ',' , "" ) ) ;
83+ }
84+ let split_idx = normalized. find ( ',' ) . unwrap_or ( 0 ) ;
85+ let int_part = & normalized[ ..split_idx] ;
86+ let frac_part = & normalized[ split_idx + 1 ..] ;
87+ let int_digits = int_part. trim_start_matches ( [ '+' , '-' ] ) ;
88+ let grouped_thousands = frac_part. len ( ) == 3
89+ && frac_part. chars ( ) . all ( |ch| ch. is_ascii_digit ( ) )
90+ && !int_digits. is_empty ( )
91+ && int_digits != "0"
92+ && !int_digits. starts_with ( '0' )
93+ && int_part
94+ . chars ( )
95+ . all ( |ch| ch. is_ascii_digit ( ) || ch == '+' || ch == '-' ) ;
96+ if grouped_thousands {
97+ // 1,000 -> 1000
98+ return parse ( normalized. replace ( ',' , "" ) ) ;
99+ }
100+ // 1,25 -> 1.25
101+ return parse ( normalized. replace ( ',' , "." ) ) ;
102+ }
103+
104+ if dot_count > 1 {
105+ // 1.234.567 -> 1234567
106+ return parse ( normalized. replace ( '.' , "" ) ) ;
107+ }
108+
109+ parse ( normalized)
54110}
55111
56112pub fn parse_decimal_or_nan ( value : & str ) -> f64 {
@@ -274,6 +330,20 @@ mod tests {
274330 assert_eq ! ( parse_decimal( "0,2" ) , Some ( 0.2 ) ) ;
275331 assert_eq ! ( parse_decimal( " 1.25 " ) , Some ( 1.25 ) ) ;
276332 assert_eq ! ( parse_decimal( " 1,25 " ) , Some ( 1.25 ) ) ;
333+ assert_eq ! ( parse_decimal( "1.000" ) , Some ( 1.0 ) ) ;
334+ assert_eq ! ( parse_decimal( "0.125" ) , Some ( 0.125 ) ) ;
335+ assert_eq ! ( parse_decimal( "0,125" ) , Some ( 0.125 ) ) ;
336+ }
337+
338+ #[ test]
339+ fn parse_decimal_accepts_grouped_thousands_inputs ( ) {
340+ assert_eq ! ( parse_decimal( "1,000" ) , Some ( 1000.0 ) ) ;
341+ assert_eq ! ( parse_decimal( "12,345" ) , Some ( 12345.0 ) ) ;
342+ assert_eq ! ( parse_decimal( "1,234.5" ) , Some ( 1234.5 ) ) ;
343+ assert_eq ! ( parse_decimal( "1.234,5" ) , Some ( 1234.5 ) ) ;
344+ assert_eq ! ( parse_decimal( "1.234.567" ) , Some ( 1234567.0 ) ) ;
345+ assert_eq ! ( parse_decimal( "1 234,5" ) , Some ( 1234.5 ) ) ;
346+ assert_eq ! ( parse_decimal( "1'234,5" ) , Some ( 1234.5 ) ) ;
277347 }
278348
279349 #[ test]
0 commit comments