use crate::imp::IEC_PREFIXES as PREFIXES;
use crate::Buffer;
use crate::Error;
use paste::paste;
#[cfg(feature = "iec-units")]
mod units;
#[cfg(feature = "iec-units")]
pub use self::units::*;
#[cfg_attr(
all(feature = "derive", feature = "serde"),
doc = " - [`Serialize`](serde::Serialize) and [`Deserialize`](serde::Deserialize) when `serde` feature is enabled."
)]
#[cfg_attr(
feature = "derive",
doc = r##"```rust
use human_units::iec::iec_unit;
#[iec_unit(symbol = "nit")]
struct Nit(pub u64);
```"##
)]
#[cfg(feature = "derive")]
pub use human_units_derive::iec_unit;
pub trait IecDisplay {
const MAX_STRING_LEN: usize;
fn iec_display(self, symbol: &str) -> Display<'_, Self>
where
Self: Sized;
}
pub struct Display<'a, T> {
number: T,
symbol: &'a str,
}
pub trait IecFromStr {
fn iec_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error>
where
Self: Sized;
}
#[deprecated(
since = "0.5.3",
note = "`format_iec` method is generated by `iec_unit` macro as `const` without this trait."
)]
pub trait FormatIec {
fn format_iec(&self) -> FormattedUnit<'static>;
}
pub trait FormatIecUnit {
fn format_iec_unit(self, symbol: &str) -> FormattedUnit<'_>;
}
pub struct FormattedUnit<'symbol> {
pub(crate) prefix: &'static str,
pub(crate) symbol: &'symbol str,
pub(crate) integer: u16,
pub(crate) fraction: u8,
}
impl<'symbol> FormattedUnit<'symbol> {
pub const fn new(
prefix: &'static str,
symbol: &'symbol str,
integer: u16,
fraction: u8,
) -> Self {
Self {
prefix,
symbol,
integer,
fraction,
}
}
pub const fn prefix(&self) -> &'static str {
self.prefix
}
pub const fn symbol(&self) -> &'symbol str {
self.symbol
}
pub const fn integer(&self) -> u16 {
self.integer
}
pub const fn fraction(&self) -> u8 {
self.fraction
}
}
impl core::fmt::Display for FormattedUnit<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut buf = Buffer::<MAX_LEN>::new();
buf.write_u16(self.integer);
if self.fraction != 0 {
buf.write_byte(b'.');
buf.write_byte(b'0' + self.fraction);
}
buf.write_byte(b' ');
buf.write_str_infallible(self.prefix);
buf.write_str_infallible(self.symbol);
f.write_str(unsafe { buf.as_str() })
}
}
const MAX_LEN: usize = 64;
#[rustfmt::skip]
macro_rules! max_string_len {
(u128) => {39};
(u64) => {20};
(u32) => {10};
(u16) => {5};
}
macro_rules! parameterize {
($((
$uint: ident
$max_prefix: ident
($max_prefix_integer: expr)
($max_integer: expr)
($($ilog: expr)+)
))+) => {
paste! {
$(
impl<const N: usize> Buffer<N> {
#[doc(hidden)]
pub fn [<write_unit_ $uint _1024>]<const MIN: usize, const MAX: usize>(
&mut self,
value: $uint,
symbol: &str,
) {
let (value, i) = $crate::imp::[<unitify_ $uint _1024>]::<MIN, MAX>(value);
self.[<write_ $uint>](value);
self.write_byte(b' ');
self.write_str_infallible(PREFIXES[i]);
self.write_str_infallible(symbol);
}
}
impl IecDisplay for $uint {
const MAX_STRING_LEN: usize = 64;
fn iec_display(self, symbol: &str) -> Display<'_, Self> {
Display { number: self, symbol }
}
}
impl core::fmt::Display for Display<'_, $uint> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
debug_assert!(
self.symbol.len() <= <$uint as IecDisplay>::MAX_STRING_LEN - max_string_len!($uint),
"The symbol is too long: {} > {}",
self.symbol.len(),
<$uint as IecDisplay>::MAX_STRING_LEN - max_string_len!($uint),
);
let mut buffer: Buffer<{ <$uint as IecDisplay>::MAX_STRING_LEN }> = Buffer::new();
buffer.[<write_unit_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(self.number, self.symbol);
f.write_str(unsafe { buffer.as_str() })
}
}
impl IecFromStr for $uint {
fn iec_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error> {
$crate::imp::[<$uint _unit_from_str>]::<1024>(
string,
symbol,
&PREFIXES[..=Prefix::$max_prefix as usize]
)
}
}
impl FormatIecUnit for $uint {
fn format_iec_unit(self, symbol: &str) -> FormattedUnit<'_> {
#![allow(clippy::int_plus_one)]
$(
{
const SCALE: $uint = (1024 as $uint).pow($ilog);
if self >= SCALE {
let integer = self / SCALE;
let mut fraction = self % SCALE;
if fraction != 0 {
fraction = match fraction.checked_mul(5) {
Some(numerator) => numerator / (SCALE / 2),
None => {
debug_assert_eq!(0, SCALE % 16);
(fraction / 8) * 5 / (SCALE / 16)
}
};
}
debug_assert!(integer <= $max_integer, "integer = {integer}");
debug_assert!(fraction <= 9, "fraction = {fraction}");
return FormattedUnit {
integer: integer as u16,
fraction: fraction as u8,
prefix: PREFIXES[$ilog],
symbol,
};
}
}
)+
let integer = self;
debug_assert!(integer <= $max_integer, "integer = {integer}");
FormattedUnit {
integer: integer as u16,
fraction: 0,
prefix: PREFIXES[0],
symbol,
}
}
}
)+
#[cfg(test)]
mod unitify_tests {
use super::*;
use arbtest::arbtest;
use alloc::format;
use alloc::string::String;
use alloc::string::ToString;
extern crate alloc;
$(
#[test]
fn [<test_unitify_ $uint>]() {
arbtest(|u| {
let number: $uint = u.arbitrary()?;
let (x, prefix) = $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(number);
let p = prefix as u32 - Prefix::None as u32;
let multiplier = (1024 as $uint).pow(p);
assert_eq!(number, x * multiplier, "x = {x}, multiplier = {multiplier}");
Ok(())
});
}
#[test]
fn [<test_max_string_len_ $uint>]() {
let string = format!("{}", $uint::MAX);
assert_eq!(max_string_len!($uint), string.len());
}
#[test]
fn [<test_buffer_io_ $uint>]() {
arbtest(|u| {
let number: $uint = u.arbitrary()?;
let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
let mut buffer = Buffer::<MAX_LEN>::new();
buffer.[<write_unit_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(number, &symbol);
let actual = $uint::iec_unit_from_str(unsafe { buffer.as_str() }, &symbol)
.unwrap_or_else(|_| panic!("String = {:?}, number = {number}, symbol = {symbol:?}", unsafe { buffer.as_str() }));
assert_eq!(number, actual);
Ok(())
});
}
#[test]
fn [<test_string_io_ $uint>]() {
arbtest(|u| {
let number: $uint = u.arbitrary()?;
let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
let string = format!("{}", number.iec_display(&symbol));
let actual = $uint::iec_unit_from_str(&string, &symbol).unwrap();
assert_eq!(number, actual);
Ok(())
});
}
#[test]
fn [<check_prefix_ $uint>]() {
const MAX_POW_OF_1024: $uint = (1024 as $uint).pow($uint::MAX.ilog(1024));
assert_eq!(None, MAX_POW_OF_1024.checked_mul(1024));
assert_eq!(
(1, Prefix::Kibi as usize),
$crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(1024)
);
assert_eq!(
($max_prefix_integer, Prefix::$max_prefix as usize),
$crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(MAX_POW_OF_1024),
"MAX_POW_OF_1024 = {MAX_POW_OF_1024}"
);
}
#[test]
fn [<test_format_unit_ $uint>]() {
arbtest(|u| {
let exact: $uint = u.arbitrary()?;
let FormattedUnit { integer, fraction, prefix, .. } = exact.format_iec_unit("");
let i = PREFIXES.iter().position(|p| p == &prefix).unwrap();
let factor = (1024 as $uint).pow(i as u32);
let inexact = (integer as $uint) * factor + (fraction as $uint) * (factor / 10);
assert!(
exact >= inexact && (exact - inexact) < factor.saturating_mul($max_integer),
"Exact = {exact},\ninexact = {inexact},\nexact - inexact = {}, factor = {factor},\ninteger = {integer}, fraction = {fraction}, prefix = {prefix:?}",
exact - inexact,
);
Ok(())
});
}
)+
}
}
};
}
parameterize! {
(u128 Quebi (1024*1024) (1024*1024*1024 - 1) (10 9 8 7 6 5 4 3 2 1))
(u64 Exbi (1) (1023) (6 5 4 3 2 1))
(u32 Gibi (1) (1023) (3 2 1))
(u16 Kibi (1) (1023) (1))
}
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
#[repr(u8)]
pub enum Prefix {
#[default]
None = 0,
Kibi = 1,
Mebi = 2,
Gibi = 3,
Tebi = 4,
Pebi = 5,
Exbi = 6,
Zebi = 7,
Yobi = 8,
Robi = 9,
Quebi = 10,
}
impl Prefix {
pub const fn as_str(self) -> &'static str {
crate::imp::IEC_PREFIXES[self as usize]
}
pub const ALL: [Self; 11] = {
use Prefix::*;
[
None, Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi, Robi, Quebi,
]
};
}
impl core::fmt::Display for Prefix {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use alloc::format;
use arbitrary::Arbitrary;
use arbitrary::Unstructured;
use arbtest::arbtest;
extern crate alloc;
#[test]
fn test_io() {
arbtest(|u| {
let expected: FormattedUnit = u.arbitrary()?;
let string = expected.to_string();
let mut words = string.splitn(2, ' ');
let number_str = words.next().unwrap();
let unit = words.next().unwrap().to_string();
let mut words = number_str.splitn(2, '.');
let integer: u16 = words.next().unwrap().parse().unwrap();
let fraction: u8 = match words.next() {
Some(word) => word.parse().unwrap(),
None => 0,
};
assert_eq!(expected.integer, integer, "string = {string:?}");
assert_eq!(expected.fraction, fraction);
assert_eq!(
format!("{}{}", expected.prefix, expected.symbol),
unit,
"expected = `{}`",
expected
);
Ok(())
});
}
impl<'a> Arbitrary<'a> for FormattedUnit<'static> {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
Ok(Self {
prefix: *u.choose(&PREFIXES[..])?,
symbol: "",
integer: u.int_in_range(0..=999)?,
fraction: u.int_in_range(0..=9)?,
})
}
}
}