1
1
use std:: convert:: Infallible ;
2
2
use std:: fmt;
3
+ use std:: mem:: MaybeUninit ;
4
+ use std:: str:: from_utf8_unchecked;
5
+
6
+ use super :: FastWritable ;
3
7
4
8
/// Returns adequate string representation (in KB, ..) of number of bytes
5
9
///
@@ -26,59 +30,85 @@ pub fn filesizeformat(b: f32) -> Result<FilesizeFormatFilter, Infallible> {
26
30
#[ derive( Debug , Clone , Copy ) ]
27
31
pub struct FilesizeFormatFilter ( f32 ) ;
28
32
29
- struct NbAndDecimal ( u32 ) ;
30
- impl NbAndDecimal {
31
- fn new ( nb : f32 ) -> Self {
32
- // `nb` will never be bigger than 999_999 so we're fine with usize.
33
- Self ( nb as _ )
34
- }
35
- }
36
- impl fmt:: Display for NbAndDecimal {
33
+ impl fmt:: Display for FilesizeFormatFilter {
34
+ #[ inline]
37
35
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
38
- let sub = self . 0 % 1_000 / 10 ;
39
- if sub != 0 {
40
- if sub < 10 {
41
- f. write_fmt ( format_args ! ( "{}.0{sub}" , self . 0 / 1_000 ) )
42
- } else {
43
- f. write_fmt ( format_args ! ( "{}.{sub}" , self . 0 / 1_000 ) )
44
- }
45
- } else {
46
- f. write_fmt ( format_args ! ( "{}" , self . 0 / 1_000 ) )
47
- }
36
+ self . write_into ( f)
48
37
}
49
38
}
50
39
51
- impl fmt :: Display for FilesizeFormatFilter {
52
- fn fmt ( & self , f : & mut fmt :: Formatter < ' _ > ) -> fmt:: Result {
40
+ impl FastWritable for FilesizeFormatFilter {
41
+ fn write_into < W : fmt:: Write + ? Sized > ( & self , dest : & mut W ) -> fmt:: Result {
53
42
if self . 0 < 1e3 {
54
- return f. write_fmt ( format_args ! ( "{} B" , self . 0 ) ) ;
55
- }
56
- for ( unit, max, divider) in [
57
- ( " KB" , 1e6 , 1. ) ,
58
- ( " MB" , 1e9 , 1e3 ) ,
59
- ( " GB" , 1e12 , 1e6 ) ,
60
- ( " TB" , 1e15 , 1e9 ) ,
61
- ( " PB" , 1e18 , 1e12 ) ,
62
- ( " EB" , 1e21 , 1e15 ) ,
63
- ( " ZB" , 1e24 , 1e18 ) ,
64
- ] {
65
- if self . 0 < max {
66
- f. write_fmt ( format_args ! ( "{}" , NbAndDecimal :: new( self . 0 / divider) ) ) ?;
67
- return f. write_str ( unit) ;
68
- }
43
+ ( self . 0 as u32 ) . write_into ( dest) ?;
44
+ dest. write_str ( " B" )
45
+ } else if let Some ( ( prefix, factor) ) = SI_PREFIXES
46
+ . iter ( )
47
+ . copied ( )
48
+ . find_map ( |( prefix_factor, max) | ( self . 0 < max) . then_some ( prefix_factor) )
49
+ {
50
+ // u32 is big enough to hold the number 999_999
51
+ let scaled = ( self . 0 * factor) as u32 ;
52
+ ( scaled / 100 ) . write_into ( dest) ?;
53
+ dest. write_str ( format_frac ( & mut MaybeUninit :: uninit ( ) , prefix, scaled) )
54
+ } else {
55
+ too_big ( self . 0 , dest)
69
56
}
70
- f. write_fmt ( format_args ! ( "{} YB" , NbAndDecimal :: new( self . 0 / 1e21 ) ) )
71
57
}
72
58
}
73
59
60
+ /// Formats `buffer` to contain the decimal point, decimal places and unit
61
+ fn format_frac ( buffer : & mut MaybeUninit < [ u8 ; 8 ] > , prefix : u8 , scaled : u32 ) -> & str {
62
+ // LLVM generates better byte code for register sized buffers, so we add some NULs
63
+ let buffer = buffer. write ( * b"..0 kB\0 \0 " ) ;
64
+ buffer[ 4 ] = prefix;
65
+
66
+ let frac = scaled % 100 ;
67
+ let buffer = if frac == 0 {
68
+ & buffer[ 3 ..6 ]
69
+ } else if frac % 10 == 0 {
70
+ // the decimal separator '.' is already contained in buffer[1]
71
+ buffer[ 2 ] = b'0' + ( frac / 10 ) as u8 ;
72
+ & buffer[ 1 ..6 ]
73
+ } else {
74
+ // the decimal separator '.' is already contained in buffer[0]
75
+ buffer[ 1 ] = b'0' + ( frac / 10 ) as u8 ;
76
+ buffer[ 2 ] = b'0' + ( frac % 10 ) as u8 ;
77
+ & buffer[ 0 ..6 ]
78
+ } ;
79
+ // SAFETY: we know that the buffer contains only ASCII data
80
+ unsafe { from_utf8_unchecked ( buffer) }
81
+ }
82
+
83
+ #[ cold]
84
+ fn too_big < W : fmt:: Write + ?Sized > ( value : f32 , dest : & mut W ) -> fmt:: Result {
85
+ // the value exceeds 999 QB, so we omit the decimal places
86
+ write ! ( dest, "{:.0} QB" , value / 1e30 )
87
+ }
88
+
89
+ /// `((si_prefix, factor), limit)`, the factor is offset by 10**2 to account for 2 decimal places
90
+ const SI_PREFIXES : & [ ( ( u8 , f32 ) , f32 ) ] = & [
91
+ ( ( b'k' , 1e-1 ) , 1e6 ) ,
92
+ ( ( b'M' , 1e-4 ) , 1e9 ) ,
93
+ ( ( b'G' , 1e-7 ) , 1e12 ) ,
94
+ ( ( b'T' , 1e-10 ) , 1e15 ) ,
95
+ ( ( b'P' , 1e-13 ) , 1e18 ) ,
96
+ ( ( b'E' , 1e-16 ) , 1e21 ) ,
97
+ ( ( b'Z' , 1e-19 ) , 1e24 ) ,
98
+ ( ( b'Y' , 1e-22 ) , 1e27 ) ,
99
+ ( ( b'R' , 1e-25 ) , 1e30 ) ,
100
+ ( ( b'Q' , 1e-28 ) , 1e33 ) ,
101
+ ] ;
102
+
74
103
#[ test]
75
104
#[ allow( clippy:: needless_borrows_for_generic_args) ]
76
105
fn test_filesizeformat ( ) {
77
106
assert_eq ! ( filesizeformat( 0. ) . unwrap( ) . to_string( ) , "0 B" ) ;
78
107
assert_eq ! ( filesizeformat( 999. ) . unwrap( ) . to_string( ) , "999 B" ) ;
79
- assert_eq ! ( filesizeformat( 1000. ) . unwrap( ) . to_string( ) , "1 KB" ) ;
80
- assert_eq ! ( filesizeformat( 1023. ) . unwrap( ) . to_string( ) , "1.02 KB" ) ;
81
- assert_eq ! ( filesizeformat( 1024. ) . unwrap( ) . to_string( ) , "1.02 KB" ) ;
108
+ assert_eq ! ( filesizeformat( 1000. ) . unwrap( ) . to_string( ) , "1 kB" ) ;
109
+ assert_eq ! ( filesizeformat( 1023. ) . unwrap( ) . to_string( ) , "1.02 kB" ) ;
110
+ assert_eq ! ( filesizeformat( 1024. ) . unwrap( ) . to_string( ) , "1.02 kB" ) ;
111
+ assert_eq ! ( filesizeformat( 1100. ) . unwrap( ) . to_string( ) , "1.1 kB" ) ;
82
112
assert_eq ! ( filesizeformat( 9_499_014. ) . unwrap( ) . to_string( ) , "9.49 MB" ) ;
83
113
assert_eq ! (
84
114
filesizeformat( 954_548_589.2 ) . unwrap( ) . to_string( ) ,
0 commit comments