//! ByteSize is an utility that easily makes bytes size representation //! and helps its arithmetic operations. //! //! ## Example //! //! ```ignore //! extern crate bytesize; //! //! use bytesize::ByteSize; //! //! fn byte_arithmetic_operator() { //! let x = ByteSize::mb(1); //! let y = ByteSize::kb(100); //! //! let plus = x + y; //! print!("{} bytes", plus.as_u64()); //! //! let minus = ByteSize::tb(100) - ByteSize::gb(4); //! print!("{} bytes", minus.as_u64()); //! } //! ``` //! //! It also provides its human readable string as follows: //! //! ```ignore= //! assert_eq!("482 GiB".to_string(), ByteSize::gb(518).to_string(true)); //! assert_eq!("518 GB".to_string(), ByteSize::gb(518).to_string(false)); //! ``` #[cfg(feature = "serde")] #[macro_use] extern crate serde; use std::fmt::{Debug, Display, Formatter, Result}; use std::ops::{Add, Mul}; /// byte size for 1 byte pub const B: u64 = 1; /// bytes size for 1 kilobyte pub const KB: u64 = 1_000; /// bytes size for 1 megabyte pub const MB: u64 = 1_000_000; /// bytes size for 1 gigabyte pub const GB: u64 = 1_000_000_000; /// bytes size for 1 terabyte pub const TB: u64 = 1_000_000_000_000; /// bytes size for 1 petabyte pub const PB: u64 = 1_000_000_000_000_000; /// bytes size for 1 kibibyte pub const KIB: u64 = 1_024; /// bytes size for 1 mebibyte pub const MIB: u64 = 1_048_576; /// bytes size for 1 gibibyte pub const GIB: u64 = 1_073_741_824; /// bytes size for 1 tebibyte pub const TIB: u64 = 1_099_511_627_776; /// bytes size for 1 pebibyte pub const PIB: u64 = 1_125_899_906_842_624; static UNITS: &'static str = "KMGTPE"; static UNITS_SI: &'static str = "kMGTPE"; static LN_KB: f64 = 6.931471806; // ln 1024 static LN_KIB: f64 = 6.907755279; // ln 1000 pub fn kb>(size: V) -> u64 { size.into() * KB } pub fn kib>(size: V) -> u64 { size.into() * KIB } pub fn mb>(size: V) -> u64 { size.into() * MB } pub fn mib>(size: V) -> u64 { size.into() * MIB } pub fn gb>(size: V) -> u64 { size.into() * GB } pub fn gib>(size: V) -> u64 { size.into() * GIB } pub fn tb>(size: V) -> u64 { size.into() * TB } pub fn tib>(size: V) -> u64 { size.into() * TIB } pub fn pb>(size: V) -> u64 { size.into() * PB } pub fn pib>(size: V) -> u64 { size.into() * PIB } /// Byte size representation #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ByteSize(pub u64); impl ByteSize { #[inline(always)] pub fn b(size: u64) -> ByteSize { ByteSize(size) } #[inline(always)] pub fn kb(size: u64) -> ByteSize { ByteSize(size * KB) } #[inline(always)] pub fn kib(size: u64) -> ByteSize { ByteSize(size * KIB) } #[inline(always)] pub fn mb(size: u64) -> ByteSize { ByteSize(size * MB) } #[inline(always)] pub fn mib(size: u64) -> ByteSize { ByteSize(size * MIB) } #[inline(always)] pub fn gb(size: u64) -> ByteSize { ByteSize(size * GB) } #[inline(always)] pub fn gib(size: u64) -> ByteSize { ByteSize(size * GIB) } #[inline(always)] pub fn tb(size: u64) -> ByteSize { ByteSize(size * TB) } #[inline(always)] pub fn tib(size: u64) -> ByteSize { ByteSize(size * TIB) } #[inline(always)] pub fn pb(size: u64) -> ByteSize { ByteSize(size * PB) } #[inline(always)] pub fn pib(size: u64) -> ByteSize { ByteSize(size * PIB) } #[inline(always)] pub fn as_u64(&self) -> u64 { self.0 } #[inline(always)] pub fn to_string_as(&self, si_unit: bool) -> String { to_string(self.0, si_unit) } } pub fn to_string(bytes: u64, si_prefix: bool) -> String { let unit = if si_prefix { KIB } else { KB }; let unit_base = if si_prefix { LN_KIB } else { LN_KB }; let unit_prefix = if si_prefix { UNITS_SI.as_bytes() } else { UNITS.as_bytes() }; let unit_suffix = if si_prefix { "iB" } else { "B" }; if bytes < unit { format!("{} B", bytes) } else { let size = bytes as f64; let exp = match (size.ln() / unit_base) as usize { e if e == 0 => 1, e => e, }; format!( "{:.1} {}{}", (size / unit.pow(exp as u32) as f64), unit_prefix[exp - 1] as char, unit_suffix ) } } impl Display for ByteSize { fn fmt(&self, f: &mut Formatter) -> Result { f.pad(&to_string(self.0, false)) } } impl Debug for ByteSize { fn fmt(&self, f: &mut Formatter) -> Result { write!(f, "{}", self) } } macro_rules! commutative_op { ($t:ty) => { impl Add<$t> for ByteSize { type Output = ByteSize; #[inline(always)] fn add(self, rhs: $t) -> ByteSize { ByteSize(self.0 + (rhs as u64)) } } impl Add for $t { type Output = ByteSize; #[inline(always)] fn add(self, rhs: ByteSize) -> ByteSize { ByteSize(rhs.0 + (self as u64)) } } impl Mul<$t> for ByteSize { type Output = ByteSize; #[inline(always)] fn mul(self, rhs: $t) -> ByteSize { ByteSize(self.0 * (rhs as u64)) } } impl Mul for $t { type Output = ByteSize; #[inline(always)] fn mul(self, rhs: ByteSize) -> ByteSize { ByteSize(rhs.0 * (self as u64)) } } }; } commutative_op!(u64); commutative_op!(u32); commutative_op!(u16); commutative_op!(u8); impl Add for ByteSize { type Output = ByteSize; #[inline(always)] fn add(self, rhs: ByteSize) -> ByteSize { ByteSize(self.0 + rhs.0) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_arithmetic_op() { let x = ByteSize::mb(1); let y = ByteSize::kb(100); assert_eq!((x + y).as_u64(), 1_100_000u64); assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000); assert_eq!((x * 2u64).as_u64(), 2_000_000); } #[test] fn test_arithmetic_primitives() { let x = ByteSize::mb(1); assert_eq!((x + MB as u64).as_u64(), 2_000_000); assert_eq!((x + MB as u32).as_u64(), 2_000_000); assert_eq!((x + KB as u16).as_u64(), 1_001_000); assert_eq!((x + B as u8).as_u64(), 1_000_001); } #[test] fn test_comparison() { assert!(ByteSize::mb(1) == ByteSize::kb(1000)); assert!(ByteSize::mib(1) == ByteSize::kib(1024)); assert!(ByteSize::mb(1) != ByteSize::kib(1024)); assert!(ByteSize::mb(1) < ByteSize::kib(1024)); assert!(ByteSize::b(0) < ByteSize::tib(1)); } fn assert_display(expected: &str, b: ByteSize) { assert_eq!(expected, format!("{}", b)); } #[test] fn test_display() { assert_display("215 B", ByteSize::b(215)); assert_display("1.0 KB", ByteSize::kb(1)); assert_display("301.0 KB", ByteSize::kb(301)); assert_display("419.0 MB", ByteSize::mb(419)); assert_display("518.0 GB", ByteSize::gb(518)); assert_display("815.0 TB", ByteSize::tb(815)); assert_display("609.0 PB", ByteSize::pb(609)); } #[test] fn test_display_alignment() { assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357))); assert_eq!("| 357 B|", format!("|{:>10}|", ByteSize(357))); assert_eq!("|357 B |", format!("|{:<10}|", ByteSize(357))); assert_eq!("| 357 B |", format!("|{:^10}|", ByteSize(357))); assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357))); assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357))); assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357))); } fn assert_to_string(expected: &str, b: ByteSize, si: bool) { assert_eq!(expected.to_string(), b.to_string_as(si)); } #[test] fn test_to_string_as() { assert_to_string("215 B", ByteSize::b(215), true); assert_to_string("215 B", ByteSize::b(215), false); assert_to_string("1.0 kiB", ByteSize::kib(1), true); assert_to_string("1.0 KB", ByteSize::kib(1), false); assert_to_string("293.9 kiB", ByteSize::kb(301), true); assert_to_string("301.0 KB", ByteSize::kb(301), false); assert_to_string("1.0 MiB", ByteSize::mib(1), true); assert_to_string("1048.6 KB", ByteSize::mib(1), false); // a bug case: https://github.com/flang-project/bytesize/issues/8 assert_to_string("1.9 GiB", ByteSize::mib(1907), true); assert_to_string("2.0 GB", ByteSize::mib(1908), false); assert_to_string("399.6 MiB", ByteSize::mb(419), true); assert_to_string("419.0 MB", ByteSize::mb(419), false); assert_to_string("482.4 GiB", ByteSize::gb(518), true); assert_to_string("518.0 GB", ByteSize::gb(518), false); assert_to_string("741.2 TiB", ByteSize::tb(815), true); assert_to_string("815.0 TB", ByteSize::tb(815), false); assert_to_string("540.9 PiB", ByteSize::pb(609), true); assert_to_string("609.0 PB", ByteSize::pb(609), false); } #[test] fn test_default() { assert_eq!(ByteSize::b(0), ByteSize::default()); } #[test] fn test_to_string() { assert_to_string("609.0 PB", ByteSize::pb(609), false); } }