human_repr/
human_count.rs

1use super::HumanCountData;
2use crate::utils::{self, SPACE};
3use std::fmt::{self, Debug, Display};
4
5// with default features we get: SI symbols, 1000 divisor, and no spaces.
6const SPEC: &[&str] = {
7    match (cfg!(feature = "iec"), cfg!(feature = "1024")) {
8        (false, false) => &["", "k", "M", "G", "T", "P", "E", "Z", "Y"], // SI (1000).
9        (false, true) => &["", "K", "M", "G", "T", "P", "E", "Z", "Y"],  // SI (1024).
10        (true, _) => &["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"], // IEC (1024)
11    }
12};
13const DECIMALS: &[usize] = &[1, 1, 1, 2, 2, 2, 2, 2, 2];
14const DIVISOR: f64 = {
15    match cfg!(feature = "1024") {
16        true => 1024.,
17        false => 1000.,
18    }
19};
20
21impl Display for HumanCountData<'_> {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        let HumanCountData { mut val, unit } = self;
24        for (&scale, &dec) in SPEC.iter().zip(DECIMALS) {
25            match utils::rounded(val, dec) {
26                r if r.abs() >= DIVISOR => val /= DIVISOR,
27                r if r.fract() == 0. => return write!(f, "{:.0}{}{}{}", r, SPACE, scale, unit),
28                r if (r * 10.).fract() == 0. => {
29                    return write!(f, "{:.1}{}{}{}", r, SPACE, scale, unit)
30                }
31                r => return write!(f, "{:.2}{}{}{}", r, SPACE, scale, unit),
32            }
33        }
34
35        write!(f, "{:.2}{}+{}", val, SPACE, unit)
36    }
37}
38
39impl Debug for HumanCountData<'_> {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        let mut ds = f.debug_struct("HumanCount");
42        ds.field("val", &self.val);
43        ds.field("unit", &self.unit);
44        ds.finish()?;
45        write!(f, " -> ")?;
46        fmt::Display::fmt(self, f)
47    }
48}
49
50impl PartialEq<HumanCountData<'_>> for &str {
51    fn eq(&self, other: &HumanCountData<'_>) -> bool {
52        utils::display_compare(self, other)
53    }
54}
55
56impl PartialEq<&str> for HumanCountData<'_> {
57    fn eq(&self, other: &&str) -> bool {
58        other == self
59    }
60}
61
62#[cfg(all(test, not(any(feature = "1024", feature = "iec", feature = "space"))))]
63mod tests {
64    use crate::HumanCount;
65
66    #[test]
67    fn operation() {
68        assert_eq!("123kB", 123000_u64.human_count_bytes());
69        assert_eq!("123.5kB", 123456_u64.human_count_bytes());
70        assert_eq!("23B", 23u8.human_count_bytes());
71        assert_eq!("23B", 23i8.human_count_bytes());
72        assert_eq!("23.5B", 23.5123.human_count_bytes());
73        assert_eq!("-23B", (-23i8).human_count_bytes());
74        assert_eq!("1kB", 1025u16.human_count_bytes());
75        assert_eq!("-1kB", (-1025i16).human_count_bytes());
76        assert_eq!("43.2MB", 43214321u32.human_count_bytes());
77        assert_eq!("23.4GB", 23403454432_u64.human_count_bytes());
78        assert_eq!("23.43GB", 23433454432_u64.human_count_bytes());
79        assert_eq!("18.45EB", u64::MAX.human_count_bytes());
80        assert_eq!("9.22EB", i64::MAX.human_count_bytes());
81        assert_eq!("-9.22EB", i64::MIN.human_count_bytes());
82        assert_eq!("340282366920.94+B", u128::MAX.human_count_bytes());
83    }
84
85    #[test]
86    fn flexibility() {
87        assert_eq!("123MCrabs", 123e6.human_count("Crabs"));
88        assert_eq!("123MCrabs", 123e6.human_count("Crabs".to_owned()));
89        assert_eq!("123MCrabs", 123e6.human_count(&"Crabs".to_owned()));
90        assert_eq!("123k🦀", 123e3.human_count("🦀"));
91        assert_eq!("12.3k°C", 123e2.human_count("°C"));
92        assert_eq!("1.2°C", 123e-2.human_count("°C"));
93    }
94
95    #[test]
96    #[allow(clippy::needless_borrow)]
97    fn ownership() {
98        let mut a = 42000;
99        assert_eq!("42kB", a.human_count_bytes());
100        assert_eq!("42kB", (&a).human_count_bytes());
101        assert_eq!("42kB", (&mut a).human_count_bytes());
102    }
103
104    #[test]
105    fn symmetric() {
106        assert_eq!(123000_u64.human_count_bytes(), "123kB");
107    }
108}
109
110#[test]
111#[cfg(feature = "serde")]
112fn serialize() -> Result<(), serde_json::Error> {
113    use crate::HumanCount;
114    let h = 123456.human_count("X");
115    let ser = serde_json::to_string(&h)?;
116    assert_eq!(r#"{"val":123456.0,"unit":"X"}"#, &ser);
117    let h2 = serde_json::from_str::<HumanCountData>(&ser)?;
118    assert_eq!(h, h2);
119    Ok(())
120}