human_units/
size_format.rs1use core::fmt::Display;
2use core::fmt::Formatter;
3use core::fmt::Write;
4
5use crate::Buffer;
6use crate::Size;
7
8pub struct FormattedSize {
15 pub unit: &'static str,
17 pub integer: u16,
19 pub fraction: u8,
21}
22
23impl Display for FormattedSize {
24 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
25 let mut buf = Buffer::<MAX_LEN>::new();
26 buf.write_u64(self.integer as u64);
27 if self.fraction != 0 {
28 buf.write_byte(b'.');
29 buf.write_byte(b'0' + self.fraction);
30 }
31 buf.write_byte(b' ');
32 buf.write_str(self.unit)?;
33 f.write_str(unsafe { buf.as_str() })
34 }
35}
36
37const MAX_LEN: usize = 10;
38
39pub trait FormatSize {
44 fn format_size(self) -> FormattedSize;
46}
47
48impl FormatSize for u64 {
49 fn format_size(self) -> FormattedSize {
50 let mut i = 0;
51 let mut scale = 1;
52 let mut n = self;
53 while n >= 1024 {
54 scale *= 1024;
55 n >>= 10;
56 i += 1;
57 }
58 let mut b = self & (scale - 1);
59 if b != 0 {
60 b = (b * 10_u64) >> (i * 10);
62 }
63 FormattedSize {
64 unit: UNITS[i],
65 integer: n as u16,
66 fraction: b as u8,
67 }
68 }
69}
70
71impl FormatSize for usize {
72 fn format_size(self) -> FormattedSize {
73 FormatSize::format_size(self as u64)
74 }
75}
76
77impl FormatSize for Size {
78 fn format_size(self) -> FormattedSize {
79 FormatSize::format_size(self.0)
80 }
81}
82
83const UNITS: [&str; 7] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
84
85#[cfg(all(test, feature = "std"))]
86mod tests {
87 #![allow(clippy::panic)]
88 use arbitrary::Arbitrary;
89 use arbitrary::Unstructured;
90 use arbtest::arbtest;
91
92 use super::*;
93 use crate::FormatSize;
94
95 #[test]
96 fn test_format_bytes() {
97 assert_eq!("512 B", 512_u64.format_size().to_string());
98 assert_eq!("0 B", 0_u64.format_size().to_string());
99 assert_eq!("1 B", 1_u64.format_size().to_string());
100 assert_eq!("1 KiB", 1024_u64.format_size().to_string());
101 assert_eq!("512 KiB", (512_u64 * 1024).format_size().to_string());
102 assert_eq!("1023 B", 1023_u64.format_size().to_string());
103 assert_eq!("1023 KiB", (1023_u64 * 1024).format_size().to_string());
104 assert_eq!("1 MiB", (1024_u64 * 1024).format_size().to_string());
105 assert_eq!("1 GiB", (1024_u64 * 1024 * 1024).format_size().to_string());
106 assert_eq!(
107 "1023 MiB",
108 (1024_u64 * 1024 * 1023).format_size().to_string()
109 );
110 assert_eq!(
111 "3.5 GiB",
112 (1024_u64 * 1024 * 1024 * 3 + 1024_u64 * 1024 * 1024 / 2)
113 .format_size()
114 .to_string()
115 );
116 assert_eq!("3.9 GiB", (u32::MAX as u64).format_size().to_string());
117 assert_eq!("15.9 EiB", u64::MAX.format_size().to_string());
118 }
119
120 #[test]
121 fn test_format_bytes_arbitrary() {
122 arbtest(|u| {
123 let expected: u64 = u.arbitrary()?;
124 let bytes = expected.format_size();
125 let x = unit_to_factor(bytes.unit);
126 let actual = (bytes.integer as u64) * x + (bytes.fraction as u64) * x / 10;
127 assert!(
128 expected >= actual && (expected - actual) < x,
129 "expected = {expected}, actual = {actual}"
130 );
131 Ok(())
132 });
133 }
134
135 #[test]
136 fn test_shift_division() {
137 arbtest(|u| {
138 let number: u64 = u.arbitrary()?;
139 let expected = number / 1024;
140 let actual = number >> 10;
141 assert_eq!(expected, actual);
142 Ok(())
143 });
144 }
145
146 #[test]
147 fn test_shift_remainder() {
148 arbtest(|u| {
149 let number: u64 = u.arbitrary()?;
150 let expected = number % 1024;
151 let actual = number & (1024 - 1);
152 assert_eq!(expected, actual);
153 Ok(())
154 });
155 }
156
157 #[test]
158 fn test_formatted_size_io() {
159 arbtest(|u| {
160 let expected: FormattedSize = u.arbitrary()?;
161 let string = expected.to_string();
162 let mut words = string.splitn(2, ' ');
163 let number_str = words.next().unwrap();
164 let unit = words.next().unwrap().to_string();
165 let mut words = number_str.splitn(2, '.');
166 let integer: u16 = words.next().unwrap().parse().unwrap();
167 let fraction: u8 = match words.next() {
168 Some(word) => word.parse().unwrap(),
169 None => 0,
170 };
171 assert_eq!(expected.integer, integer);
172 assert_eq!(expected.fraction, fraction);
173 assert_eq!(expected.unit, unit, "expected = `{expected}`");
174 Ok(())
175 });
176 }
177
178 impl<'a> Arbitrary<'a> for FormattedSize {
179 fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
180 Ok(Self {
181 unit: *u.choose(&UNITS[..])?,
182 integer: u.int_in_range(0..=MAX_INTEGER)?,
183 fraction: u.int_in_range(0..=9)?,
184 })
185 }
186 }
187
188 fn unit_to_factor(unit: &str) -> u64 {
189 match unit {
190 "B" => 1_u64,
191 "KiB" => 1024_u64,
192 "MiB" => 1024_u64.pow(2),
193 "GiB" => 1024_u64.pow(3),
194 "TiB" => 1024_u64.pow(4),
195 "PiB" => 1024_u64.pow(5),
196 "EiB" => 1024_u64.pow(6),
197 _ => panic!("unknown unit `{unit}`"),
198 }
199 }
200
201 const MAX_INTEGER: u16 = 1023;
202}