i3status_rs/formatting/
prefix.rs

1use crate::errors::*;
2use std::fmt;
3use std::str::FromStr;
4
5/// SI prefix
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub enum Prefix {
8    /// `n`
9    Nano,
10    /// `u`
11    Micro,
12    /// `m`
13    Milli,
14    /// `1`
15    One,
16    /// `1i`
17    /// `1i` is a special prefix which means "one but binary". `1i` is to `1` as `Ki` is to `K`.
18    OneButBinary,
19    /// `K`
20    Kilo,
21    /// `Ki`
22    Kibi,
23    /// `M`
24    Mega,
25    /// `Mi`
26    Mebi,
27    /// `G`
28    Giga,
29    /// `Gi`
30    Gibi,
31    /// `T`
32    Tera,
33    /// `Ti`
34    Tebi,
35}
36
37const MUL: [f64; 13] = [
38    1e-9,
39    1e-6,
40    1e-3,
41    1.0,
42    1.0,
43    1e3,
44    1024.0,
45    1e6,
46    1024.0 * 1024.0,
47    1e9,
48    1024.0 * 1024.0 * 1024.0,
49    1e12,
50    1024.0 * 1024.0 * 1024.0 * 1024.0,
51];
52
53impl Prefix {
54    pub fn min_available() -> Self {
55        Self::Nano
56    }
57
58    pub fn max_available() -> Self {
59        Self::Tebi
60    }
61
62    pub fn max(self, other: Self) -> Self {
63        if other > self { other } else { self }
64    }
65
66    pub fn apply(self, value: f64) -> f64 {
67        value / MUL[self as usize]
68    }
69
70    pub fn eng(mut number: f64) -> Self {
71        if number == 0.0 {
72            Self::One
73        } else {
74            number = number.abs();
75            if number > 1.0 {
76                number = number.round();
77            } else {
78                let round_up_to = -(number.log10().ceil() as i32);
79                let m = 10f64.powi(round_up_to);
80                number = (number * m).round() / m;
81            }
82            match number.log10().div_euclid(3.) as i32 {
83                i32::MIN..=-3 => Prefix::Nano,
84                -2 => Prefix::Micro,
85                -1 => Prefix::Milli,
86                0 => Prefix::One,
87                1 => Prefix::Kilo,
88                2 => Prefix::Mega,
89                3 => Prefix::Giga,
90                4..=i32::MAX => Prefix::Tera,
91            }
92        }
93    }
94
95    pub fn eng_binary(number: f64) -> Self {
96        if number == 0.0 {
97            Self::One
98        } else {
99            match number.abs().round().log2().div_euclid(10.) as i32 {
100                i32::MIN..=0 => Prefix::OneButBinary,
101                1 => Prefix::Kibi,
102                2 => Prefix::Mebi,
103                3 => Prefix::Gibi,
104                4..=i32::MAX => Prefix::Tebi,
105            }
106        }
107    }
108
109    pub fn is_binary(&self) -> bool {
110        matches!(
111            self,
112            Self::OneButBinary | Self::Kibi | Self::Mebi | Self::Gibi | Self::Tebi
113        )
114    }
115}
116
117impl fmt::Display for Prefix {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        f.write_str(match self {
120            Self::Nano => "n",
121            Self::Micro => "u",
122            Self::Milli => "m",
123            Self::One | Self::OneButBinary => "",
124            Self::Kilo => "K",
125            Self::Kibi => "Ki",
126            Self::Mega => "M",
127            Self::Mebi => "Mi",
128            Self::Giga => "G",
129            Self::Gibi => "Gi",
130            Self::Tera => "T",
131            Self::Tebi => "Ti",
132        })
133    }
134}
135
136impl FromStr for Prefix {
137    type Err = Error;
138
139    fn from_str(s: &str) -> Result<Self> {
140        match s {
141            "n" => Ok(Prefix::Nano),
142            "u" => Ok(Prefix::Micro),
143            "m" => Ok(Prefix::Milli),
144            "1" => Ok(Prefix::One),
145            "1i" => Ok(Prefix::OneButBinary),
146            "K" => Ok(Prefix::Kilo),
147            "Ki" => Ok(Prefix::Kibi),
148            "M" => Ok(Prefix::Mega),
149            "Mi" => Ok(Prefix::Mebi),
150            "G" => Ok(Prefix::Giga),
151            "Gi" => Ok(Prefix::Gibi),
152            "T" => Ok(Prefix::Tera),
153            "Ti" => Ok(Prefix::Tebi),
154            x => Err(Error::new(format!("Unknown prefix: '{x}'"))),
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn eng() {
165        assert_eq!(Prefix::eng(0.000_000_000_1), Prefix::Nano);
166        assert_eq!(Prefix::eng(0.000_000_001), Prefix::Nano);
167        assert_eq!(Prefix::eng(0.000_000_01), Prefix::Nano);
168        assert_eq!(Prefix::eng(0.000_000_1), Prefix::Nano);
169        assert_eq!(Prefix::eng(0.000_001), Prefix::Micro);
170        assert_eq!(Prefix::eng(0.000_01), Prefix::Micro);
171        assert_eq!(Prefix::eng(0.000_1), Prefix::Micro);
172        assert_eq!(Prefix::eng(0.001), Prefix::Milli);
173        assert_eq!(Prefix::eng(0.01), Prefix::Milli);
174        assert_eq!(Prefix::eng(0.1), Prefix::Milli);
175        assert_eq!(Prefix::eng(1.0), Prefix::One);
176        assert_eq!(Prefix::eng(10.0), Prefix::One);
177        assert_eq!(Prefix::eng(100.0), Prefix::One);
178        assert_eq!(Prefix::eng(1_000.0), Prefix::Kilo);
179        assert_eq!(Prefix::eng(10_000.0), Prefix::Kilo);
180        assert_eq!(Prefix::eng(100_000.0), Prefix::Kilo);
181        assert_eq!(Prefix::eng(1_000_000.0), Prefix::Mega);
182        assert_eq!(Prefix::eng(10_000_000.0), Prefix::Mega);
183        assert_eq!(Prefix::eng(100_000_000.0), Prefix::Mega);
184        assert_eq!(Prefix::eng(1_000_000_000.0), Prefix::Giga);
185        assert_eq!(Prefix::eng(10_000_000_000.0), Prefix::Giga);
186        assert_eq!(Prefix::eng(100_000_000_000.0), Prefix::Giga);
187        assert_eq!(Prefix::eng(1_000_000_000_000.0), Prefix::Tera);
188        assert_eq!(Prefix::eng(10_000_000_000_000.0), Prefix::Tera);
189        assert_eq!(Prefix::eng(100_000_000_000_000.0), Prefix::Tera);
190        assert_eq!(Prefix::eng(1_000_000_000_000_000.0), Prefix::Tera);
191    }
192
193    #[test]
194    fn eng_round() {
195        assert_eq!(Prefix::eng(0.000_000_000_09), Prefix::Nano);
196        assert_eq!(Prefix::eng(0.000_000_000_9), Prefix::Nano);
197        assert_eq!(Prefix::eng(0.000_000_009), Prefix::Nano);
198        assert_eq!(Prefix::eng(0.000_000_09), Prefix::Nano);
199        assert_eq!(Prefix::eng(0.000_000_9), Prefix::Micro);
200        assert_eq!(Prefix::eng(0.000_009), Prefix::Micro);
201        assert_eq!(Prefix::eng(0.000_09), Prefix::Micro);
202        assert_eq!(Prefix::eng(0.000_9), Prefix::Milli);
203        assert_eq!(Prefix::eng(0.009), Prefix::Milli);
204        assert_eq!(Prefix::eng(0.09), Prefix::Milli);
205        assert_eq!(Prefix::eng(0.9), Prefix::One);
206        assert_eq!(Prefix::eng(9.9), Prefix::One);
207        assert_eq!(Prefix::eng(99.9), Prefix::One);
208        assert_eq!(Prefix::eng(999.9), Prefix::Kilo);
209        assert_eq!(Prefix::eng(9_999.9), Prefix::Kilo);
210        assert_eq!(Prefix::eng(99_999.9), Prefix::Kilo);
211        assert_eq!(Prefix::eng(999_999.9), Prefix::Mega);
212        assert_eq!(Prefix::eng(9_999_999.9), Prefix::Mega);
213        assert_eq!(Prefix::eng(99_999_999.9), Prefix::Mega);
214        assert_eq!(Prefix::eng(999_999_999.9), Prefix::Giga);
215        assert_eq!(Prefix::eng(9_999_999_999.9), Prefix::Giga);
216        assert_eq!(Prefix::eng(99_999_999_999.9), Prefix::Giga);
217        assert_eq!(Prefix::eng(999_999_999_999.9), Prefix::Tera);
218        assert_eq!(Prefix::eng(9_999_999_999_999.9), Prefix::Tera);
219        assert_eq!(Prefix::eng(99_999_999_999_999.9), Prefix::Tera);
220        assert_eq!(Prefix::eng(999_999_999_999_999.9), Prefix::Tera);
221    }
222
223    #[test]
224    fn eng_binary() {
225        assert_eq!(Prefix::eng_binary(0.1), Prefix::OneButBinary);
226        assert_eq!(Prefix::eng_binary(1.0), Prefix::OneButBinary);
227        assert_eq!(Prefix::eng_binary((1 << 9) as f64), Prefix::OneButBinary);
228        assert_eq!(Prefix::eng_binary((1 << 10) as f64), Prefix::Kibi);
229        assert_eq!(Prefix::eng_binary((1 << 19) as f64), Prefix::Kibi);
230        assert_eq!(Prefix::eng_binary((1 << 29) as f64), Prefix::Mebi);
231        assert_eq!(Prefix::eng_binary((1 << 20) as f64), Prefix::Mebi);
232        assert_eq!(Prefix::eng_binary((1 << 30) as f64), Prefix::Gibi);
233        assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64), Prefix::Gibi);
234        assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64), Prefix::Tebi);
235        assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64), Prefix::Tebi);
236        assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64), Prefix::Tebi);
237    }
238
239    #[test]
240    fn eng_binary_round() {
241        assert_eq!(Prefix::eng_binary(0.9), Prefix::OneButBinary);
242        assert_eq!(
243            Prefix::eng_binary((1 << 9) as f64 - 0.1),
244            Prefix::OneButBinary
245        );
246        assert_eq!(Prefix::eng_binary((1 << 10) as f64 - 0.1), Prefix::Kibi);
247        assert_eq!(Prefix::eng_binary((1 << 19) as f64 - 0.1), Prefix::Kibi);
248        assert_eq!(Prefix::eng_binary((1 << 29) as f64 - 0.1), Prefix::Mebi);
249        assert_eq!(Prefix::eng_binary((1 << 20) as f64 - 0.1), Prefix::Mebi);
250        assert_eq!(Prefix::eng_binary((1 << 30) as f64 - 0.1), Prefix::Gibi);
251        assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64 - 0.1), Prefix::Gibi);
252        assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64 - 0.1), Prefix::Tebi);
253        assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64 - 0.1), Prefix::Tebi);
254        assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64 - 0.1), Prefix::Tebi);
255    }
256}