33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6- // spell-checker:ignore powf seps
6+ // spell-checker:ignore powf seps replacen
77
88use uucore:: display:: Quotable ;
9- use uucore:: i18n:: decimal:: locale_grouping_separator;
9+ use uucore:: i18n:: decimal:: { locale_decimal_separator , locale_grouping_separator} ;
1010use uucore:: translate;
1111
1212use crate :: numeric:: ParsedNumber ;
@@ -16,30 +16,37 @@ use crate::units::{
1616} ;
1717
1818fn find_numeric_beginning ( s : & str ) -> Option < & str > {
19- let mut decimal_point_seen = false ;
19+ let dec_sep = locale_decimal_separator ( ) ;
20+ let mut seen_dec = false ;
2021 if s. is_empty ( ) {
2122 return None ;
2223 }
2324
24- if s. starts_with ( '.' ) {
25- return Some ( "." ) ;
25+ if s. starts_with ( dec_sep ) {
26+ return Some ( & s [ ..dec_sep . len ( ) ] ) ;
2627 }
2728
28- for ( idx, c) in s. char_indices ( ) {
29- if c == '-' && idx == 0 {
29+ let mut chars = s. char_indices ( ) . peekable ( ) ;
30+ while let Some ( ( i, c) ) = chars. next ( ) {
31+ if c == '-' && i == 0 {
3032 continue ;
3133 }
3234 if c. is_ascii_digit ( ) {
3335 continue ;
3436 }
35- if c == '.' && !decimal_point_seen {
36- decimal_point_seen = true ;
37+ if !seen_dec && s[ i..] . starts_with ( dec_sep) {
38+ seen_dec = true ;
39+ // skip past any remaining bytes of a multi-byte sep
40+ for _ in 1 ..dec_sep. chars ( ) . count ( ) {
41+ chars. next ( ) ;
42+ }
3743 continue ;
3844 }
39- if s[ ..idx] . parse :: < f64 > ( ) . is_err ( ) {
45+ let num_str = s[ ..i] . replace ( dec_sep, "." ) ;
46+ if num_str. parse :: < f64 > ( ) . is_err ( ) {
4047 return None ;
4148 }
42- return Some ( & s[ ..idx ] ) ;
49+ return Some ( & s[ ..i ] ) ;
4350 }
4451
4552 Some ( s)
@@ -149,15 +156,27 @@ fn detailed_error_message(s: &str, unit: Unit, unit_separator: &str) -> Option<S
149156}
150157
151158fn parse_number_part ( s : & str , input : & str ) -> Result < ParsedNumber > {
152- if s. ends_with ( '.' ) {
159+ let dec_sep = locale_decimal_separator ( ) ;
160+ if s. ends_with ( dec_sep) {
153161 return Err ( translate ! ( "numfmt-error-invalid-number" , "input" => input. quote( ) ) ) ;
154162 }
155163
156164 if let Ok ( n) = s. parse :: < i128 > ( ) {
157165 return Ok ( ParsedNumber :: ExactInt ( n) ) ;
158166 }
159167
160- s. parse :: < f64 > ( )
168+ if dec_sep != "." && s. contains ( '.' ) {
169+ return Err ( translate ! ( "numfmt-error-invalid-number" , "input" => input. quote( ) ) ) ;
170+ }
171+
172+ let normalized = if dec_sep == "." {
173+ s. to_string ( )
174+ } else {
175+ s. replace ( dec_sep, "." )
176+ } ;
177+
178+ normalized
179+ . parse :: < f64 > ( )
161180 . map ( ParsedNumber :: Float )
162181 . map_err ( |_| translate ! ( "numfmt-error-invalid-number" , "input" => input. quote( ) ) )
163182}
@@ -234,7 +253,8 @@ fn apply_grouping(s: &str) -> String {
234253 } else {
235254 ( "" , s)
236255 } ;
237- let ( integer, fraction) = rest. split_once ( '.' ) . map_or ( ( rest, "" ) , |( i, f) | ( i, f) ) ;
256+ let dec_sep = locale_decimal_separator ( ) ;
257+ let ( integer, fraction) = rest. split_once ( dec_sep) . map_or ( ( rest, "" ) , |( i, f) | ( i, f) ) ;
238258 if integer. len ( ) < 4 {
239259 return s. to_string ( ) ;
240260 }
@@ -263,7 +283,7 @@ fn apply_grouping(s: &str) -> String {
263283 }
264284
265285 if !fraction. is_empty ( ) {
266- grouped. push ( '.' ) ;
286+ grouped. push_str ( dec_sep ) ;
267287 grouped. push_str ( fraction) ;
268288 }
269289
@@ -341,7 +361,8 @@ impl<'a> Iterator for WhitespaceSplitter<'a, '_> {
341361/// Returns the implicit precision of a number, which is the count of digits after the dot. For
342362/// example, 1.23 has an implicit precision of 2.
343363fn parse_implicit_precision ( s : & str ) -> usize {
344- match s. split_once ( '.' ) {
364+ let dec_sep = locale_decimal_separator ( ) ;
365+ match s. split_once ( dec_sep) {
345366 Some ( ( _, decimal_part) ) => decimal_part
346367 . chars ( )
347368 . take_while ( char:: is_ascii_digit)
@@ -533,7 +554,11 @@ fn try_format_exact_int_without_suffix_scaling(
533554 Some ( if precision == 0 {
534555 scaled. to_string ( )
535556 } else {
536- format ! ( "{scaled}.{}" , "0" . repeat( precision) )
557+ format ! (
558+ "{scaled}{}{}" ,
559+ locale_decimal_separator( ) ,
560+ "0" . repeat( precision)
561+ )
537562 } )
538563}
539564
@@ -552,25 +577,32 @@ fn transform_to(
552577 let s = s. to_f64 ( ) ;
553578 let i2 = s / ( opts. to_unit as f64 ) ;
554579 let ( i2, s) = consider_suffix ( i2, opts. to , round_method, precision) ?;
555- Ok ( match s {
556- None => {
557- format ! (
558- "{:.precision$}" ,
559- round_with_precision( i2, round_method, precision) ,
560- )
561- }
562- Some ( s) if precision > 0 => {
563- format ! (
564- "{i2:.precision$}{unit_separator}{}" ,
565- DisplayableSuffix ( s, opts. to) ,
566- )
580+ let dec_sep = locale_decimal_separator ( ) ;
581+ let localize = |s : String | -> String {
582+ if dec_sep == "." {
583+ s
584+ } else {
585+ s. replacen ( '.' , dec_sep, 1 )
567586 }
587+ } ;
588+ Ok ( match s {
589+ None => localize ( format ! (
590+ "{:.precision$}" ,
591+ round_with_precision( i2, round_method, precision) ,
592+ ) ) ,
593+ Some ( s) if precision > 0 => localize ( format ! (
594+ "{i2:.precision$}{unit_separator}{}" ,
595+ DisplayableSuffix ( s, opts. to) ,
596+ ) ) ,
568597 Some ( s) if is_precision_specified => {
569598 format ! ( "{i2:.0}{unit_separator}{}" , DisplayableSuffix ( s, opts. to) )
570599 }
571600 Some ( s) if i2. abs ( ) < 10.0 => {
572- // when there's a single digit before the dot.
573- format ! ( "{i2:.1}{unit_separator}{}" , DisplayableSuffix ( s, opts. to) )
601+ // single digit before the decimal, like 1.5K
602+ localize ( format ! (
603+ "{i2:.1}{unit_separator}{}" ,
604+ DisplayableSuffix ( s, opts. to)
605+ ) )
574606 }
575607 Some ( s) => {
576608 format ! ( "{i2:.0}{unit_separator}{}" , DisplayableSuffix ( s, opts. to) )
0 commit comments