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