#[repr(transparent)]
#[derive(Debug, Default, Copy, Clone, Eq, Hash, Ord)]
pub struct ByteUnit(pub(crate) u64);
macro_rules! rem_and_suffix {
($n:expr => $(($isuffix:ident, $suffix:ident)),+ $or_else:ident) => {
loop {
$(
let i_val = ByteUnit::$isuffix.as_u64();
let s_val = ByteUnit::$suffix.as_u64();
if $n >= s_val {
let (u_val, unit, string) = if $n % s_val >= i_val - s_val {
(i_val, ByteUnit::$isuffix, stringify!($isuffix))
} else {
(s_val, ByteUnit::$suffix, stringify!($suffix))
};
break ($n / u_val, ($n % u_val) as f64 / u_val as f64, string, unit)
}
)+
break ($n, 0f64, stringify!($or_else), ByteUnit::$or_else)
}
};
}
macro_rules! const_if {
($cond:expr, $on_true:expr, $on_false:expr) => (
[$on_false, $on_true][$cond as usize]
)
}
macro_rules! constructor_fns {
($($sstr:expr, $nstr:expr, $example:expr, $suffix:ident, $name:ident = $size:expr),*) => (
$(
#[doc = $sstr]
#[doc = $nstr]
#[allow(non_upper_case_globals)]
pub const $suffix: ByteUnit = ByteUnit::$name(1);
)*
$(
#[doc = $sstr]
#[doc = $example]
#[allow(non_snake_case)]
pub const fn $name(n: u64) -> ByteUnit {
let size: u64 = $size;
let v = const_if!(n as u128 * size as u128 > u64::max_value() as u128,
ByteUnit::max_value().as_u128(),
n as u128 * size as u128
);
ByteUnit(v as u64)
}
)*
);
($($suffix:ident, $name:ident = $size:expr),* $(,)?) => (
constructor_fns!($(
stringify!($suffix), stringify!($size), concat!(
"assert_eq!(ByteUnit::", stringify!($name), "(10), ",
"10 * ByteUnit::", stringify!($suffix), ");"
), $suffix, $name = $size
),*);
)
}
impl ByteUnit {
constructor_fns! {
B, Byte = 1,
kB, Kilobyte = 1_000,
KiB, Kibibyte = 1 << 10,
MB, Megabyte = 1_000_000,
MiB, Mebibyte = 1 << 20,
GB, Gigabyte = 1_000_000_000,
GiB, Gibibyte = 1 << 30,
TB, Terabyte = 1_000_000_000_000,
TiB, Tebibyte = 1 << 40,
PB, Petabyte = 1_000_000_000_000_000,
PiB, Pebibyte = 1 << 50,
EB, Exabyte = 1_000_000_000_000_000_000,
EiB, Exbibyte = 1 << 60,
}
pub const fn max_value() -> ByteUnit {
ByteUnit(u64::max_value())
}
pub const fn as_u64(self) -> u64 {
self.0
}
pub const fn as_u128(self) -> u128 {
self.0 as u128
}
pub fn repr(self) -> (u64, f64, &'static str, ByteUnit) {
rem_and_suffix! { self.as_u64() =>
(EiB, EB), (TiB, TB), (GiB, GB), (MiB, MB), (KiB, kB) B
}
}
}
impl From<ByteUnit> for u64 {
#[inline(always)]
fn from(v: ByteUnit) -> Self {
v.as_u64()
}
}
impl From<ByteUnit> for u128 {
#[inline(always)]
fn from(v: ByteUnit) -> Self {
v.as_u128()
}
}
macro_rules! impl_from_int_unknown {
($T:ty) => (
impl From<$T> for ByteUnit {
#[inline(always)]
fn from(value: $T) -> Self {
if core::mem::size_of::<$T>() <= core::mem::size_of::<i64>() {
ByteUnit::from(value as i64)
} else if value <= i64::max_value() as $T {
ByteUnit::from(value as i64)
} else {
ByteUnit::max_value()
}
}
}
)
}
macro_rules! impl_from_uint_unknown {
($T:ty) => (
impl From<$T> for ByteUnit {
#[inline(always)]
fn from(value: $T) -> Self {
if core::mem::size_of::<$T>() <= core::mem::size_of::<u64>() {
ByteUnit(value as u64)
} else if value <= u64::max_value() as $T {
ByteUnit(value as u64)
} else {
ByteUnit::max_value()
}
}
}
)
}
macro_rules! impl_from_unsigned {
($T:ty) => (
impl From<$T> for ByteUnit {
#[inline(always)] fn from(v: $T) -> Self { ByteUnit(v as u64) }
}
)
}
macro_rules! impl_from_signed {
($T:ty) => (
impl From<$T> for ByteUnit {
#[inline(always)] fn from(v: $T) -> Self {
ByteUnit(core::cmp::max(v, 0) as u64)
}
}
)
}
impl_from_unsigned!(u8);
impl_from_unsigned!(u16);
impl_from_unsigned!(u32);
impl_from_unsigned!(u64);
impl_from_signed!(i8);
impl_from_signed!(i16);
impl_from_signed!(i32);
impl_from_signed!(i64);
impl_from_uint_unknown!(usize);
impl_from_uint_unknown!(u128);
impl_from_int_unknown!(isize);
impl_from_int_unknown!(i128);
macro_rules! helper_fn {
($kindstr:expr, $name:ident = $kind:ident) => (
#[doc = $kindstr]
#[inline(always)]
fn $name(self) -> ByteUnit {
self.bytes() * ByteUnit::$kind
}
);
($name:ident = $kind:ident) => (
helper_fn!(stringify!($kind), $name = $kind);
)
}
pub trait ToByteUnit: Into<ByteUnit> {
#[inline(always)]
fn bytes(self) -> ByteUnit {
self.into()
}
helper_fn!(kilobytes = kB);
helper_fn!(kibibytes = KiB);
helper_fn!(megabytes = MB);
helper_fn!(mebibytes = MiB);
helper_fn!(gigabytes = GB);
helper_fn!(gibibytes = GiB);
helper_fn!(terabytes = TB);
helper_fn!(tibibytes = TiB);
helper_fn!(petabytes = PB);
helper_fn!(pebibytes = PiB);
helper_fn!(exabytes = EB);
helper_fn!(exbibytes = EiB);
}
impl<T: Into<ByteUnit> + Copy> ToByteUnit for T {}
impl core::fmt::Display for ByteUnit {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let (whole, rem, suffix, unit) = self.repr();
let width = f.width().unwrap_or(0);
if rem != 0f64 && f.precision().map(|p| p > 0).unwrap_or(true) {
let p = f.precision().unwrap_or(2);
let k = 10u64.saturating_pow(p as u32) as f64;
write!(f, "{:0width$}.{:0p$.0}{}", whole, rem * k, suffix,
p = p, width = width)
} else if rem > 0.5f64 {
((whole.bytes() + 1) * unit).fmt(f)
} else {
write!(f, "{:0width$}{}", whole, suffix, width = width)
}
}
}