Skip to main content

human_units/
imp.rs

1use crate::Buffer;
2use crate::Error;
3use pastey::paste;
4
5#[cfg(feature = "serde")]
6pub use serde;
7
8macro_rules! unicode {
9    ($utf8: literal, $ascii: literal) => {
10        if cfg!(feature = "unicode") {
11            $utf8
12        } else {
13            $ascii
14        }
15    };
16}
17
18const MICRO: &str = if true { "μ" } else { "u" }unicode!("μ", "u");
19
20#[doc(hidden)]
21pub const SI_PREFIXES: [&str; 21] = [
22    "q", "r", "y", "z", "a", "f", "p", "n", MICRO, "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y",
23    "R", "Q",
24];
25
26#[doc(hidden)]
27pub const IEC_PREFIXES: [&str; 11] = [
28    "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi", "Ri", "Qi",
29];
30
31#[inline]
32fn pre_parse_str<'a>(string: &'a str, symbol: &str) -> Result<(&'a str, &'a str), Error> {
33    let string = string.trim();
34    let i = string.rfind(char::is_numeric).ok_or(Error)?;
35    let value = &string[..=i];
36    let unit = string[i + 1..].trim_start();
37    if !unit.ends_with(symbol) {
38        return Err(Error);
39    }
40    let prefix = &unit[..unit.len() - symbol.len()];
41    Ok((value, prefix))
42}
43
44/// An approximate value that consists of integer and fraction parts, prefix and symbol.
45pub struct FormattedUnit<'symbol, T, const N: usize> {
46    prefix: &'static str,
47    symbol: &'symbol str,
48    integer: T,
49    fraction: u8,
50}
51
52impl<'symbol, T, const N: usize> FormattedUnit<'symbol, T, N> {
53    /// Create new instance.
54    #[doc(hidden)]
55    pub const fn new(prefix: &'static str, symbol: &'symbol str, integer: T, fraction: u8) -> Self {
56        Self {
57            prefix,
58            symbol,
59            integer,
60            fraction,
61        }
62    }
63
64    /// Unit prefix.
65    pub const fn prefix(&self) -> &'static str {
66        self.prefix
67    }
68
69    /// Unit symbol.
70    pub const fn symbol(&self) -> &'symbol str {
71        self.symbol
72    }
73
74    /// Integer part.
75    pub const fn integer(&self) -> T
76    where
77        T: Copy,
78    {
79        self.integer
80    }
81
82    /// Fraction part. Max. value is 9.
83    pub const fn fraction(&self) -> u8 {
84        self.fraction
85    }
86}
87
88macro_rules! parameterize {
89    ($($uint: ident)+) => {
90        paste! {
91            $(
92                #[inline]
93                #[doc(hidden)]
94                pub fn [<unitify_ $uint _1000>]<const MIN: usize, const MAX: usize>(
95                    mut value: $uint,
96                ) -> ($uint, usize) {
97                    if value == 0 {
98                        return (0, MIN);
99                    }
100                    for power_of_1000 in MIN..MAX {
101                        if value % 1000 != 0 {
102                            return (value, power_of_1000);
103                        }
104                        value /= 1000;
105                    }
106                    (value, MAX)
107                }
108
109                #[inline]
110                #[doc(hidden)]
111                pub fn [<$uint _unit_from_str>]<const SCALE: $uint>(
112                    string: &str,
113                    symbol: &str,
114                    prefixes: &[&str],
115                ) -> Result<$uint, Error> {
116                    let (value_str, prefix_str) = pre_parse_str(string, symbol)?;
117                    let power = prefixes
118                        .iter()
119                        .position(|prefix| *prefix == prefix_str)
120                        .ok_or(Error)?;
121                    let value: $uint = value_str.parse().map_err(|_| Error)?;
122                    let factor = SCALE.pow(power as u32);
123                    value.checked_mul(factor).ok_or(Error)
124                }
125
126                #[inline]
127                #[doc(hidden)]
128                pub fn [<unitify_ $uint _1024>]<const MIN: usize, const MAX: usize>(
129                    value: $uint,
130                ) -> ($uint, usize) {
131                    if value == 0 {
132                        return (0, MIN);
133                    }
134                    for p in (0..=MAX - MIN).rev() {
135                        let scale = (1024 as $uint).pow(p as u32);
136                        if value % scale == 0 {
137                            return (value >> (10 * p), p + MIN);
138                        }
139                    }
140                    (value, MIN)
141                }
142
143                impl<const N: usize> core::fmt::Display for FormattedUnit<'_, $uint, N> {
144                    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
145                        let mut buf = Buffer::<{ N }>::new();
146                        buf.[<write_ $uint>](self.integer);
147                        if self.fraction != 0 {
148                            buf.write_byte(b'.');
149                            buf.write_byte(b'0' + self.fraction);
150                        }
151                        buf.write_byte(b' ');
152                        buf.write_str_infallible(self.prefix);
153                        buf.write_str_infallible(self.symbol);
154                        f.write_str(unsafe { buf.as_str() })
155                    }
156                }
157            )+
158        }
159    };
160}
161
162#[inline]
#[doc(hidden)]
pub fn unitify_u16_1000<const MIN : usize, const MAX : usize>(mut value: u16)
    -> (u16, usize) {
    if value == 0 { return (0, MIN); }
    for power_of_1000 in MIN..MAX {
        if value % 1000 != 0 { return (value, power_of_1000); }
        value /= 1000;
    }
    (value, MAX)
}
#[inline]
#[doc(hidden)]
pub fn u16_unit_from_str<const SCALE :
    u16>(string: &str, symbol: &str, prefixes: &[&str])
    -> Result<u16, Error> {
    let (value_str, prefix_str) = pre_parse_str(string, symbol)?;
    let power =
        prefixes.iter().position(|prefix|
                        *prefix == prefix_str).ok_or(Error)?;
    let value: u16 = value_str.parse().map_err(|_| Error)?;
    let factor = SCALE.pow(power as u32);
    value.checked_mul(factor).ok_or(Error)
}
#[inline]
#[doc(hidden)]
pub fn unitify_u16_1024<const MIN : usize, const MAX : usize>(value: u16)
    -> (u16, usize) {
    if value == 0 { return (0, MIN); }
    for p in (0..=MAX - MIN).rev() {
        let scale = (1024 as u16).pow(p as u32);
        if value % scale == 0 { return (value >> (10 * p), p + MIN); }
    }
    (value, MIN)
}
impl<const N : usize> core::fmt::Display for FormattedUnit<'_, u16, N> {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        let mut buf = Buffer::<{ N }>::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() })
    }
}parameterize! { u128 u64 u32 u16 }