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