bytesize/
lib.rs

1//! `ByteSize` is a semantic wrapper for byte count representations.
2//!
3//! Features:
4//!
5//! - Pre-defined constants for various size units (e.g., B, KB, KiB, MB, MiB, ... EB, EiB).
6//! - `ByteSize` type which presents size units convertible to different size units.
7//! - Arithmetic operations for `ByteSize`.
8//! - `FromStr` impl for `ByteSize`, allowing for parsing string size representations like "1.5KiB"
9//!   and "521TiB".
10//! - Serde support for binary and human-readable deserializers like JSON.
11//!
12//! # Examples
13//!
14//! Construction using SI or IEC helpers.
15//!
16//! ```
17//! use bytesize::ByteSize;
18//!
19//! assert!(ByteSize::kib(4) > ByteSize::kb(4));
20//! ```
21//!
22//! Display as human-readable string.
23//!
24//! ```
25//! use bytesize::ByteSize;
26//!
27//! assert_eq!("518.0 GiB", ByteSize::gib(518).display().iec().to_string());
28//! assert_eq!("556.2 GB", ByteSize::gib(518).display().si().to_string());
29//! assert_eq!("518.0G", ByteSize::gib(518).display().iec_short().to_string());
30//! ```
31//!
32//! Arithmetic operations are supported.
33//!
34//! ```
35//! use bytesize::ByteSize;
36//!
37//! let plus = ByteSize::mb(1) + ByteSize::kb(100);
38//! println!("{plus}");
39//!
40//! let minus = ByteSize::tb(1) - ByteSize::gb(4);
41//! assert_eq!(ByteSize::gb(996), minus);
42//! ```
43
44#![cfg_attr(not(feature = "std"), no_std)]
45
46extern crate alloc;
47
48use alloc::string::ToString as _;
49use core::{fmt, ops};
50
51#[cfg(feature = "arbitrary")]
52mod arbitrary;
53mod display;
54mod parse;
55#[cfg(feature = "serde")]
56mod serde;
57
58pub use crate::display::Display;
59use crate::display::Format;
60
61/// Number of bytes in 1 kilobyte.
62pub const KB: u64 = 1_000;
63/// Number of bytes in 1 megabyte.
64pub const MB: u64 = 1_000_000;
65/// Number of bytes in 1 gigabyte.
66pub const GB: u64 = 1_000_000_000;
67/// Number of bytes in 1 terabyte.
68pub const TB: u64 = 1_000_000_000_000;
69/// Number of bytes in 1 petabyte.
70pub const PB: u64 = 1_000_000_000_000_000;
71/// Number of bytes in 1 exabyte.
72pub const EB: u64 = 1_000_000_000_000_000_000;
73
74/// Number of bytes in 1 kibibyte.
75pub const KIB: u64 = 1_024;
76/// Number of bytes in 1 mebibyte.
77pub const MIB: u64 = 1_048_576;
78/// Number of bytes in 1 gibibyte.
79pub const GIB: u64 = 1_073_741_824;
80/// Number of bytes in 1 tebibyte.
81pub const TIB: u64 = 1_099_511_627_776;
82/// Number of bytes in 1 pebibyte.
83pub const PIB: u64 = 1_125_899_906_842_624;
84/// Number of bytes in 1 exbibyte.
85pub const EIB: u64 = 1_152_921_504_606_846_976;
86
87/// IEC (binary) units.
88///
89/// See <https://en.wikipedia.org/wiki/Kilobyte>.
90const UNITS_IEC: &str = "KMGTPE";
91
92/// SI (decimal) units.
93///
94/// See <https://en.wikipedia.org/wiki/Kilobyte>.
95const UNITS_SI: &str = "kMGTPE";
96
97/// `ln(1024) ~= 6.931`
98const LN_KIB: f64 = 6.931_471_805_599_453;
99
100/// `ln(1000) ~= 6.908`
101const LN_KB: f64 = 6.907_755_278_982_137;
102
103/// Converts a quantity of kilobytes to bytes.
104pub fn kb(size: impl Into<u64>) -> u64 {
105    size.into() * KB
106}
107
108/// Converts a quantity of kibibytes to bytes.
109pub fn kib<V: Into<u64>>(size: V) -> u64 {
110    size.into() * KIB
111}
112
113/// Converts a quantity of megabytes to bytes.
114pub fn mb<V: Into<u64>>(size: V) -> u64 {
115    size.into() * MB
116}
117
118/// Converts a quantity of mebibytes to bytes.
119pub fn mib<V: Into<u64>>(size: V) -> u64 {
120    size.into() * MIB
121}
122
123/// Converts a quantity of gigabytes to bytes.
124pub fn gb<V: Into<u64>>(size: V) -> u64 {
125    size.into() * GB
126}
127
128/// Converts a quantity of gibibytes to bytes.
129pub fn gib<V: Into<u64>>(size: V) -> u64 {
130    size.into() * GIB
131}
132
133/// Converts a quantity of terabytes to bytes.
134pub fn tb<V: Into<u64>>(size: V) -> u64 {
135    size.into() * TB
136}
137
138/// Converts a quantity of tebibytes to bytes.
139pub fn tib<V: Into<u64>>(size: V) -> u64 {
140    size.into() * TIB
141}
142
143/// Converts a quantity of petabytes to bytes.
144pub fn pb<V: Into<u64>>(size: V) -> u64 {
145    size.into() * PB
146}
147
148/// Converts a quantity of pebibytes to bytes.
149pub fn pib<V: Into<u64>>(size: V) -> u64 {
150    size.into() * PIB
151}
152
153/// Converts a quantity of exabytes to bytes.
154pub fn eb<V: Into<u64>>(size: V) -> u64 {
155    size.into() * EB
156}
157
158/// Converts a quantity of exbibytes to bytes.
159pub fn eib<V: Into<u64>>(size: V) -> u64 {
160    size.into() * EIB
161}
162
163/// Byte size representation.
164#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
165pub struct ByteSize(pub u64);
166
167impl ByteSize {
168    /// Constructs a byte size wrapper from a quantity of bytes.
169    #[inline(always)]
170    pub const fn b(size: u64) -> ByteSize {
171        ByteSize(size)
172    }
173
174    /// Constructs a byte size wrapper from a quantity of kilobytes.
175    #[inline(always)]
176    pub const fn kb(size: u64) -> ByteSize {
177        ByteSize(size * KB)
178    }
179
180    /// Constructs a byte size wrapper from a quantity of kibibytes.
181    #[inline(always)]
182    pub const fn kib(size: u64) -> ByteSize {
183        ByteSize(size * KIB)
184    }
185
186    /// Constructs a byte size wrapper from a quantity of megabytes.
187    #[inline(always)]
188    pub const fn mb(size: u64) -> ByteSize {
189        ByteSize(size * MB)
190    }
191
192    /// Constructs a byte size wrapper from a quantity of mebibytes.
193    #[inline(always)]
194    pub const fn mib(size: u64) -> ByteSize {
195        ByteSize(size * MIB)
196    }
197
198    /// Constructs a byte size wrapper from a quantity of gigabytes.
199    #[inline(always)]
200    pub const fn gb(size: u64) -> ByteSize {
201        ByteSize(size * GB)
202    }
203
204    /// Constructs a byte size wrapper from a quantity of gibibytes.
205    #[inline(always)]
206    pub const fn gib(size: u64) -> ByteSize {
207        ByteSize(size * GIB)
208    }
209
210    /// Constructs a byte size wrapper from a quantity of terabytes.
211    #[inline(always)]
212    pub const fn tb(size: u64) -> ByteSize {
213        ByteSize(size * TB)
214    }
215
216    /// Constructs a byte size wrapper from a quantity of tebibytes.
217    #[inline(always)]
218    pub const fn tib(size: u64) -> ByteSize {
219        ByteSize(size * TIB)
220    }
221
222    /// Constructs a byte size wrapper from a quantity of petabytes.
223    #[inline(always)]
224    pub const fn pb(size: u64) -> ByteSize {
225        ByteSize(size * PB)
226    }
227
228    /// Constructs a byte size wrapper from a quantity of pebibytes.
229    #[inline(always)]
230    pub const fn pib(size: u64) -> ByteSize {
231        ByteSize(size * PIB)
232    }
233
234    /// Constructs a byte size wrapper from a quantity of exabytes.
235    #[inline(always)]
236    pub const fn eb(size: u64) -> ByteSize {
237        ByteSize(size * EB)
238    }
239
240    /// Constructs a byte size wrapper from a quantity of exbibytes.
241    #[inline(always)]
242    pub const fn eib(size: u64) -> ByteSize {
243        ByteSize(size * EIB)
244    }
245
246    /// Returns byte count.
247    #[inline(always)]
248    pub const fn as_u64(&self) -> u64 {
249        self.0
250    }
251
252    /// Returns a formatting display wrapper.
253    pub fn display(&self) -> Display {
254        Display {
255            byte_size: *self,
256            format: Format::Iec,
257        }
258    }
259}
260
261impl fmt::Display for ByteSize {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        let display = self.display();
264
265        if f.width().is_none() {
266            // allocation-free fast path for when no formatting options are specified
267            fmt::Display::fmt(&display, f)
268        } else {
269            f.pad(&display.to_string())
270        }
271    }
272}
273
274impl fmt::Debug for ByteSize {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        write!(f, "{} ({} bytes)", self, self.0)
277    }
278}
279
280macro_rules! commutative_op {
281    ($t:ty) => {
282        impl ops::Add<ByteSize> for $t {
283            type Output = ByteSize;
284            #[inline(always)]
285            fn add(self, rhs: ByteSize) -> ByteSize {
286                ByteSize(rhs.0 + (self as u64))
287            }
288        }
289
290        impl ops::Mul<ByteSize> for $t {
291            type Output = ByteSize;
292            #[inline(always)]
293            fn mul(self, rhs: ByteSize) -> ByteSize {
294                ByteSize(rhs.0 * (self as u64))
295            }
296        }
297    };
298}
299
300commutative_op!(u64);
301commutative_op!(u32);
302commutative_op!(u16);
303commutative_op!(u8);
304
305impl ops::Add<ByteSize> for ByteSize {
306    type Output = ByteSize;
307
308    #[inline(always)]
309    fn add(self, rhs: ByteSize) -> ByteSize {
310        ByteSize(self.0 + rhs.0)
311    }
312}
313
314impl ops::AddAssign<ByteSize> for ByteSize {
315    #[inline(always)]
316    fn add_assign(&mut self, rhs: ByteSize) {
317        self.0 += rhs.0
318    }
319}
320
321impl<T> ops::Add<T> for ByteSize
322where
323    T: Into<u64>,
324{
325    type Output = ByteSize;
326    #[inline(always)]
327    fn add(self, rhs: T) -> ByteSize {
328        ByteSize(self.0 + (rhs.into()))
329    }
330}
331
332impl<T> ops::AddAssign<T> for ByteSize
333where
334    T: Into<u64>,
335{
336    #[inline(always)]
337    fn add_assign(&mut self, rhs: T) {
338        self.0 += rhs.into();
339    }
340}
341
342impl ops::Sub<ByteSize> for ByteSize {
343    type Output = ByteSize;
344
345    #[inline(always)]
346    fn sub(self, rhs: ByteSize) -> ByteSize {
347        ByteSize(self.0 - rhs.0)
348    }
349}
350
351impl ops::SubAssign<ByteSize> for ByteSize {
352    #[inline(always)]
353    fn sub_assign(&mut self, rhs: ByteSize) {
354        self.0 -= rhs.0
355    }
356}
357
358impl<T> ops::Sub<T> for ByteSize
359where
360    T: Into<u64>,
361{
362    type Output = ByteSize;
363    #[inline(always)]
364    fn sub(self, rhs: T) -> ByteSize {
365        ByteSize(self.0 - (rhs.into()))
366    }
367}
368
369impl<T> ops::SubAssign<T> for ByteSize
370where
371    T: Into<u64>,
372{
373    #[inline(always)]
374    fn sub_assign(&mut self, rhs: T) {
375        self.0 -= rhs.into();
376    }
377}
378
379impl<T> ops::Mul<T> for ByteSize
380where
381    T: Into<u64>,
382{
383    type Output = ByteSize;
384    #[inline(always)]
385    fn mul(self, rhs: T) -> ByteSize {
386        ByteSize(self.0 * rhs.into())
387    }
388}
389
390impl<T> ops::MulAssign<T> for ByteSize
391where
392    T: Into<u64>,
393{
394    #[inline(always)]
395    fn mul_assign(&mut self, rhs: T) {
396        self.0 *= rhs.into();
397    }
398}
399
400#[cfg(test)]
401mod property_tests {
402    use alloc::string::{String, ToString as _};
403
404    use super::*;
405
406    impl quickcheck::Arbitrary for ByteSize {
407        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
408            Self(u64::arbitrary(g))
409        }
410    }
411
412    quickcheck::quickcheck! {
413        fn parsing_never_panics(size: String) -> bool {
414            let _ = size.parse::<ByteSize>();
415            true
416        }
417
418        fn to_string_never_blank(size: ByteSize) -> bool {
419            !size.to_string().is_empty()
420        }
421
422        fn to_string_never_large(size: ByteSize) -> bool {
423            size.to_string().len() < 11
424        }
425
426        fn string_round_trip(size: ByteSize) -> bool {
427            // currently fails on many inputs above the pebibyte level
428            if size > ByteSize::pib(1) {
429                return true;
430            }
431
432            size.to_string().parse::<ByteSize>().unwrap() == size
433        }
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use alloc::format;
440
441    use super::*;
442
443    #[test]
444    fn test_arithmetic_op() {
445        let mut x = ByteSize::mb(1);
446        let y = ByteSize::kb(100);
447
448        assert_eq!((x + y).as_u64(), 1_100_000u64);
449
450        assert_eq!((x - y).as_u64(), 900_000u64);
451
452        assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
453
454        assert_eq!((x * 2u64).as_u64(), 2_000_000);
455
456        x += y;
457        assert_eq!(x.as_u64(), 1_100_000);
458        x *= 2u64;
459        assert_eq!(x.as_u64(), 2_200_000);
460    }
461
462    #[allow(clippy::unnecessary_cast)]
463    #[test]
464    fn test_arithmetic_primitives() {
465        let mut x = ByteSize::mb(1);
466
467        assert_eq!((x + MB as u64).as_u64(), 2_000_000);
468        assert_eq!((x + MB as u32).as_u64(), 2_000_000);
469        assert_eq!((x + KB as u16).as_u64(), 1_001_000);
470        assert_eq!((x - MB as u64).as_u64(), 0);
471        assert_eq!((x - MB as u32).as_u64(), 0);
472        assert_eq!((x - KB as u32).as_u64(), 999_000);
473
474        x += MB as u64;
475        x += MB as u32;
476        x += 10u16;
477        x += 1u8;
478        assert_eq!(x.as_u64(), 3_000_011);
479    }
480
481    #[test]
482    fn test_comparison() {
483        assert!(ByteSize::mb(1) == ByteSize::kb(1000));
484        assert!(ByteSize::mib(1) == ByteSize::kib(1024));
485        assert!(ByteSize::mb(1) != ByteSize::kib(1024));
486        assert!(ByteSize::mb(1) < ByteSize::kib(1024));
487        assert!(ByteSize::b(0) < ByteSize::tib(1));
488        assert!(ByteSize::pib(1) < ByteSize::eb(1));
489    }
490
491    #[track_caller]
492    fn assert_display(expected: &str, b: ByteSize) {
493        assert_eq!(expected, format!("{b}"));
494    }
495
496    #[test]
497    fn test_display() {
498        assert_display("215 B", ByteSize::b(215));
499        assert_display("1.0 KiB", ByteSize::kib(1));
500        assert_display("301.0 KiB", ByteSize::kib(301));
501        assert_display("419.0 MiB", ByteSize::mib(419));
502        assert_display("518.0 GiB", ByteSize::gib(518));
503        assert_display("815.0 TiB", ByteSize::tib(815));
504        assert_display("609.0 PiB", ByteSize::pib(609));
505        assert_display("15.0 EiB", ByteSize::eib(15));
506    }
507
508    #[test]
509    fn test_display_alignment() {
510        assert_eq!("|357 B     |", format!("|{:10}|", ByteSize(357)));
511        assert_eq!("|     357 B|", format!("|{:>10}|", ByteSize(357)));
512        assert_eq!("|357 B     |", format!("|{:<10}|", ByteSize(357)));
513        assert_eq!("|  357 B   |", format!("|{:^10}|", ByteSize(357)));
514
515        assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
516        assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
517        assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
518    }
519
520    #[test]
521    fn test_default() {
522        assert_eq!(ByteSize::b(0), ByteSize::default());
523    }
524}