ubyte/byte_unit.rs
1/// A unit of bytes with saturating `const` constructors and arithmetic.
2///
3/// # Overview
4///
5/// A `ByteUnit` represents a unit, a count, a number, of bytes. All operations
6/// on a `ByteUnit` -- constructors, arithmetic, conversions -- saturate.
7/// Overflow, underflow, and divide-by-zero are impossible. See the [top-level
8/// documentation](./index.html) for more.
9///
10/// [`ToByteUnit`] provides human-friendly methods on all integer types for
11/// converting into a `ByteUnit`: [`512.megabytes()`](ToByteUnit::megabytes).
12///
13/// # Parsing
14///
15/// `ByteUnit` implements `FromStr` for parsing byte unit strings into a
16/// `ByteUnit`. The grammar accepted by the parser is:
17///
18/// ```ebnf
19/// byte_unit := uint+ ('.' uint+)? WHITESPACE* suffix
20///
21/// uint := '0'..'9'
22/// suffix := case insensitive SI byte unit suffix ('b' to 'eib')
23/// WHITESPACE := the ' ' character
24/// ```
25///
26/// ```rust
27/// use ubyte::{ByteUnit, ToByteUnit};
28///
29/// let one_gib: ByteUnit = "1GiB".parse().unwrap();
30/// assert_eq!(one_gib, 1.gibibytes());
31///
32/// let quarter_mb: ByteUnit = "256 kB".parse().unwrap();
33/// assert_eq!(quarter_mb, 256.kilobytes());
34///
35/// let half_mb: ByteUnit = "0.5MB".parse().unwrap();
36/// assert_eq!(half_mb, 500.kilobytes());
37///
38/// let half_mib: ByteUnit = "0.500 mib".parse().unwrap();
39/// assert_eq!(half_mib, 512.kibibytes());
40///
41/// let some_mb: ByteUnit = "20.5MB".parse().unwrap();
42/// assert_eq!(some_mb, 20.megabytes() + 500.kilobytes());
43/// ```
44///
45/// # (De)serialization
46///
47/// With the `serde` feaure enabled (disabled by default), `ByteUnit` implements
48/// [`Deserialize`](#impl-Deserialize<%27de>) from strings, using the same
49/// grammar as the `FromStr` implementation, defined above, as well as all
50/// integer types. The [`Serialize`](struct.ByteUnit.html#impl-Serialize)
51/// implementation serializes into a `u64`.
52///
53/// # Example
54///
55/// ```rust
56/// use ubyte::{ByteUnit, ToByteUnit};
57///
58/// // Construct with unit-valued associated constants, `const` constructors, or
59/// // human-friendly methods from the `ToByteUnit` integer extension trait.
60/// const HALF_GB: ByteUnit = ByteUnit::Megabyte(500);
61/// const HALF_GIB: ByteUnit = ByteUnit::Mebibyte(512);
62/// let half_gb = 500 * ByteUnit::MB;
63/// let half_gib = 512 * ByteUnit::MiB;
64/// let half_gb = 500.megabytes();
65/// let half_gib = 512.mebibytes();
66///
67/// // All arithmetic operations and conversions saturate.
68/// let exbibyte = ByteUnit::Exbibyte(1);
69/// let exbibyte_too_large_a = 1024 * ByteUnit::EiB;
70/// let exbibyte_too_large_b = ByteUnit::Exbibyte(1024);
71/// let exbibyte_too_large_c = 1024.exbibytes();
72/// let div_by_zero = 1024.exbibytes() / 0;
73/// let too_small = 1000.megabytes() - 1.gibibytes();
74/// assert_eq!(exbibyte << 4, ByteUnit::max_value());
75/// assert_eq!(exbibyte << 10, ByteUnit::max_value());
76/// assert_eq!(exbibyte_too_large_a, ByteUnit::max_value());
77/// assert_eq!(exbibyte_too_large_b, ByteUnit::max_value());
78/// assert_eq!(exbibyte_too_large_c, ByteUnit::max_value());
79/// assert_eq!(div_by_zero, ByteUnit::max_value());
80/// assert_eq!(too_small, 0);
81/// ```
82#[repr(transparent)]
83#[derive(Debug, Default, Copy, Clone, Eq, Hash, Ord)]
84pub struct ByteUnit(pub(crate) u64);
85
86macro_rules! rem_and_suffix {
87 ($n:expr => $(($isuffix:ident, $suffix:ident)),+ $or_else:ident) => {
88 loop {
89 $(
90 let i_val = ByteUnit::$isuffix.as_u64();
91 let s_val = ByteUnit::$suffix.as_u64();
92
93 if $n >= s_val {
94 let (u_val, unit, string) = if $n % s_val >= i_val - s_val {
95 (i_val, ByteUnit::$isuffix, stringify!($isuffix))
96 } else {
97 (s_val, ByteUnit::$suffix, stringify!($suffix))
98 };
99
100 break ($n / u_val, ($n % u_val) as f64 / u_val as f64, string, unit)
101 }
102 )+
103
104 break ($n, 0f64, stringify!($or_else), ByteUnit::$or_else)
105 }
106 };
107}
108
109macro_rules! const_if {
110 ($cond:expr, $on_true:expr, $on_false:expr) => (
111 [$on_false, $on_true][$cond as usize]
112 )
113}
114
115macro_rules! constructor_fns {
116 ($($sstr:expr, $nstr:expr, $example:expr, $suffix:ident, $name:ident = $size:expr),*) => (
117 $(
118 /// Number of bytes in 1
119 #[doc = $sstr]
120 /// (`
121 #[doc = $nstr]
122 /// `).
123 #[allow(non_upper_case_globals)]
124 pub const $suffix: ByteUnit = ByteUnit::$name(1);
125 )*
126
127 $(
128 /// Constructs a `ByteUnit` representing `n`
129 #[doc = $sstr]
130 /// .
131 ///
132 /// # Example
133 ///
134 /// ```rust
135 /// # use ubyte::ByteUnit;
136 #[doc = $example]
137 /// ```
138 #[allow(non_snake_case)]
139 pub const fn $name(n: u64) -> ByteUnit {
140 let size: u64 = $size;
141 let v = const_if!(n as u128 * size as u128 > u64::max_value() as u128,
142 ByteUnit::max_value().as_u128(),
143 n as u128 * size as u128
144 );
145
146 ByteUnit(v as u64)
147 }
148 )*
149 );
150
151 ($($suffix:ident, $name:ident = $size:expr),* $(,)?) => (
152 constructor_fns!($(
153 stringify!($suffix), stringify!($size), concat!(
154 "assert_eq!(ByteUnit::", stringify!($name), "(10), ",
155 "10 * ByteUnit::", stringify!($suffix), ");"
156 ), $suffix, $name = $size
157 ),*);
158 )
159}
160
161impl ByteUnit {
162 constructor_fns! {
163 B, Byte = 1,
164 kB, Kilobyte = 1_000,
165 KiB, Kibibyte = 1 << 10,
166 MB, Megabyte = 1_000_000,
167 MiB, Mebibyte = 1 << 20,
168 GB, Gigabyte = 1_000_000_000,
169 GiB, Gibibyte = 1 << 30,
170 TB, Terabyte = 1_000_000_000_000,
171 TiB, Tebibyte = 1 << 40,
172 PB, Petabyte = 1_000_000_000_000_000,
173 PiB, Pebibyte = 1 << 50,
174 EB, Exabyte = 1_000_000_000_000_000_000,
175 EiB, Exbibyte = 1 << 60,
176 }
177
178 /// The maximum value of bytes representable by `ByteUnit`.
179 ///
180 /// # Example
181 ///
182 /// ```rust
183 /// # use ubyte::ByteUnit;
184 /// assert_eq!(ByteUnit::max_value(), u64::max_value());
185 /// ```
186 pub const fn max_value() -> ByteUnit {
187 ByteUnit(u64::max_value())
188 }
189
190 /// Returns the value of bytes represented by `self` as a `u64`.
191 ///
192 /// # Example
193 ///
194 /// ```rust
195 /// # use ubyte::ByteUnit;
196 /// let int: u64 = ByteUnit::Gigabyte(4).as_u64();
197 /// assert_eq!(int, 4 * ByteUnit::GB);
198 ///
199 /// assert_eq!(ByteUnit::Megabyte(42).as_u64(), 42 * 1_000_000);
200 /// assert_eq!(ByteUnit::Exbibyte(7).as_u64(), 7 * 1 << 60);
201 /// ```
202 pub const fn as_u64(self) -> u64 {
203 self.0
204 }
205
206 /// Returns the value of bytes represented by `self` as a `u128`.
207 ///
208 /// # Example
209 ///
210 /// ```rust
211 /// # use ubyte::ByteUnit;
212 /// let int: u128 = ByteUnit::Gigabyte(4).as_u128();
213 /// assert_eq!(int, 4 * ByteUnit::GB);
214 ///
215 /// assert_eq!(ByteUnit::Megabyte(42).as_u64(), 42 * 1_000_000);
216 /// assert_eq!(ByteUnit::Exbibyte(7).as_u64(), 7 * 1 << 60);
217 /// ```
218 pub const fn as_u128(self) -> u128 {
219 self.0 as u128
220 }
221
222 /// Returns the components of the minimal unit representation of `self`.
223 ///
224 /// The "minimal unit representation" is the representation that maximizes
225 /// the SI-unit while minimizing the whole part of the value. For example,
226 /// `1024.bytes()` is minimally represented by `1KiB`, while `1023.bytes()`
227 /// is minimally represented by `1.023kB`.
228 ///
229 /// The four components returned, in tuple-order, are:
230 /// * `whole` - the whole part of the minimal representation.
231 /// * `frac` - the fractional part of the minimal representation.
232 /// * `suffix` - the suffix of the minimal representation.
233 /// * `unit` - the `1`-unit of the minimal representation.
234 ///
235 /// Succinctly, this is: `(whole, frac, suffix, unit)`. Observe that `(whole
236 /// + frac) * unit` reconstructs the original value.
237 ///
238 /// # Example
239 ///
240 /// ```rust
241 /// use ubyte::{ByteUnit, ToByteUnit};
242 ///
243 /// let value = 2.mebibytes() + 512.kibibytes();
244 /// assert_eq!(value.to_string(), "2.50MiB");
245 ///
246 /// let (whole, frac, suffix, unit) = value.repr();
247 /// assert_eq!(whole, 2);
248 /// assert_eq!(frac, 0.5);
249 /// assert_eq!(suffix, "MiB");
250 /// assert_eq!(unit, ByteUnit::MiB);
251 ///
252 /// let reconstructed = (whole as f64 + frac) * unit.as_u64() as f64;
253 /// assert_eq!(reconstructed as u64, value);
254 /// ```
255 pub fn repr(self) -> (u64, f64, &'static str, ByteUnit) {
256 rem_and_suffix! { self.as_u64() =>
257 (EiB, EB), (TiB, TB), (GiB, GB), (MiB, MB), (KiB, kB) B
258 }
259 }
260}
261
262impl From<ByteUnit> for u64 {
263 #[inline(always)]
264 fn from(v: ByteUnit) -> Self {
265 v.as_u64()
266 }
267}
268
269impl From<ByteUnit> for u128 {
270 #[inline(always)]
271 fn from(v: ByteUnit) -> Self {
272 v.as_u128()
273 }
274}
275
276macro_rules! impl_from_int_unknown {
277 ($T:ty) => (
278 impl From<$T> for ByteUnit {
279 #[inline(always)]
280 fn from(value: $T) -> Self {
281 if core::mem::size_of::<$T>() <= core::mem::size_of::<i64>() {
282 ByteUnit::from(value as i64)
283 } else if value <= i64::max_value() as $T {
284 ByteUnit::from(value as i64)
285 } else {
286 ByteUnit::max_value()
287 }
288 }
289 }
290 )
291}
292
293macro_rules! impl_from_uint_unknown {
294 ($T:ty) => (
295 impl From<$T> for ByteUnit {
296 #[inline(always)]
297 fn from(value: $T) -> Self {
298 if core::mem::size_of::<$T>() <= core::mem::size_of::<u64>() {
299 ByteUnit(value as u64)
300 } else if value <= u64::max_value() as $T {
301 ByteUnit(value as u64)
302 } else {
303 ByteUnit::max_value()
304 }
305 }
306 }
307 )
308}
309
310macro_rules! impl_from_unsigned {
311 ($T:ty) => (
312 impl From<$T> for ByteUnit {
313 #[inline(always)] fn from(v: $T) -> Self { ByteUnit(v as u64) }
314 }
315 )
316}
317
318macro_rules! impl_from_signed {
319 ($T:ty) => (
320 impl From<$T> for ByteUnit {
321 #[inline(always)] fn from(v: $T) -> Self {
322 ByteUnit(core::cmp::max(v, 0) as u64)
323 }
324 }
325 )
326}
327
328impl_from_unsigned!(u8);
329impl_from_unsigned!(u16);
330impl_from_unsigned!(u32);
331impl_from_unsigned!(u64);
332
333impl_from_signed!(i8);
334impl_from_signed!(i16);
335impl_from_signed!(i32);
336impl_from_signed!(i64);
337
338impl_from_uint_unknown!(usize);
339impl_from_uint_unknown!(u128);
340impl_from_int_unknown!(isize);
341impl_from_int_unknown!(i128);
342
343macro_rules! helper_fn {
344 ($kindstr:expr, $name:ident = $kind:ident) => (
345 /// Converts `self` to a `ByteUnit` representing `self`
346 #[doc = $kindstr]
347 /// .
348 #[inline(always)]
349 fn $name(self) -> ByteUnit {
350 self.bytes() * ByteUnit::$kind
351 }
352 );
353
354 ($name:ident = $kind:ident) => (
355 helper_fn!(stringify!($kind), $name = $kind);
356 )
357}
358
359/// Extension trait for conversion from integer types to [`ByteUnit`].
360///
361/// The `ToByteUnit` trait provides methods on integer types that convert the
362/// integer type into the [`ByteUnit`] unit represented by the method name. To
363/// use the trait, simply import it. The trait is implemented for all integer
364/// types.
365///
366/// As with all other `ByteUnit` operations, conversions saturate.
367///
368/// # Example
369///
370/// ```rust
371/// use ubyte::ToByteUnit;
372///
373/// assert_eq!(512.kilobytes(), 512000.bytes());
374/// assert_eq!(512.kibibytes(), 524288.bytes());
375/// assert_eq!(512.kilobytes(), 512 * 1.kilobytes());
376///
377/// assert_eq!(1000.bytes(), 1.kilobytes());
378/// assert_eq!(1000.bytes() + 24, 1.kibibytes());
379/// assert_eq!(2048.mebibytes(), 2.gibibytes());
380///
381/// assert!(2.megabytes() + 500.kilobytes() > 2.mebibytes());
382/// assert!(2.pebibytes() > 2.petabytes());
383///
384/// // As with other `ByteUnit` operations, conversions saturate.
385/// assert_eq!((1 << 10).exbibytes(), (1 << 20).exbibytes());
386/// ```
387pub trait ToByteUnit: Into<ByteUnit> {
388 /// Converts `self` to a `ByteUnit` representing `self` bytes.
389 #[inline(always)]
390 fn bytes(self) -> ByteUnit {
391 self.into()
392 }
393
394 helper_fn!(kilobytes = kB);
395 helper_fn!(kibibytes = KiB);
396 helper_fn!(megabytes = MB);
397 helper_fn!(mebibytes = MiB);
398 helper_fn!(gigabytes = GB);
399 helper_fn!(gibibytes = GiB);
400 helper_fn!(terabytes = TB);
401 helper_fn!(tibibytes = TiB);
402 helper_fn!(petabytes = PB);
403 helper_fn!(pebibytes = PiB);
404 helper_fn!(exabytes = EB);
405 helper_fn!(exbibytes = EiB);
406}
407
408impl<T: Into<ByteUnit> + Copy> ToByteUnit for T {}
409
410/// Display `self` as best as possible. For perfectly custom display output,
411/// consider using [`ByteUnit::repr()`].
412///
413/// # Example
414///
415/// ```rust
416/// use ubyte::{ByteUnit, ToByteUnit};
417///
418/// assert_eq!(323.kilobytes().to_string(), "323kB");
419/// assert_eq!(3.megabytes().to_string(), "3MB");
420/// assert_eq!(3.mebibytes().to_string(), "3MiB");
421///
422/// assert_eq!((3.mebibytes() + 140.kilobytes()).to_string(), "3.13MiB");
423/// assert_eq!((3.mebibytes() + 2.mebibytes()).to_string(), "5MiB");
424/// assert_eq!((7.gigabytes() + 58.mebibytes() + 3.kilobytes()).to_string(), "7.06GB");
425/// assert_eq!((7.gibibytes() + 920.mebibytes()).to_string(), "7.90GiB");
426/// assert_eq!(7231.kilobytes().to_string(), "6.90MiB");
427///
428/// assert_eq!(format!("{:.0}", 7.gibibytes() + 920.mebibytes()), "8GiB");
429/// assert_eq!(format!("{:.1}", 7.gibibytes() + 920.mebibytes()), "7.9GiB");
430/// assert_eq!(format!("{:.2}", 7.gibibytes() + 920.mebibytes()), "7.90GiB");
431/// assert_eq!(format!("{:.3}", 7.gibibytes() + 920.mebibytes()), "7.898GiB");
432/// assert_eq!(format!("{:.4}", 7.gibibytes() + 920.mebibytes()), "7.8984GiB");
433/// assert_eq!(format!("{:.4}", 7231.kilobytes()), "6.8960MiB");
434/// assert_eq!(format!("{:.0}", 7231.kilobytes()), "7MiB");
435/// assert_eq!(format!("{:.2}", 999.kilobytes() + 990.bytes()), "976.55KiB");
436/// assert_eq!(format!("{:.0}", 999.kilobytes() + 990.bytes()), "1MB");
437///
438/// assert_eq!(format!("{:04.2}", 999.kilobytes() + 990.bytes()), "0976.55KiB");
439/// assert_eq!(format!("{:02.0}", 999.kilobytes() + 990.bytes()), "01MB");
440/// assert_eq!(format!("{:04.0}", 999.kilobytes() + 990.bytes()), "0001MB");
441/// ```
442impl core::fmt::Display for ByteUnit {
443 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
444 let (whole, rem, suffix, unit) = self.repr();
445 let width = f.width().unwrap_or(0);
446 if rem != 0f64 && f.precision().map(|p| p > 0).unwrap_or(true) {
447 let p = f.precision().unwrap_or(2);
448 let k = 10u64.saturating_pow(p as u32) as f64;
449 write!(f, "{:0width$}.{:0p$.0}{}", whole, rem * k, suffix,
450 p = p, width = width)
451 } else if rem > 0.5f64 {
452 ((whole.bytes() + 1) * unit).fmt(f)
453 } else {
454 write!(f, "{:0width$}{}", whole, suffix, width = width)
455 }
456 }
457}