i3status_rs/formatting/
prefix.rs

1use crate::errors::*;
2use nom::IResult;
3use nom::Parser as _;
4use nom::branch::alt;
5use nom::bytes::complete::tag;
6use nom::combinator::{all_consuming, map, opt, value};
7use nom::number::complete::double;
8use std::fmt;
9use std::str::FromStr;
10
11/// SI prefix
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
13pub enum Prefix {
14    /// `n`
15    Nano,
16    /// `u`
17    Micro,
18    /// `m`
19    Milli,
20    /// `1`
21    #[default]
22    One,
23    /// `1i`
24    /// `1i` is a special prefix which means "one but binary". `1i` is to `1` as `Ki` is to `K`.
25    OneButBinary,
26    /// `K`
27    Kilo,
28    /// `Ki`
29    Kibi,
30    /// `M`
31    Mega,
32    /// `Mi`
33    Mebi,
34    /// `G`
35    Giga,
36    /// `Gi`
37    Gibi,
38    /// `T`
39    Tera,
40    /// `Ti`
41    Tebi,
42}
43
44const MUL: [f64; 13] = [
45    1e-9,
46    1e-6,
47    1e-3,
48    1.0,
49    1.0,
50    1e3,
51    1024.0,
52    1e6,
53    1024.0 * 1024.0,
54    1e9,
55    1024.0 * 1024.0 * 1024.0,
56    1e12,
57    1024.0 * 1024.0 * 1024.0 * 1024.0,
58];
59
60impl Prefix {
61    #[inline]
62    pub const fn min_available() -> Self {
63        Self::Nano
64    }
65
66    #[inline]
67    pub const fn max_available() -> Self {
68        Self::Tebi
69    }
70
71    #[inline]
72    pub fn max(self, other: Self) -> Self {
73        if other > self { other } else { self }
74    }
75
76    #[inline]
77    pub const fn apply(self, value: f64) -> f64 {
78        value / MUL[self as usize]
79    }
80
81    #[inline]
82    pub const fn unapply(self, value: f64) -> f64 {
83        value * MUL[self as usize]
84    }
85
86    pub fn eng(mut number: f64) -> Self {
87        if number == 0.0 {
88            Self::One
89        } else {
90            number = number.abs();
91            if number > 1.0 {
92                number = number.round();
93            } else {
94                let round_up_to = -(number.log10().ceil() as i32);
95                let m = 10f64.powi(round_up_to);
96                number = (number * m).round() / m;
97            }
98            match number.log10().div_euclid(3.) as i32 {
99                i32::MIN..=-3 => Prefix::Nano,
100                -2 => Prefix::Micro,
101                -1 => Prefix::Milli,
102                0 => Prefix::One,
103                1 => Prefix::Kilo,
104                2 => Prefix::Mega,
105                3 => Prefix::Giga,
106                4..=i32::MAX => Prefix::Tera,
107            }
108        }
109    }
110
111    pub fn eng_binary(number: f64) -> Self {
112        if number == 0.0 {
113            Self::One
114        } else {
115            match number.abs().round().log2().div_euclid(10.) as i32 {
116                i32::MIN..=0 => Prefix::OneButBinary,
117                1 => Prefix::Kibi,
118                2 => Prefix::Mebi,
119                3 => Prefix::Gibi,
120                4..=i32::MAX => Prefix::Tebi,
121            }
122        }
123    }
124
125    #[inline]
126    pub const fn is_binary(&self) -> bool {
127        matches!(
128            self,
129            Self::OneButBinary | Self::Kibi | Self::Mebi | Self::Gibi | Self::Tebi
130        )
131    }
132}
133
134impl fmt::Display for Prefix {
135    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136        f.write_str(match self {
137            Self::Nano => "n",
138            Self::Micro => "u",
139            Self::Milli => "m",
140            Self::One | Self::OneButBinary => "",
141            Self::Kilo => "K",
142            Self::Kibi => "Ki",
143            Self::Mega => "M",
144            Self::Mebi => "Mi",
145            Self::Giga => "G",
146            Self::Gibi => "Gi",
147            Self::Tera => "T",
148            Self::Tebi => "Ti",
149        })
150    }
151}
152
153impl FromStr for Prefix {
154    type Err = Error;
155
156    fn from_str(s: &str) -> Result<Self> {
157        match s {
158            "n" => Ok(Prefix::Nano),
159            "u" => Ok(Prefix::Micro),
160            "m" => Ok(Prefix::Milli),
161            "1" => Ok(Prefix::One),
162            "1i" => Ok(Prefix::OneButBinary),
163            "K" => Ok(Prefix::Kilo),
164            "Ki" => Ok(Prefix::Kibi),
165            "M" => Ok(Prefix::Mega),
166            "Mi" => Ok(Prefix::Mebi),
167            "G" => Ok(Prefix::Giga),
168            "Gi" => Ok(Prefix::Gibi),
169            "T" => Ok(Prefix::Tera),
170            "Ti" => Ok(Prefix::Tebi),
171            x => Err(Error::new(format!("Unknown prefix: '{x}'"))),
172        }
173    }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
177pub struct ValuePrefix(pub f64, pub Prefix);
178
179impl ValuePrefix {
180    #[inline]
181    pub const fn value(&self) -> f64 {
182        self.0
183    }
184
185    #[inline]
186    pub const fn prefix(&self) -> Prefix {
187        self.1
188    }
189
190    #[inline]
191    pub const fn result(&self) -> f64 {
192        self.prefix().unapply(self.value())
193    }
194}
195
196impl fmt::Display for ValuePrefix {
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198        write!(f, "{}{}", self.value(), self.prefix())
199    }
200}
201
202impl FromStr for ValuePrefix {
203    type Err = Error;
204
205    fn from_str(s: &str) -> Result<Self, Self::Err> {
206        parse_value_prefix(s)
207            .map(|(_, v)| v)
208            .map_err(|e| Error::new(format!("Failed to parse value prefix: {e}")))
209    }
210}
211
212fn parse_prefix(input: &str) -> IResult<&str, Prefix> {
213    map(
214        opt(alt((
215            value(Prefix::Kibi, tag("Ki")),
216            value(Prefix::Mebi, tag("Mi")),
217            value(Prefix::Gibi, tag("Gi")),
218            value(Prefix::Tebi, tag("Ti")),
219            value(Prefix::Nano, tag("n")),
220            value(Prefix::Micro, tag("u")),
221            value(Prefix::Milli, tag("m")),
222            value(Prefix::OneButBinary, tag("i")),
223            value(Prefix::Kilo, tag("K")),
224            value(Prefix::Mega, tag("M")),
225            value(Prefix::Giga, tag("G")),
226            value(Prefix::Tera, tag("T")),
227        ))),
228        |p| p.unwrap_or_default(),
229    )
230    .parse(input)
231}
232
233fn parse_value_prefix(input: &str) -> IResult<&str, ValuePrefix> {
234    all_consuming(map((double, parse_prefix), |(v, p)| ValuePrefix(v, p))).parse(input)
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn eng() {
243        assert_eq!(Prefix::eng(0.000_000_000_1), Prefix::Nano);
244        assert_eq!(Prefix::eng(0.000_000_001), Prefix::Nano);
245        assert_eq!(Prefix::eng(0.000_000_01), Prefix::Nano);
246        assert_eq!(Prefix::eng(0.000_000_1), Prefix::Nano);
247        assert_eq!(Prefix::eng(0.000_001), Prefix::Micro);
248        assert_eq!(Prefix::eng(0.000_01), Prefix::Micro);
249        assert_eq!(Prefix::eng(0.000_1), Prefix::Micro);
250        assert_eq!(Prefix::eng(0.001), Prefix::Milli);
251        assert_eq!(Prefix::eng(0.01), Prefix::Milli);
252        assert_eq!(Prefix::eng(0.1), Prefix::Milli);
253        assert_eq!(Prefix::eng(1.0), Prefix::One);
254        assert_eq!(Prefix::eng(10.0), Prefix::One);
255        assert_eq!(Prefix::eng(100.0), Prefix::One);
256        assert_eq!(Prefix::eng(1_000.0), Prefix::Kilo);
257        assert_eq!(Prefix::eng(10_000.0), Prefix::Kilo);
258        assert_eq!(Prefix::eng(100_000.0), Prefix::Kilo);
259        assert_eq!(Prefix::eng(1_000_000.0), Prefix::Mega);
260        assert_eq!(Prefix::eng(10_000_000.0), Prefix::Mega);
261        assert_eq!(Prefix::eng(100_000_000.0), Prefix::Mega);
262        assert_eq!(Prefix::eng(1_000_000_000.0), Prefix::Giga);
263        assert_eq!(Prefix::eng(10_000_000_000.0), Prefix::Giga);
264        assert_eq!(Prefix::eng(100_000_000_000.0), Prefix::Giga);
265        assert_eq!(Prefix::eng(1_000_000_000_000.0), Prefix::Tera);
266        assert_eq!(Prefix::eng(10_000_000_000_000.0), Prefix::Tera);
267        assert_eq!(Prefix::eng(100_000_000_000_000.0), Prefix::Tera);
268        assert_eq!(Prefix::eng(1_000_000_000_000_000.0), Prefix::Tera);
269    }
270
271    #[test]
272    fn eng_round() {
273        assert_eq!(Prefix::eng(0.000_000_000_09), Prefix::Nano);
274        assert_eq!(Prefix::eng(0.000_000_000_9), Prefix::Nano);
275        assert_eq!(Prefix::eng(0.000_000_009), Prefix::Nano);
276        assert_eq!(Prefix::eng(0.000_000_09), Prefix::Nano);
277        assert_eq!(Prefix::eng(0.000_000_9), Prefix::Micro);
278        assert_eq!(Prefix::eng(0.000_009), Prefix::Micro);
279        assert_eq!(Prefix::eng(0.000_09), Prefix::Micro);
280        assert_eq!(Prefix::eng(0.000_9), Prefix::Milli);
281        assert_eq!(Prefix::eng(0.009), Prefix::Milli);
282        assert_eq!(Prefix::eng(0.09), Prefix::Milli);
283        assert_eq!(Prefix::eng(0.9), Prefix::One);
284        assert_eq!(Prefix::eng(9.9), Prefix::One);
285        assert_eq!(Prefix::eng(99.9), Prefix::One);
286        assert_eq!(Prefix::eng(999.9), Prefix::Kilo);
287        assert_eq!(Prefix::eng(9_999.9), Prefix::Kilo);
288        assert_eq!(Prefix::eng(99_999.9), Prefix::Kilo);
289        assert_eq!(Prefix::eng(999_999.9), Prefix::Mega);
290        assert_eq!(Prefix::eng(9_999_999.9), Prefix::Mega);
291        assert_eq!(Prefix::eng(99_999_999.9), Prefix::Mega);
292        assert_eq!(Prefix::eng(999_999_999.9), Prefix::Giga);
293        assert_eq!(Prefix::eng(9_999_999_999.9), Prefix::Giga);
294        assert_eq!(Prefix::eng(99_999_999_999.9), Prefix::Giga);
295        assert_eq!(Prefix::eng(999_999_999_999.9), Prefix::Tera);
296        assert_eq!(Prefix::eng(9_999_999_999_999.9), Prefix::Tera);
297        assert_eq!(Prefix::eng(99_999_999_999_999.9), Prefix::Tera);
298        assert_eq!(Prefix::eng(999_999_999_999_999.9), Prefix::Tera);
299    }
300
301    #[test]
302    fn eng_binary() {
303        assert_eq!(Prefix::eng_binary(0.1), Prefix::OneButBinary);
304        assert_eq!(Prefix::eng_binary(1.0), Prefix::OneButBinary);
305        assert_eq!(Prefix::eng_binary((1 << 9) as f64), Prefix::OneButBinary);
306        assert_eq!(Prefix::eng_binary((1 << 10) as f64), Prefix::Kibi);
307        assert_eq!(Prefix::eng_binary((1 << 19) as f64), Prefix::Kibi);
308        assert_eq!(Prefix::eng_binary((1 << 29) as f64), Prefix::Mebi);
309        assert_eq!(Prefix::eng_binary((1 << 20) as f64), Prefix::Mebi);
310        assert_eq!(Prefix::eng_binary((1 << 30) as f64), Prefix::Gibi);
311        assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64), Prefix::Gibi);
312        assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64), Prefix::Tebi);
313        assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64), Prefix::Tebi);
314        assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64), Prefix::Tebi);
315    }
316
317    #[test]
318    fn eng_binary_round() {
319        assert_eq!(Prefix::eng_binary(0.9), Prefix::OneButBinary);
320        assert_eq!(
321            Prefix::eng_binary((1 << 9) as f64 - 0.1),
322            Prefix::OneButBinary
323        );
324        assert_eq!(Prefix::eng_binary((1 << 10) as f64 - 0.1), Prefix::Kibi);
325        assert_eq!(Prefix::eng_binary((1 << 19) as f64 - 0.1), Prefix::Kibi);
326        assert_eq!(Prefix::eng_binary((1 << 29) as f64 - 0.1), Prefix::Mebi);
327        assert_eq!(Prefix::eng_binary((1 << 20) as f64 - 0.1), Prefix::Mebi);
328        assert_eq!(Prefix::eng_binary((1 << 30) as f64 - 0.1), Prefix::Gibi);
329        assert_eq!(Prefix::eng_binary((1_u64 << 39) as f64 - 0.1), Prefix::Gibi);
330        assert_eq!(Prefix::eng_binary((1_u64 << 40) as f64 - 0.1), Prefix::Tebi);
331        assert_eq!(Prefix::eng_binary((1_u64 << 49) as f64 - 0.1), Prefix::Tebi);
332        assert_eq!(Prefix::eng_binary((1_u64 << 50) as f64 - 0.1), Prefix::Tebi);
333    }
334
335    #[test]
336    fn value_prefix() -> Result<()> {
337        assert_eq!(ValuePrefix::from_str("1")?.result(), 1.0);
338        assert_eq!(ValuePrefix::from_str("1G")?.result(), 1e9);
339        assert_eq!(ValuePrefix::from_str("1e9")?.result(), 1e9);
340        assert_eq!(ValuePrefix::from_str("10e9")?.result(), 10e9);
341        assert_eq!(ValuePrefix::from_str("10Gi")?.result(), 10737418240.0);
342        assert_eq!(ValuePrefix::from_str("10M")?.result(), 1e7);
343
344        Ok(())
345    }
346}