Skip to main content

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, iter, ops};
50
51#[cfg(feature = "arbitrary")]
52mod arbitrary;
53mod display;
54mod parse;
55#[cfg(feature = "serde")]
56mod serde;
57
58pub use self::display::Display;
59use self::display::Format;
60pub use self::parse::{Unit, UnitParseError};
61
62/// Number of bytes in 1 kilobyte.
63pub const KB: u64 = 1_000;
64/// Number of bytes in 1 megabyte.
65pub const MB: u64 = 1_000_000;
66/// Number of bytes in 1 gigabyte.
67pub const GB: u64 = 1_000_000_000;
68/// Number of bytes in 1 terabyte.
69pub const TB: u64 = 1_000_000_000_000;
70/// Number of bytes in 1 petabyte.
71pub const PB: u64 = 1_000_000_000_000_000;
72/// Number of bytes in 1 exabyte.
73pub const EB: u64 = 1_000_000_000_000_000_000;
74
75/// Number of bytes in 1 kibibyte.
76pub const KIB: u64 = 1_024;
77/// Number of bytes in 1 mebibyte.
78pub const MIB: u64 = 1_048_576;
79/// Number of bytes in 1 gibibyte.
80pub const GIB: u64 = 1_073_741_824;
81/// Number of bytes in 1 tebibyte.
82pub const TIB: u64 = 1_099_511_627_776;
83/// Number of bytes in 1 pebibyte.
84pub const PIB: u64 = 1_125_899_906_842_624;
85/// Number of bytes in 1 exbibyte.
86pub const EIB: u64 = 1_152_921_504_606_846_976;
87
88/// IEC (binary) units.
89///
90/// See <https://en.wikipedia.org/wiki/Kilobyte>.
91const UNITS_IEC: &str = "KMGTPE";
92
93/// SI (decimal) units.
94///
95/// See <https://en.wikipedia.org/wiki/Kilobyte>.
96const UNITS_SI: &str = "kMGTPE";
97
98/// `ln(1024) ~= 6.931`
99const LN_KIB: f64 = 6.931_471_805_599_453;
100
101/// `ln(1000) ~= 6.908`
102const LN_KB: f64 = 6.907_755_278_982_137;
103
104/// Converts a quantity of kilobytes to bytes.
105pub fn kb(size: impl Into<u64>) -> u64 {
106    size.into() * KB
107}
108
109/// Converts a quantity of kibibytes to bytes.
110pub fn kib<V: Into<u64>>(size: V) -> u64 {
111    size.into() * KIB
112}
113
114/// Converts a quantity of megabytes to bytes.
115pub fn mb<V: Into<u64>>(size: V) -> u64 {
116    size.into() * MB
117}
118
119/// Converts a quantity of mebibytes to bytes.
120pub fn mib<V: Into<u64>>(size: V) -> u64 {
121    size.into() * MIB
122}
123
124/// Converts a quantity of gigabytes to bytes.
125pub fn gb<V: Into<u64>>(size: V) -> u64 {
126    size.into() * GB
127}
128
129/// Converts a quantity of gibibytes to bytes.
130pub fn gib<V: Into<u64>>(size: V) -> u64 {
131    size.into() * GIB
132}
133
134/// Converts a quantity of terabytes to bytes.
135pub fn tb<V: Into<u64>>(size: V) -> u64 {
136    size.into() * TB
137}
138
139/// Converts a quantity of tebibytes to bytes.
140pub fn tib<V: Into<u64>>(size: V) -> u64 {
141    size.into() * TIB
142}
143
144/// Converts a quantity of petabytes to bytes.
145pub fn pb<V: Into<u64>>(size: V) -> u64 {
146    size.into() * PB
147}
148
149/// Converts a quantity of pebibytes to bytes.
150pub fn pib<V: Into<u64>>(size: V) -> u64 {
151    size.into() * PIB
152}
153
154/// Converts a quantity of exabytes to bytes.
155pub fn eb<V: Into<u64>>(size: V) -> u64 {
156    size.into() * EB
157}
158
159/// Converts a quantity of exbibytes to bytes.
160pub fn eib<V: Into<u64>>(size: V) -> u64 {
161    size.into() * EIB
162}
163
164/// Byte size representation.
165#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
166pub struct ByteSize(pub u64);
167
168impl ByteSize {
169    /// Constructs a byte size wrapper from a quantity of bytes.
170    #[inline(always)]
171    pub const fn b(size: u64) -> ByteSize {
172        ByteSize(size)
173    }
174
175    /// Constructs a byte size wrapper from a quantity of kilobytes.
176    #[inline(always)]
177    pub const fn kb(size: u64) -> ByteSize {
178        ByteSize(size * KB)
179    }
180
181    /// Constructs a byte size wrapper from a quantity of kibibytes.
182    #[inline(always)]
183    pub const fn kib(size: u64) -> ByteSize {
184        ByteSize(size * KIB)
185    }
186
187    /// Constructs a byte size wrapper from a quantity of megabytes.
188    #[inline(always)]
189    pub const fn mb(size: u64) -> ByteSize {
190        ByteSize(size * MB)
191    }
192
193    /// Constructs a byte size wrapper from a quantity of mebibytes.
194    #[inline(always)]
195    pub const fn mib(size: u64) -> ByteSize {
196        ByteSize(size * MIB)
197    }
198
199    /// Constructs a byte size wrapper from a quantity of gigabytes.
200    #[inline(always)]
201    pub const fn gb(size: u64) -> ByteSize {
202        ByteSize(size * GB)
203    }
204
205    /// Constructs a byte size wrapper from a quantity of gibibytes.
206    #[inline(always)]
207    pub const fn gib(size: u64) -> ByteSize {
208        ByteSize(size * GIB)
209    }
210
211    /// Constructs a byte size wrapper from a quantity of terabytes.
212    #[inline(always)]
213    pub const fn tb(size: u64) -> ByteSize {
214        ByteSize(size * TB)
215    }
216
217    /// Constructs a byte size wrapper from a quantity of tebibytes.
218    #[inline(always)]
219    pub const fn tib(size: u64) -> ByteSize {
220        ByteSize(size * TIB)
221    }
222
223    /// Constructs a byte size wrapper from a quantity of petabytes.
224    #[inline(always)]
225    pub const fn pb(size: u64) -> ByteSize {
226        ByteSize(size * PB)
227    }
228
229    /// Constructs a byte size wrapper from a quantity of pebibytes.
230    #[inline(always)]
231    pub const fn pib(size: u64) -> ByteSize {
232        ByteSize(size * PIB)
233    }
234
235    /// Constructs a byte size wrapper from a quantity of exabytes.
236    #[inline(always)]
237    pub const fn eb(size: u64) -> ByteSize {
238        ByteSize(size * EB)
239    }
240
241    /// Constructs a byte size wrapper from a quantity of exbibytes.
242    #[inline(always)]
243    pub const fn eib(size: u64) -> ByteSize {
244        ByteSize(size * EIB)
245    }
246
247    /// Returns byte count.
248    #[inline(always)]
249    pub const fn as_u64(&self) -> u64 {
250        self.0
251    }
252
253    /// Returns byte count as kilobytes.
254    #[inline(always)]
255    pub fn as_kb(&self) -> f64 {
256        self.0 as f64 / KB as f64
257    }
258
259    /// Returns byte count as kibibytes.
260    #[inline(always)]
261    pub fn as_kib(&self) -> f64 {
262        self.0 as f64 / KIB as f64
263    }
264
265    /// Returns byte count as megabytes.
266    #[inline(always)]
267    pub fn as_mb(&self) -> f64 {
268        self.0 as f64 / MB as f64
269    }
270
271    /// Returns byte count as mebibytes.
272    #[inline(always)]
273    pub fn as_mib(&self) -> f64 {
274        self.0 as f64 / MIB as f64
275    }
276
277    /// Returns byte count as gigabytes.
278    #[inline(always)]
279    pub fn as_gb(&self) -> f64 {
280        self.0 as f64 / GB as f64
281    }
282
283    /// Returns byte count as gibibytes.
284    #[inline(always)]
285    pub fn as_gib(&self) -> f64 {
286        self.0 as f64 / GIB as f64
287    }
288
289    /// Returns byte count as terabytes.
290    #[inline(always)]
291    pub fn as_tb(&self) -> f64 {
292        self.0 as f64 / TB as f64
293    }
294
295    /// Returns byte count as tebibytes.
296    #[inline(always)]
297    pub fn as_tib(&self) -> f64 {
298        self.0 as f64 / TIB as f64
299    }
300
301    /// Returns byte count as petabytes.
302    #[inline(always)]
303    pub fn as_pb(&self) -> f64 {
304        self.0 as f64 / PB as f64
305    }
306
307    /// Returns byte count as pebibytes.
308    #[inline(always)]
309    pub fn as_pib(&self) -> f64 {
310        self.0 as f64 / PIB as f64
311    }
312
313    /// Returns byte count as exabytes.
314    #[inline(always)]
315    pub fn as_eb(&self) -> f64 {
316        self.0 as f64 / EB as f64
317    }
318
319    /// Returns byte count as exbibytes.
320    #[inline(always)]
321    pub fn as_eib(&self) -> f64 {
322        self.0 as f64 / EIB as f64
323    }
324
325    /// Returns a formatting display wrapper.
326    pub fn display(&self) -> Display {
327        Display {
328            byte_size: *self,
329            format: Format::Iec,
330        }
331    }
332}
333
334impl fmt::Display for ByteSize {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        let display = self.display();
337
338        if f.width().is_none() {
339            // allocation-free fast path for when no formatting options are specified
340            fmt::Display::fmt(&display, f)
341        } else {
342            f.pad(&display.to_string())
343        }
344    }
345}
346
347impl fmt::Debug for ByteSize {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        write!(f, "{} ({} bytes)", self, self.0)
350    }
351}
352
353macro_rules! commutative_op {
354    ($t:ty) => {
355        impl ops::Add<ByteSize> for $t {
356            type Output = ByteSize;
357            #[inline(always)]
358            fn add(self, rhs: ByteSize) -> ByteSize {
359                ByteSize(rhs.0 + (self as u64))
360            }
361        }
362
363        impl ops::Mul<ByteSize> for $t {
364            type Output = ByteSize;
365            #[inline(always)]
366            fn mul(self, rhs: ByteSize) -> ByteSize {
367                ByteSize(rhs.0 * (self as u64))
368            }
369        }
370    };
371}
372
373commutative_op!(u64);
374commutative_op!(u32);
375commutative_op!(u16);
376commutative_op!(u8);
377
378impl ops::Add<ByteSize> for ByteSize {
379    type Output = ByteSize;
380
381    #[inline(always)]
382    fn add(self, rhs: ByteSize) -> ByteSize {
383        ByteSize(self.0 + rhs.0)
384    }
385}
386
387impl ops::AddAssign<ByteSize> for ByteSize {
388    #[inline(always)]
389    fn add_assign(&mut self, rhs: ByteSize) {
390        self.0 += rhs.0
391    }
392}
393
394impl iter::Sum<ByteSize> for ByteSize {
395    fn sum<I>(iter: I) -> Self
396    where
397        I: Iterator<Item = ByteSize>,
398    {
399        iter.fold(Self::default(), ops::Add::add)
400    }
401}
402
403impl<'a> iter::Sum<&'a ByteSize> for ByteSize {
404    fn sum<I>(iter: I) -> Self
405    where
406        I: Iterator<Item = &'a ByteSize>,
407    {
408        iter.copied().sum()
409    }
410}
411
412impl<T> ops::Add<T> for ByteSize
413where
414    T: Into<u64>,
415{
416    type Output = ByteSize;
417    #[inline(always)]
418    fn add(self, rhs: T) -> ByteSize {
419        ByteSize(self.0 + (rhs.into()))
420    }
421}
422
423impl<T> ops::AddAssign<T> for ByteSize
424where
425    T: Into<u64>,
426{
427    #[inline(always)]
428    fn add_assign(&mut self, rhs: T) {
429        self.0 += rhs.into();
430    }
431}
432
433impl ops::Sub<ByteSize> for ByteSize {
434    type Output = ByteSize;
435
436    #[inline(always)]
437    fn sub(self, rhs: ByteSize) -> ByteSize {
438        ByteSize(self.0 - rhs.0)
439    }
440}
441
442impl ops::SubAssign<ByteSize> for ByteSize {
443    #[inline(always)]
444    fn sub_assign(&mut self, rhs: ByteSize) {
445        self.0 -= rhs.0
446    }
447}
448
449impl<T> ops::Sub<T> for ByteSize
450where
451    T: Into<u64>,
452{
453    type Output = ByteSize;
454    #[inline(always)]
455    fn sub(self, rhs: T) -> ByteSize {
456        ByteSize(self.0 - (rhs.into()))
457    }
458}
459
460impl<T> ops::SubAssign<T> for ByteSize
461where
462    T: Into<u64>,
463{
464    #[inline(always)]
465    fn sub_assign(&mut self, rhs: T) {
466        self.0 -= rhs.into();
467    }
468}
469
470impl<T> ops::Mul<T> for ByteSize
471where
472    T: Into<u64>,
473{
474    type Output = ByteSize;
475    #[inline(always)]
476    fn mul(self, rhs: T) -> ByteSize {
477        ByteSize(self.0 * rhs.into())
478    }
479}
480
481impl<T> ops::MulAssign<T> for ByteSize
482where
483    T: Into<u64>,
484{
485    #[inline(always)]
486    fn mul_assign(&mut self, rhs: T) {
487        self.0 *= rhs.into();
488    }
489}
490
491#[cfg(test)]
492mod property_tests {
493    use alloc::string::{String, ToString as _};
494
495    use super::*;
496
497    impl quickcheck::Arbitrary for ByteSize {
498        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
499            Self(u64::arbitrary(g))
500        }
501    }
502
503    quickcheck::quickcheck! {
504        fn parsing_never_panics(size: String) -> bool {
505            let _ = size.parse::<ByteSize>();
506            true
507        }
508
509        fn to_string_never_blank(size: ByteSize) -> bool {
510            !size.to_string().is_empty()
511        }
512
513        fn to_string_never_large(size: ByteSize) -> bool {
514            size.to_string().len() < 11
515        }
516
517        fn string_round_trip(size: ByteSize) -> bool {
518            // currently fails on many inputs above the pebibyte level
519            if size > ByteSize::pib(1) {
520                return true;
521            }
522
523            size.to_string().parse::<ByteSize>().unwrap() == size
524        }
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use alloc::format;
531
532    use super::*;
533
534    #[test]
535    fn test_arithmetic_op() {
536        let mut x = ByteSize::mb(1);
537        let y = ByteSize::kb(100);
538
539        assert_eq!((x + y).as_u64(), 1_100_000u64);
540
541        assert_eq!((x - y).as_u64(), 900_000u64);
542
543        assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
544
545        assert_eq!((x * 2u64).as_u64(), 2_000_000);
546
547        x += y;
548        assert_eq!(x.as_u64(), 1_100_000);
549        x *= 2u64;
550        assert_eq!(x.as_u64(), 2_200_000);
551    }
552
553    #[allow(clippy::unnecessary_cast)]
554    #[test]
555    fn test_arithmetic_primitives() {
556        let mut x = ByteSize::mb(1);
557
558        assert_eq!((x + MB as u64).as_u64(), 2_000_000);
559        assert_eq!((x + MB as u32).as_u64(), 2_000_000);
560        assert_eq!((x + KB as u16).as_u64(), 1_001_000);
561        assert_eq!((x - MB as u64).as_u64(), 0);
562        assert_eq!((x - MB as u32).as_u64(), 0);
563        assert_eq!((x - KB as u32).as_u64(), 999_000);
564
565        x += MB as u64;
566        x += MB as u32;
567        x += 10u16;
568        x += 1u8;
569        assert_eq!(x.as_u64(), 3_000_011);
570    }
571
572    #[test]
573    fn test_sum() {
574        let sizes = [ByteSize::kb(1), ByteSize::mb(1), ByteSize::mib(1)];
575
576        assert_eq!(
577            sizes.into_iter().sum::<ByteSize>(),
578            ByteSize::b(KB + MB + MIB)
579        );
580        assert_eq!(sizes.iter().sum::<ByteSize>(), ByteSize::b(KB + MB + MIB));
581        assert_eq!(
582            core::iter::empty::<ByteSize>().sum::<ByteSize>(),
583            ByteSize::b(0)
584        );
585    }
586
587    #[test]
588    fn test_comparison() {
589        assert!(ByteSize::mb(1) == ByteSize::kb(1000));
590        assert!(ByteSize::mib(1) == ByteSize::kib(1024));
591        assert!(ByteSize::mb(1) != ByteSize::kib(1024));
592        assert!(ByteSize::mb(1) < ByteSize::kib(1024));
593        assert!(ByteSize::b(0) < ByteSize::tib(1));
594        assert!(ByteSize::pib(1) < ByteSize::eb(1));
595    }
596
597    #[test]
598    fn as_unit_conversions() {
599        assert_eq!(41992187.5, ByteSize::gb(43).as_kib());
600        assert_eq!(0.028311552, ByteSize::mib(27).as_gb());
601        assert_eq!(0.0380859375, ByteSize::tib(39).as_pib());
602        assert_eq!(961.482752, ByteSize::kib(938948).as_mb());
603        assert_eq!(4.195428726649908, ByteSize::pb(4837).as_eib());
604        assert_eq!(2.613772153284117, ByteSize::b(2873872874893).as_tib());
605    }
606
607    #[track_caller]
608    fn assert_display(expected: &str, b: ByteSize) {
609        assert_eq!(expected, format!("{b}"));
610    }
611
612    #[test]
613    fn test_display() {
614        assert_display("215 B", ByteSize::b(215));
615        assert_display("1.0 KiB", ByteSize::kib(1));
616        assert_display("301.0 KiB", ByteSize::kib(301));
617        assert_display("419.0 MiB", ByteSize::mib(419));
618        assert_display("518.0 GiB", ByteSize::gib(518));
619        assert_display("815.0 TiB", ByteSize::tib(815));
620        assert_display("609.0 PiB", ByteSize::pib(609));
621        assert_display("15.0 EiB", ByteSize::eib(15));
622    }
623
624    #[test]
625    fn test_display_alignment() {
626        assert_eq!("|357 B     |", format!("|{:10}|", ByteSize(357)));
627        assert_eq!("|     357 B|", format!("|{:>10}|", ByteSize(357)));
628        assert_eq!("|357 B     |", format!("|{:<10}|", ByteSize(357)));
629        assert_eq!("|  357 B   |", format!("|{:^10}|", ByteSize(357)));
630
631        assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
632        assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
633        assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
634    }
635
636    #[test]
637    fn test_default() {
638        assert_eq!(ByteSize::b(0), ByteSize::default());
639    }
640}