@@ -74,15 +74,28 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
7474 }
7575 let suffix = match iter. next_back ( ) {
7676 Some ( 'K' ) => Some ( ( RawSuffix :: K , with_i) ) ,
77+ Some ( 'k' ) => Some ( ( RawSuffix :: K , with_i) ) ,
7778 Some ( 'M' ) => Some ( ( RawSuffix :: M , with_i) ) ,
7879 Some ( 'G' ) => Some ( ( RawSuffix :: G , with_i) ) ,
7980 Some ( 'T' ) => Some ( ( RawSuffix :: T , with_i) ) ,
8081 Some ( 'P' ) => Some ( ( RawSuffix :: P , with_i) ) ,
8182 Some ( 'E' ) => Some ( ( RawSuffix :: E , with_i) ) ,
8283 Some ( 'Z' ) => Some ( ( RawSuffix :: Z , with_i) ) ,
8384 Some ( 'Y' ) => Some ( ( RawSuffix :: Y , with_i) ) ,
85+ Some ( 'R' ) => Some ( ( RawSuffix :: R , with_i) ) ,
86+ Some ( 'Q' ) => Some ( ( RawSuffix :: Q , with_i) ) ,
8487 Some ( '0' ..='9' ) if !with_i => None ,
8588 _ => {
89+ // If with_i is true, the string ends with 'i' but there's no valid suffix letter
90+ // This is always an invalid suffix (e.g., "1i", "2Ai")
91+ if with_i {
92+ return Err ( translate ! ( "numfmt-error-invalid-suffix" , "input" => s. quote( ) ) ) ;
93+ }
94+ // For other cases, check if the number part (without the last character) is valid
95+ let number_part = & s[ ..s. len ( ) - 1 ] ;
96+ if number_part. is_empty ( ) || number_part. parse :: < f64 > ( ) . is_err ( ) {
97+ return Err ( translate ! ( "numfmt-error-invalid-number" , "input" => s. quote( ) ) ) ;
98+ }
8699 return Err ( translate ! ( "numfmt-error-invalid-suffix" , "input" => s. quote( ) ) ) ;
87100 }
88101 } ;
@@ -123,6 +136,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
123136 RawSuffix :: E => Ok ( i * 1e18 ) ,
124137 RawSuffix :: Z => Ok ( i * 1e21 ) ,
125138 RawSuffix :: Y => Ok ( i * 1e24 ) ,
139+ RawSuffix :: R => Ok ( i * 1e27 ) ,
140+ RawSuffix :: Q => Ok ( i * 1e30 ) ,
126141 } ,
127142 ( Some ( ( raw_suffix, false ) ) , & Unit :: Iec ( false ) )
128143 | ( Some ( ( raw_suffix, true ) ) , & Unit :: Auto | & Unit :: Iec ( true ) ) => match raw_suffix {
@@ -134,6 +149,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
134149 RawSuffix :: E => Ok ( i * IEC_BASES [ 6 ] ) ,
135150 RawSuffix :: Z => Ok ( i * IEC_BASES [ 7 ] ) ,
136151 RawSuffix :: Y => Ok ( i * IEC_BASES [ 8 ] ) ,
152+ RawSuffix :: R => Ok ( i * IEC_BASES [ 9 ] ) ,
153+ RawSuffix :: Q => Ok ( i * IEC_BASES [ 10 ] ) ,
137154 } ,
138155 ( Some ( ( raw_suffix, false ) ) , & Unit :: Iec ( true ) ) => Err (
139156 translate ! ( "numfmt-error-missing-i-suffix" , "number" => i, "suffix" => format!( "{raw_suffix:?}" ) ) ,
@@ -212,10 +229,10 @@ fn consider_suffix(
212229 round_method : RoundMethod ,
213230 precision : usize ,
214231) -> Result < ( f64 , Option < Suffix > ) > {
215- use crate :: units:: RawSuffix :: { E , G , K , M , P , T , Y , Z } ;
232+ use crate :: units:: RawSuffix :: { E , G , K , M , P , Q , R , T , Y , Z } ;
216233
217234 let abs_n = n. abs ( ) ;
218- let suffixes = [ K , M , G , T , P , E , Z , Y ] ;
235+ let suffixes = [ K , M , G , T , P , E , Z , Y , R , Q ] ;
219236
220237 let ( bases, with_i) = match * u {
221238 Unit :: Si => ( & SI_BASES , false ) ,
@@ -234,6 +251,8 @@ fn consider_suffix(
234251 _ if abs_n < bases[ 7 ] => 6 ,
235252 _ if abs_n < bases[ 8 ] => 7 ,
236253 _ if abs_n < bases[ 9 ] => 8 ,
254+ _ if abs_n < bases[ 10 ] => 9 ,
255+ _ if abs_n < bases[ 10 ] * 1000.0 => 10 ,
237256 _ => return Err ( translate ! ( "numfmt-error-number-too-big" ) ) ,
238257 } ;
239258
@@ -334,38 +353,41 @@ fn format_string(
334353
335354fn format_and_print_delimited ( s : & str , options : & NumfmtOptions ) -> Result < ( ) > {
336355 let delimiter = options. delimiter . as_ref ( ) . unwrap ( ) ;
356+ let mut output = String :: new ( ) ;
337357
338358 for ( n, field) in ( 1 ..) . zip ( s. split ( delimiter) ) {
339359 let field_selected = uucore:: ranges:: contain ( & options. fields , n) ;
340360
341- // print delimiter before second and subsequent fields
361+ // add delimiter before second and subsequent fields
342362 if n > 1 {
343- print ! ( "{ delimiter}" ) ;
363+ output . push_str ( delimiter) ;
344364 }
345365
346366 if field_selected {
347- print ! ( "{}" , format_string( field. trim_start( ) , options, None ) ?) ;
367+ output . push_str ( & format_string ( field. trim_start ( ) , options, None ) ?) ;
348368 } else {
349- // print unselected field without conversion
350- print ! ( "{ field}" ) ;
369+ // add unselected field without conversion
370+ output . push_str ( field) ;
351371 }
352372 }
353373
354- println ! ( ) ;
374+ println ! ( "{output}" ) ;
355375
356376 Ok ( ( ) )
357377}
358378
359379fn format_and_print_whitespace ( s : & str , options : & NumfmtOptions ) -> Result < ( ) > {
380+ let mut output = String :: new ( ) ;
381+
360382 for ( n, ( prefix, field) ) in ( 1 ..) . zip ( WhitespaceSplitter { s : Some ( s) } ) {
361383 let field_selected = uucore:: ranges:: contain ( & options. fields , n) ;
362384
363385 if field_selected {
364386 let empty_prefix = prefix. is_empty ( ) ;
365387
366- // print delimiter before second and subsequent fields
388+ // add delimiter before second and subsequent fields
367389 let prefix = if n > 1 {
368- print ! ( " " ) ;
390+ output . push ( ' ' ) ;
369391 & prefix[ 1 ..]
370392 } else {
371393 prefix
@@ -377,22 +399,24 @@ fn format_and_print_whitespace(s: &str, options: &NumfmtOptions) -> Result<()> {
377399 None
378400 } ;
379401
380- print ! ( "{}" , format_string( field, options, implicit_padding) ?) ;
402+ output . push_str ( & format_string ( field, options, implicit_padding) ?) ;
381403 } else {
382404 // the -z option converts an initial \n into a space
383405 let prefix = if options. zero_terminated && prefix. starts_with ( '\n' ) {
384- print ! ( " " ) ;
406+ output . push ( ' ' ) ;
385407 & prefix[ 1 ..]
386408 } else {
387409 prefix
388410 } ;
389- // print unselected field without conversion
390- print ! ( "{prefix}{field}" ) ;
411+ // add unselected field without conversion
412+ output. push_str ( prefix) ;
413+ output. push_str ( field) ;
391414 }
392415 }
393416
394417 let eol = if options. zero_terminated { '\0' } else { '\n' } ;
395- print ! ( "{eol}" ) ;
418+ output. push ( eol) ;
419+ print ! ( "{output}" ) ;
396420
397421 Ok ( ( ) )
398422}
@@ -445,4 +469,123 @@ mod tests {
445469 assert_eq ! ( 2 , parse_implicit_precision( "1.23K" ) ) ;
446470 assert_eq ! ( 3 , parse_implicit_precision( "1.234K" ) ) ;
447471 }
472+
473+ #[ test]
474+ fn test_parse_suffix_q_r_k ( ) {
475+ let result = parse_suffix ( "1Q" ) ;
476+ assert ! ( result. is_ok( ) ) ;
477+ let ( number, suffix) = result. unwrap ( ) ;
478+ assert_eq ! ( number, 1.0 ) ;
479+ assert ! ( suffix. is_some( ) ) ;
480+ let ( raw_suffix, with_i) = suffix. unwrap ( ) ;
481+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
482+ assert ! ( !with_i) ;
483+
484+ let result = parse_suffix ( "2R" ) ;
485+ assert ! ( result. is_ok( ) ) ;
486+ let ( number, suffix) = result. unwrap ( ) ;
487+ assert_eq ! ( number, 2.0 ) ;
488+ assert ! ( suffix. is_some( ) ) ;
489+ let ( raw_suffix, with_i) = suffix. unwrap ( ) ;
490+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: R as i32 ) ;
491+ assert ! ( !with_i) ;
492+
493+ let result = parse_suffix ( "3k" ) ;
494+ assert ! ( result. is_ok( ) ) ;
495+ let ( number, suffix) = result. unwrap ( ) ;
496+ assert_eq ! ( number, 3.0 ) ;
497+ assert ! ( suffix. is_some( ) ) ;
498+ let ( raw_suffix, with_i) = suffix. unwrap ( ) ;
499+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: K as i32 ) ;
500+ assert ! ( !with_i) ;
501+
502+ let result = parse_suffix ( "4Qi" ) ;
503+ assert ! ( result. is_ok( ) ) ;
504+ let ( number, suffix) = result. unwrap ( ) ;
505+ assert_eq ! ( number, 4.0 ) ;
506+ assert ! ( suffix. is_some( ) ) ;
507+ let ( raw_suffix, with_i) = suffix. unwrap ( ) ;
508+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
509+ assert ! ( with_i) ;
510+
511+ let result = parse_suffix ( "5Ri" ) ;
512+ assert ! ( result. is_ok( ) ) ;
513+ let ( number, suffix) = result. unwrap ( ) ;
514+ assert_eq ! ( number, 5.0 ) ;
515+ assert ! ( suffix. is_some( ) ) ;
516+ let ( raw_suffix, with_i) = suffix. unwrap ( ) ;
517+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: R as i32 ) ;
518+ assert ! ( with_i) ;
519+ }
520+
521+ #[ test]
522+ fn test_parse_suffix_error_messages ( ) {
523+ let result = parse_suffix ( "foo" ) ;
524+ assert ! ( result. is_err( ) ) ;
525+ let error = result. unwrap_err ( ) ;
526+ assert ! ( error. contains( "numfmt-error-invalid-number" ) || error. contains( "invalid number" ) ) ;
527+ assert ! ( !error. contains( "invalid suffix" ) ) ;
528+
529+ let result = parse_suffix ( "World" ) ;
530+ assert ! ( result. is_err( ) ) ;
531+ let error = result. unwrap_err ( ) ;
532+ assert ! ( error. contains( "numfmt-error-invalid-number" ) || error. contains( "invalid number" ) ) ;
533+ assert ! ( !error. contains( "invalid suffix" ) ) ;
534+
535+ let result = parse_suffix ( "123i" ) ;
536+ assert ! ( result. is_err( ) ) ;
537+ let error = result. unwrap_err ( ) ;
538+ assert ! ( error. contains( "numfmt-error-invalid-suffix" ) || error. contains( "invalid suffix" ) ) ;
539+ }
540+
541+ #[ test]
542+ fn test_remove_suffix_q_r ( ) {
543+ use crate :: units:: Unit ;
544+
545+ let result = remove_suffix ( 1.0 , Some ( ( RawSuffix :: Q , false ) ) , & Unit :: Si ) ;
546+ assert ! ( result. is_ok( ) ) ;
547+ assert_eq ! ( result. unwrap( ) , 1e30 ) ;
548+
549+ let result = remove_suffix ( 1.0 , Some ( ( RawSuffix :: R , false ) ) , & Unit :: Si ) ;
550+ assert ! ( result. is_ok( ) ) ;
551+ assert_eq ! ( result. unwrap( ) , 1e27 ) ;
552+
553+ let result = remove_suffix ( 1.0 , Some ( ( RawSuffix :: Q , true ) ) , & Unit :: Iec ( true ) ) ;
554+ assert ! ( result. is_ok( ) ) ;
555+ assert_eq ! ( result. unwrap( ) , IEC_BASES [ 10 ] ) ;
556+
557+ let result = remove_suffix ( 1.0 , Some ( ( RawSuffix :: R , true ) ) , & Unit :: Iec ( true ) ) ;
558+ assert ! ( result. is_ok( ) ) ;
559+ assert_eq ! ( result. unwrap( ) , IEC_BASES [ 9 ] ) ;
560+ }
561+
562+ #[ test]
563+ fn test_consider_suffix_q_r ( ) {
564+ use crate :: options:: RoundMethod ;
565+ use crate :: units:: Unit ;
566+
567+ let result = consider_suffix ( 1e27 , & Unit :: Si , RoundMethod :: FromZero , 0 ) ;
568+ assert ! ( result. is_ok( ) ) ;
569+ let ( value, suffix) = result. unwrap ( ) ;
570+ assert ! ( suffix. is_some( ) ) ;
571+ let ( raw_suffix, _) = suffix. unwrap ( ) ;
572+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: R as i32 ) ;
573+ assert_eq ! ( value, 1.0 ) ;
574+
575+ let result = consider_suffix ( 1e30 , & Unit :: Si , RoundMethod :: FromZero , 0 ) ;
576+ assert ! ( result. is_ok( ) ) ;
577+ let ( value, suffix) = result. unwrap ( ) ;
578+ assert ! ( suffix. is_some( ) ) ;
579+ let ( raw_suffix, _) = suffix. unwrap ( ) ;
580+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
581+ assert_eq ! ( value, 1.0 ) ;
582+
583+ let result = consider_suffix ( 5e30 , & Unit :: Si , RoundMethod :: FromZero , 0 ) ;
584+ assert ! ( result. is_ok( ) ) ;
585+ let ( value, suffix) = result. unwrap ( ) ;
586+ assert ! ( suffix. is_some( ) ) ;
587+ let ( raw_suffix, _) = suffix. unwrap ( ) ;
588+ assert_eq ! ( raw_suffix as i32 , RawSuffix :: Q as i32 ) ;
589+ assert_eq ! ( value, 5.0 ) ;
590+ }
448591}
0 commit comments