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, 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<T> ops::Add<T> for ByteSize
395where
396    T: Into<u64>,
397{
398    type Output = ByteSize;
399    #[inline(always)]
400    fn add(self, rhs: T) -> ByteSize {
401        ByteSize(self.0 + (rhs.into()))
402    }
403}
404
405impl<T> ops::AddAssign<T> for ByteSize
406where
407    T: Into<u64>,
408{
409    #[inline(always)]
410    fn add_assign(&mut self, rhs: T) {
411        self.0 += rhs.into();
412    }
413}
414
415impl ops::Sub<ByteSize> for ByteSize {
416    type Output = ByteSize;
417
418    #[inline(always)]
419    fn sub(self, rhs: ByteSize) -> ByteSize {
420        ByteSize(self.0 - rhs.0)
421    }
422}
423
424impl ops::SubAssign<ByteSize> for ByteSize {
425    #[inline(always)]
426    fn sub_assign(&mut self, rhs: ByteSize) {
427        self.0 -= rhs.0
428    }
429}
430
431impl<T> ops::Sub<T> for ByteSize
432where
433    T: Into<u64>,
434{
435    type Output = ByteSize;
436    #[inline(always)]
437    fn sub(self, rhs: T) -> ByteSize {
438        ByteSize(self.0 - (rhs.into()))
439    }
440}
441
442impl<T> ops::SubAssign<T> for ByteSize
443where
444    T: Into<u64>,
445{
446    #[inline(always)]
447    fn sub_assign(&mut self, rhs: T) {
448        self.0 -= rhs.into();
449    }
450}
451
452impl<T> ops::Mul<T> for ByteSize
453where
454    T: Into<u64>,
455{
456    type Output = ByteSize;
457    #[inline(always)]
458    fn mul(self, rhs: T) -> ByteSize {
459        ByteSize(self.0 * rhs.into())
460    }
461}
462
463impl<T> ops::MulAssign<T> for ByteSize
464where
465    T: Into<u64>,
466{
467    #[inline(always)]
468    fn mul_assign(&mut self, rhs: T) {
469        self.0 *= rhs.into();
470    }
471}
472
473#[cfg(test)]
474mod property_tests {
475    use alloc::string::{String, ToString as _};
476
477    use super::*;
478
479    impl quickcheck::Arbitrary for ByteSize {
480        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
481            Self(u64::arbitrary(g))
482        }
483    }
484
485    quickcheck::quickcheck! {
486        fn parsing_never_panics(size: String) -> bool {
487            let _ = size.parse::<ByteSize>();
488            true
489        }
490
491        fn to_string_never_blank(size: ByteSize) -> bool {
492            !size.to_string().is_empty()
493        }
494
495        fn to_string_never_large(size: ByteSize) -> bool {
496            size.to_string().len() < 11
497        }
498
499        fn string_round_trip(size: ByteSize) -> bool {
500            // currently fails on many inputs above the pebibyte level
501            if size > ByteSize::pib(1) {
502                return true;
503            }
504
505            size.to_string().parse::<ByteSize>().unwrap() == size
506        }
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use alloc::format;
513
514    use super::*;
515
516    #[test]
517    fn test_arithmetic_op() {
518        let mut x = ByteSize::mb(1);
519        let y = ByteSize::kb(100);
520
521        assert_eq!((x + y).as_u64(), 1_100_000u64);
522
523        assert_eq!((x - y).as_u64(), 900_000u64);
524
525        assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
526
527        assert_eq!((x * 2u64).as_u64(), 2_000_000);
528
529        x += y;
530        assert_eq!(x.as_u64(), 1_100_000);
531        x *= 2u64;
532        assert_eq!(x.as_u64(), 2_200_000);
533    }
534
535    #[allow(clippy::unnecessary_cast)]
536    #[test]
537    fn test_arithmetic_primitives() {
538        let mut x = ByteSize::mb(1);
539
540        assert_eq!((x + MB as u64).as_u64(), 2_000_000);
541        assert_eq!((x + MB as u32).as_u64(), 2_000_000);
542        assert_eq!((x + KB as u16).as_u64(), 1_001_000);
543        assert_eq!((x - MB as u64).as_u64(), 0);
544        assert_eq!((x - MB as u32).as_u64(), 0);
545        assert_eq!((x - KB as u32).as_u64(), 999_000);
546
547        x += MB as u64;
548        x += MB as u32;
549        x += 10u16;
550        x += 1u8;
551        assert_eq!(x.as_u64(), 3_000_011);
552    }
553
554    #[test]
555    fn test_comparison() {
556        assert!(ByteSize::mb(1) == ByteSize::kb(1000));
557        assert!(ByteSize::mib(1) == ByteSize::kib(1024));
558        assert!(ByteSize::mb(1) != ByteSize::kib(1024));
559        assert!(ByteSize::mb(1) < ByteSize::kib(1024));
560        assert!(ByteSize::b(0) < ByteSize::tib(1));
561        assert!(ByteSize::pib(1) < ByteSize::eb(1));
562    }
563
564    #[test]
565    fn as_unit_conversions() {
566        assert_eq!(41992187.5, ByteSize::gb(43).as_kib());
567        assert_eq!(0.028311552, ByteSize::mib(27).as_gb());
568        assert_eq!(0.0380859375, ByteSize::tib(39).as_pib());
569        assert_eq!(961.482752, ByteSize::kib(938948).as_mb());
570        assert_eq!(4.195428726649908, ByteSize::pb(4837).as_eib());
571        assert_eq!(2.613772153284117, ByteSize::b(2873872874893).as_tib());
572    }
573
574    #[track_caller]
575    fn assert_display(expected: &str, b: ByteSize) {
576        assert_eq!(expected, format!("{b}"));
577    }
578
579    #[test]
580    fn test_display() {
581        assert_display("215 B", ByteSize::b(215));
582        assert_display("1.0 KiB", ByteSize::kib(1));
583        assert_display("301.0 KiB", ByteSize::kib(301));
584        assert_display("419.0 MiB", ByteSize::mib(419));
585        assert_display("518.0 GiB", ByteSize::gib(518));
586        assert_display("815.0 TiB", ByteSize::tib(815));
587        assert_display("609.0 PiB", ByteSize::pib(609));
588        assert_display("15.0 EiB", ByteSize::eib(15));
589    }
590
591    #[test]
592    fn test_display_alignment() {
593        assert_eq!("|357 B     |", format!("|{:10}|", ByteSize(357)));
594        assert_eq!("|     357 B|", format!("|{:>10}|", ByteSize(357)));
595        assert_eq!("|357 B     |", format!("|{:<10}|", ByteSize(357)));
596        assert_eq!("|  357 B   |", format!("|{:^10}|", ByteSize(357)));
597
598        assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
599        assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
600        assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
601    }
602
603    #[test]
604    fn test_default() {
605        assert_eq!(ByteSize::b(0), ByteSize::default());
606    }
607}