#![no_std]
#![warn(missing_docs)]
extern crate generic_array;
extern crate num;
use core::{
cmp,
fmt::{self, Display},
marker::PhantomData,
};
use num::{integer::Integer, rational::Ratio, traits::cast::FromPrimitive, traits::Pow};
mod config;
pub use self::config::{
BinaryPrefixes, CommaSeparated, DecimalSeparator, PointSeparated, PrefixType, SIPrefixes,
};
const DEFAULT_PRECISION: usize = 1;
pub type SizeFormatterSI = SizeFormatter<u64, SIPrefixes, PointSeparated>;
pub type SizeFormatterBinary = SizeFormatter<u64, BinaryPrefixes, PointSeparated>;
pub struct SizeFormatter<BaseType, Prefix, Separator>
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
Prefix: PrefixType,
Separator: DecimalSeparator,
{
num: BaseType,
_marker: PhantomData<(Prefix, Separator)>,
}
impl<BaseType, Prefix, Separator> SizeFormatter<BaseType, Prefix, Separator>
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
Prefix: PrefixType,
Separator: DecimalSeparator,
{
pub fn new(num: BaseType) -> SizeFormatter<BaseType, Prefix, Separator> {
SizeFormatter {
num,
_marker: PhantomData,
}
}
pub fn from<T: Into<BaseType>>(num: T) -> SizeFormatter<BaseType, Prefix, Separator> {
SizeFormatter {
num: num.into(),
_marker: PhantomData,
}
}
}
impl<BaseType, Prefix, Separator> Display for SizeFormatter<BaseType, Prefix, Separator>
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
Prefix: PrefixType,
Separator: DecimalSeparator,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let max_prefix = Prefix::prefixes().len() - 1;
let precision = f.precision().unwrap_or(DEFAULT_PRECISION);
let prefix_size = BaseType::from_u32(Prefix::PREFIX_SIZE)
.expect("prefix size is too large for number type");
let divisions = cmp::min(int_log(self.num.clone(), prefix_size.clone()), max_prefix);
let precision = cmp::min(precision, divisions * 3);
let ratio = Ratio::<BaseType>::new(self.num.clone(), prefix_size.pow(divisions as u32));
let format_number = FormatRatio::<BaseType, Separator>::new(ratio);
write!(
f,
"{:.*}{}",
precision,
format_number,
Prefix::prefixes()[divisions]
)
}
}
fn int_log<BaseType>(mut num: BaseType, base: BaseType) -> usize
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
{
let mut divisions = 0;
while num >= base {
num = num / base.clone();
divisions += 1;
}
divisions
}
struct FormatRatio<BaseType, Separator>
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
Separator: DecimalSeparator,
{
num: Ratio<BaseType>,
_marker: PhantomData<Separator>,
}
impl<BaseType, Separator> FormatRatio<BaseType, Separator>
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
Separator: DecimalSeparator,
{
fn new(num: Ratio<BaseType>) -> FormatRatio<BaseType, Separator> {
FormatRatio {
num,
_marker: PhantomData,
}
}
}
impl<BaseType, Separator> Display for FormatRatio<BaseType, Separator>
where
BaseType: Clone + Integer + Display + FromPrimitive + Pow<u32, Output = BaseType>,
Ratio<BaseType>: FromPrimitive,
Separator: DecimalSeparator,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.num.trunc())?;
let precision = f.precision().unwrap_or(DEFAULT_PRECISION);
if precision > 0 {
write!(f, "{}", Separator::SEPARATOR)?;
let mut frac = self.num.fract();
for _ in 0..precision {
if frac.is_integer() {
write!(f, "0")?;
} else {
frac = frac * Ratio::from_u64(10).unwrap();
write!(f, "{}", frac.trunc())?;
frac = frac.fract();
}
}
}
Ok(())
}
}
#[cfg(test)]
#[macro_use]
extern crate std;
#[cfg(test)]
mod tests {
use super::*;
use std::string::ToString;
#[test]
fn small_sizes() {
assert_eq!(format!("{}B", SizeFormatterSI::new(0)), "0B".to_string());
assert_eq!(format!("{}B", SizeFormatterSI::new(1)), "1B".to_string());
assert_eq!(
format!("{}B", SizeFormatterSI::new(999)),
"999B".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(0)),
"0B".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(1)),
"1B".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(999)),
"999B".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(1_000)),
"1.0kB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(55_000)),
"55.0kB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(999_999)),
"999.9kB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(1_000_000)),
"1.0MB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(1 * 1024)),
"1.0KiB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(55 * 1024)),
"55.0KiB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(999 * 1024 + 1023)),
"999.9KiB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterBinary::new(1 * 1024 * 1024)),
"1.0MiB".to_string()
);
}
#[test]
fn big_sizes() {
assert_eq!(
format!("{}B", SizeFormatterSI::new(387_854_348_875)),
"387.8GB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(123_456_789_999_999)),
"123.4TB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(499_999_999_999_999_999)),
"499.9PB".to_string()
);
assert_eq!(
format!("{}B", SizeFormatterSI::new(1_000_000_000_000_000_000)),
"1.0EB".to_string()
);
assert_eq!(
format!(
"{}B",
SizeFormatter::<u128, SIPrefixes, PointSeparated>::new(
1_000_000_000_000_000_000_000
)
),
"1.0ZB".to_string()
);
assert_eq!(
format!(
"{}B",
SizeFormatter::<u128, SIPrefixes, PointSeparated>::new(
1_000_000_000_000_000_000_000_000
)
),
"1.0YB".to_string()
);
}
#[test]
fn exceeds_yotta() {
assert_eq!(
format!(
"{}B",
SizeFormatter::<u128, SIPrefixes, PointSeparated>::new(
1_000_000_000_000_000_000_000_000_000
)
),
"1000.0YB".to_string()
);
assert_eq!(
format!(
"{}B",
SizeFormatter::<u128, SIPrefixes, PointSeparated>::new(
1_000_000_000_000_000_000_000_000_000_000
)
),
"1000000.0YB".to_string()
);
}
#[test]
fn precision() {
assert_eq!(format!("{:.9}B", SizeFormatterSI::new(1)), "1B".to_string());
assert_eq!(
format!("{:.0}B", SizeFormatterSI::new(1_111)),
"1kB".to_string()
);
assert_eq!(
format!("{:.1}B", SizeFormatterSI::new(1_111)),
"1.1kB".to_string()
);
assert_eq!(
format!("{:.2}B", SizeFormatterSI::new(1_111)),
"1.11kB".to_string()
);
assert_eq!(
format!("{:.3}B", SizeFormatterSI::new(1_111)),
"1.111kB".to_string()
);
assert_eq!(
format!("{:.4}B", SizeFormatterSI::new(1_111)),
"1.111kB".to_string()
);
assert_eq!(
format!("{:.4}B", SizeFormatterSI::new(1_000_100)),
"1.0001MB".to_string()
);
assert_eq!(
format!("{:.4}B", SizeFormatterSI::new(1_500_000)),
"1.5000MB".to_string()
);
assert_eq!(
format!("{:.4}B", SizeFormatterSI::new(1_000_000)),
"1.0000MB".to_string()
);
}
#[test]
fn configurations() {
assert_eq!(
format!(
"{}B",
SizeFormatter::<u16, SIPrefixes, CommaSeparated>::new(65_535)
),
"65,5kB".to_string()
);
assert_eq!(
format!(
"{}B",
SizeFormatter::<u16, BinaryPrefixes, PointSeparated>::new(65_535)
),
"63.9KiB".to_string()
);
}
#[test]
fn from() {
assert_eq!(
format!("{}B", SizeFormatterSI::from(546_987u32)),
"546.9kB".to_string()
);
}
#[test]
#[should_panic(expected = "prefix size is too large")]
fn incompatile_base_type_fails() {
assert_eq!(
format!(
"{}B",
SizeFormatter::<u8, SIPrefixes, CommaSeparated>::new(10)
),
"65.5kB".to_string()
);
}
}