5
5
6
6
//! Utilities for formatting numbers in various formats
7
7
8
+ use std:: cmp:: min;
8
9
use std:: io:: Write ;
9
10
10
11
use super :: {
@@ -77,22 +78,16 @@ pub struct SignedInt {
77
78
impl Formatter for SignedInt {
78
79
type Input = i64 ;
79
80
80
- fn fmt ( & self , mut writer : impl Write , x : Self :: Input ) -> std:: io:: Result < ( ) > {
81
- if x >= 0 {
82
- match self . positive_sign {
83
- PositiveSign :: None => Ok ( ( ) ) ,
84
- PositiveSign :: Plus => write ! ( writer, "+" ) ,
85
- PositiveSign :: Space => write ! ( writer, " " ) ,
86
- } ?;
87
- }
81
+ fn fmt ( & self , writer : impl Write , x : Self :: Input ) -> std:: io:: Result < ( ) > {
82
+ let s = if self . precision > 0 {
83
+ format ! ( "{:0>width$}" , x. abs( ) , width = self . precision)
84
+ } else {
85
+ x. abs ( ) . to_string ( )
86
+ } ;
88
87
89
- let s = format ! ( "{:0width$}" , x , width = self . precision ) ;
88
+ let sign_indicator = get_sign_indicator ( self . positive_sign , & x ) ;
90
89
91
- match self . alignment {
92
- NumberAlignment :: Left => write ! ( writer, "{s:<width$}" , width = self . width) ,
93
- NumberAlignment :: RightSpace => write ! ( writer, "{s:>width$}" , width = self . width) ,
94
- NumberAlignment :: RightZero => write ! ( writer, "{s:0>width$}" , width = self . width) ,
95
- }
90
+ write_output ( writer, sign_indicator, s, self . width , self . alignment )
96
91
}
97
92
98
93
fn try_from_spec ( s : Spec ) -> Result < Self , FormatError > {
@@ -244,16 +239,8 @@ impl Default for Float {
244
239
impl Formatter for Float {
245
240
type Input = f64 ;
246
241
247
- fn fmt ( & self , mut writer : impl Write , x : Self :: Input ) -> std:: io:: Result < ( ) > {
248
- if x. is_sign_positive ( ) {
249
- match self . positive_sign {
250
- PositiveSign :: None => Ok ( ( ) ) ,
251
- PositiveSign :: Plus => write ! ( writer, "+" ) ,
252
- PositiveSign :: Space => write ! ( writer, " " ) ,
253
- } ?;
254
- }
255
-
256
- let s = if x. is_finite ( ) {
242
+ fn fmt ( & self , writer : impl Write , x : Self :: Input ) -> std:: io:: Result < ( ) > {
243
+ let mut s = if x. is_finite ( ) {
257
244
match self . variant {
258
245
FloatVariant :: Decimal => {
259
246
format_float_decimal ( x, self . precision , self . force_decimal )
@@ -272,11 +259,13 @@ impl Formatter for Float {
272
259
format_float_non_finite ( x, self . case )
273
260
} ;
274
261
275
- match self . alignment {
276
- NumberAlignment :: Left => write ! ( writer, "{s:<width$}" , width = self . width) ,
277
- NumberAlignment :: RightSpace => write ! ( writer, "{s:>width$}" , width = self . width) ,
278
- NumberAlignment :: RightZero => write ! ( writer, "{s:0>width$}" , width = self . width) ,
279
- }
262
+ // The format function will parse `x` together with its sign char,
263
+ // which should be placed in `sign_indicator`. So drop it here
264
+ s = if x < 0. { s[ 1 ..] . to_string ( ) } else { s } ;
265
+
266
+ let sign_indicator = get_sign_indicator ( self . positive_sign , & x) ;
267
+
268
+ write_output ( writer, sign_indicator, s, self . width , self . alignment )
280
269
}
281
270
282
271
fn try_from_spec ( s : Spec ) -> Result < Self , FormatError >
@@ -326,6 +315,18 @@ impl Formatter for Float {
326
315
}
327
316
}
328
317
318
+ fn get_sign_indicator < T : PartialOrd + Default > ( sign : PositiveSign , x : & T ) -> String {
319
+ if * x >= T :: default ( ) {
320
+ match sign {
321
+ PositiveSign :: None => String :: new ( ) ,
322
+ PositiveSign :: Plus => String :: from ( "+" ) ,
323
+ PositiveSign :: Space => String :: from ( " " ) ,
324
+ }
325
+ } else {
326
+ String :: from ( "-" )
327
+ }
328
+ }
329
+
329
330
fn format_float_non_finite ( f : f64 , case : Case ) -> String {
330
331
debug_assert ! ( !f. is_finite( ) ) ;
331
332
let mut s = format ! ( "{f}" ) ;
@@ -501,6 +502,47 @@ fn strip_fractional_zeroes_and_dot(s: &mut String) {
501
502
}
502
503
}
503
504
505
+ fn write_output (
506
+ mut writer : impl Write ,
507
+ sign_indicator : String ,
508
+ mut s : String ,
509
+ width : usize ,
510
+ alignment : NumberAlignment ,
511
+ ) -> std:: io:: Result < ( ) > {
512
+ // Take length of `sign_indicator`, which could be 0 or 1, into consideration when padding
513
+ // by storing remaining_width indicating the actual width needed.
514
+ // Using min() because self.width could be 0, 0usize - 1usize should be avoided
515
+ let remaining_width = width - min ( width, sign_indicator. len ( ) ) ;
516
+ match alignment {
517
+ NumberAlignment :: Left => write ! (
518
+ writer,
519
+ "{sign_indicator}{s:<width$}" ,
520
+ width = remaining_width
521
+ ) ,
522
+ NumberAlignment :: RightSpace => {
523
+ let is_sign = sign_indicator. starts_with ( '-' ) || sign_indicator. starts_with ( '+' ) ; // When sign_indicator is in ['-', '+']
524
+ if is_sign && remaining_width > 0 {
525
+ // Make sure sign_indicator is just next to number, e.g. "% +5.1f" 1 ==> $ +1.0
526
+ s = sign_indicator + s. as_str ( ) ;
527
+ write ! ( writer, "{s:>width$}" , width = remaining_width + 1 ) // Since we now add sign_indicator and s together, plus 1
528
+ } else {
529
+ write ! (
530
+ writer,
531
+ "{sign_indicator}{s:>width$}" ,
532
+ width = remaining_width
533
+ )
534
+ }
535
+ }
536
+ NumberAlignment :: RightZero => {
537
+ write ! (
538
+ writer,
539
+ "{sign_indicator}{s:0>width$}" ,
540
+ width = remaining_width
541
+ )
542
+ }
543
+ }
544
+ }
545
+
504
546
#[ cfg( test) ]
505
547
mod test {
506
548
use crate :: format:: num_format:: { Case , ForceDecimal } ;
0 commit comments