use std::num::{
NonZeroU8,NonZeroU16,NonZeroU32,NonZeroU64,
NonZeroI8,NonZeroI16,NonZeroI32,NonZeroI64,
NonZeroUsize,NonZeroIsize,
};
use crate::str::Str;
use crate::macros::{
impl_traits,impl_impl_math,impl_usize,
impl_math, impl_common, impl_const,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Byte(u64, Str<{ Byte::MAX_LEN }>);
impl_math!(Byte, u64);
impl_traits!(Byte, u64);
const BYTE: u64 = 1;
const KILOBYTE: u64 = 1_000;
const MEGABYTE: u64 = 1_000_000;
const GIGABYTE: u64 = 1_000_000_000;
const TERABYTE: u64 = 1_000_000_000_000;
const PETABYTE: u64 = 1_000_000_000_000_000;
const EXABYTE: u64 = 1_000_000_000_000_000_000;
const ZERO: u64 = 0;
impl Byte {
pub const MAX_LEN: usize = 10;
pub const ZERO: Self = Self(ZERO, Str::from_static_str("0 B"));
pub const BYTE: Self = Self(BYTE, Str::from_static_str("1 B"));
pub const KILOBYTE: Self = Self(KILOBYTE, Str::from_static_str("1.000 KB"));
pub const MEGABYTE: Self = Self(MEGABYTE, Str::from_static_str("1.000 MB"));
pub const GIGABYTE: Self = Self(GIGABYTE, Str::from_static_str("1.000 GB"));
pub const TERABYTE: Self = Self(TERABYTE, Str::from_static_str("1.000 TB"));
pub const PETABYTE: Self = Self(PETABYTE, Str::from_static_str("1.000 PB"));
pub const EXABYTE: Self = Self(EXABYTE, Str::from_static_str("1.000 EB"));
pub const MAX: Self = Self(u64::MAX, Str::from_static_str("18.446 EB"));
pub const UNKNOWN: Self = Self(ZERO, Str::from_static_str("???.??? B"));
}
impl Byte {
impl_common!(u64);
impl_const!();
impl_usize!();
#[inline]
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(*self, Self::UNKNOWN)
}
}
impl Byte {
fn from_priv(bytes: u64) -> Self {
const UNITS: [u8; 6] = [b'K', b'M', b'G', b'T', b'P', b'E'];
const LN_KILOBYTE: f64 = 6.931471806; const Z: u8 = b'0';
const SPACE: u8 = b' ';
const B: u8 = b'B';
const DOT: u8 = b'.';
match bytes {
ZERO => return Self::ZERO,
BYTE => return Self::BYTE,
KILOBYTE => return Self::KILOBYTE,
MEGABYTE => return Self::MEGABYTE,
GIGABYTE => return Self::GIGABYTE,
TERABYTE => return Self::TERABYTE,
PETABYTE => return Self::PETABYTE,
EXABYTE => return Self::EXABYTE,
_ => (),
}
let mut b = [0; 10];
if bytes < Self::KILOBYTE {
let mut itoa = crate::toa::ItoaTmp::new();
let itoa = itoa.format(bytes).as_bytes();
let len = itoa.len();
b[..len].copy_from_slice(itoa);
b[len] = SPACE;
b[len + 1] = B;
Self(bytes, unsafe { Str::from_raw(b, len as u8 + 2) })
} else {
let size = bytes as f64;
let exp = match (size.ln() / LN_KILOBYTE) as usize {
0 => 1,
e => e,
};
let float = size / KILOBYTE.pow(exp as u32) as f64;
let fract = (float.fract() * 1_000.0) as u16;
let base = float as u16;
let mut itoa = crate::toa::ItoaTmp::new();
let itoa = itoa.format(base).as_bytes();
b[0] = itoa[0];
let idx = if base < 10 {
b[1] = DOT;
2
} else if base < 100 {
b[1] = itoa[1];
b[2] = DOT;
3
} else {
b[1] = itoa[1];
b[2] = itoa[2];
b[3] = DOT;
4
};
let mut itoa = crate::toa::ItoaTmp::new();
let itoa = itoa.format(fract).as_bytes();
if fract < 10 {
b[idx ] = Z;
b[idx + 1] = Z;
b[idx + 2] = itoa[0];
} else if fract < 100 {
b[idx ] = Z;
b[idx + 1] = itoa[0];
b[idx + 2] = itoa[1];
} else {
b[idx ] = itoa[0];
b[idx + 1] = itoa[1];
b[idx + 2] = itoa[2];
}
b[idx + 3] = SPACE;
b[idx + 4] = UNITS[exp - 1];
b[idx + 5] = B;
Self(bytes, unsafe { Str::from_raw(b, idx as u8 + 6)})
}
}
}
macro_rules! impl_u {
($( $from:ty ),* $(,)?) => {
$(
impl From<$from> for Byte {
#[inline]
fn from(uint: $from) -> Self {
let u = uint as u64;
Self::from_priv(u)
}
}
impl From<&$from> for Byte {
#[inline]
fn from(uint: &$from) -> Self {
let u = *uint as u64;
Self::from_priv(u)
}
}
)*
}
}
impl_u!(u8,u16,u32,u64);
#[cfg(target_pointer_width = "64")]
impl_u!(usize);
macro_rules! impl_i {
($( $from:ty ),* $(,)?) => {
$(
impl From<$from> for Byte {
#[inline]
fn from(uint: $from) -> Self {
if uint.is_negative() {
return Self::UNKNOWN;
}
let u = uint as u64;
Self::from_priv(u)
}
}
impl From<&$from> for Byte {
#[inline]
fn from(uint: &$from) -> Self {
if uint.is_negative() {
return Self::UNKNOWN;
}
let u = *uint as u64;
Self::from_priv(u)
}
}
)*
}
}
impl_i!(i8,i16,i32,i64,isize);
macro_rules! impl_f {
($from:ty) => {
impl From<$from> for Byte {
fn from(float: $from) -> Self {
match float.classify() {
std::num::FpCategory::Normal => (),
std::num::FpCategory::Nan => return Self::UNKNOWN,
std::num::FpCategory::Infinite => return Self::UNKNOWN,
_ => (),
}
if float.is_sign_negative() {
return Self::UNKNOWN;
}
Self::from_priv(float as u64)
}
}
}
}
impl_f!(f32);
impl_f!(f64);
macro_rules! impl_nonu {
($( $from:ty ),* $(,)?) => {
$(
impl From<$from> for Byte {
fn from(uint: $from) -> Self {
let u = uint.get() as u64;
Self::from_priv(u)
}
}
)*
}
}
impl_nonu! {
NonZeroU8,NonZeroU16,NonZeroU32,NonZeroU64,
&NonZeroU8,&NonZeroU16,&NonZeroU32,&NonZeroU64,
}
#[cfg(target_pointer_width = "64")]
impl_nonu!(NonZeroUsize,&NonZeroUsize);
macro_rules! impl_noni {
($( $from:ty ),* $(,)?) => {
$(
impl From<$from> for Byte {
fn from(int: $from) -> Self {
let u = int.get();
if u.is_negative() {
return Self::UNKNOWN;
}
let u = u as u64;
Self::from_priv(u)
}
}
)*
}
}
impl_noni! {
NonZeroI8,NonZeroI16,NonZeroI32,NonZeroI64,
&NonZeroI8,&NonZeroI16,&NonZeroI32,&NonZeroI64,
NonZeroIsize,&NonZeroIsize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "serde")]
fn serde() {
let this: Byte = Byte::from(1000);
let json = serde_json::to_string(&this).unwrap();
assert_eq!(json, r#"[1000,"1.000 KB"]"#);
let this: Byte = serde_json::from_str(&json).unwrap();
assert_eq!(this, 1000);
assert_eq!(this, "1.000 KB");
assert!(serde_json::from_str::<Byte>(&"---").is_err());
let json = serde_json::to_string(&Byte::UNKNOWN).unwrap();
assert_eq!(json, r#"[0,"???.??? B"]"#);
assert!(serde_json::from_str::<Byte>(&json).unwrap().is_unknown());
}
#[test]
#[cfg(feature = "bincode")]
fn bincode() {
let this: Byte = Byte::from(1000);
let config = bincode::config::standard();
let bytes = bincode::encode_to_vec(&this, config).unwrap();
let this: Byte = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert_eq!(this, 1000);
assert_eq!(this, "1.000 KB");
let bytes = bincode::encode_to_vec(&Byte::UNKNOWN, config).unwrap();
let this: Byte = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert!(this.is_unknown());
}
#[test]
#[cfg(feature = "borsh")]
fn borsh() {
let this: Byte = Byte::from(1000);
let bytes = borsh::to_vec(&this).unwrap();
let this: Byte = borsh::from_slice(&bytes).unwrap();
assert_eq!(this, 1000);
assert_eq!(this, "1.000 KB");
assert!(borsh::from_slice::<Byte>(b"bad .-;[]124/ bytes").is_err());
let bytes = borsh::to_vec(&Byte::UNKNOWN).unwrap();
let this: Byte = borsh::from_slice(&bytes).unwrap();
assert!(this.is_unknown());
}
}