human_units/si/
core.rs

1use paste::paste;
2
3use crate::imp::SI_PREFIXES as PREFIXES;
4use crate::Buffer;
5use crate::Error;
6
7/// Parse value from a string with SI unit.
8pub trait SiFromStr {
9    /// Parse value that has the specified unit symbol from string.
10    fn si_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error>
11    where
12        Self: Sized;
13}
14
15/// Display SI unit value.
16pub trait SiDisplay {
17    /// Maximum allowed length in string form.
18    const MAX_STRING_LEN: usize;
19
20    /// Display SI unit value with the specified unit symbol.
21    fn si_display(self, symbol: &str) -> Display<'_, Self>
22    where
23        Self: Sized;
24}
25
26/// Implements [`Display`](::core::fmt::Display) for SI unit value.
27pub struct Display<'a, T> {
28    number: T,
29    symbol: &'a str,
30}
31
32/// Format the value as a number using the largest possible SI prefix.
33#[deprecated(
34    since = "0.5.3",
35    note = "`format_si` method is generated by `si_unit` macro as `const` without this trait."
36)]
37pub trait FormatSi {
38    /// Represent the value as a number using the largest possible unit prefix.
39    ///
40    /// The number has integer part in the range `0..=999` and fractional part in the range `0..=9`.
41    fn format_si(&self) -> FormattedUnit<'static>;
42}
43
44/// Format the value as a number using the largest possible SI prefix.
45pub trait FormatSiUnit {
46    /// Represent the value as a number using the largest possible unit prefix.
47    ///
48    /// The number has integer part in the range `0..=999` and fractional part in the range `0..=9`.
49    fn format_si_unit(self, symbol: &str) -> FormattedUnit<'_>;
50}
51
52/// An approximate value that consists of integer and fraction parts, prefix and symbol.
53pub struct FormattedUnit<'symbol> {
54    prefix: &'static str,
55    symbol: &'symbol str,
56    integer: u16,
57    fraction: u8,
58}
59
60impl<'symbol> FormattedUnit<'symbol> {
61    /// Create new instance.
62    pub const fn new(
63        prefix: &'static str,
64        symbol: &'symbol str,
65        integer: u16,
66        fraction: u8,
67    ) -> Self {
68        Self {
69            prefix,
70            symbol,
71            integer,
72            fraction,
73        }
74    }
75
76    /// Unit prefix.
77    pub const fn prefix(&self) -> &'static str {
78        self.prefix
79    }
80
81    /// Unit symbol.
82    pub const fn symbol(&self) -> &'symbol str {
83        self.symbol
84    }
85
86    /// Integer part. Max. value is 999.
87    pub const fn integer(&self) -> u16 {
88        self.integer
89    }
90
91    /// Fraction part. Max. value is 9.
92    pub const fn fraction(&self) -> u8 {
93        self.fraction
94    }
95}
96
97impl core::fmt::Display for FormattedUnit<'_> {
98    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
99        let mut buf = Buffer::<MAX_LEN>::new();
100        buf.write_u16(self.integer);
101        if self.fraction != 0 {
102            buf.write_byte(b'.');
103            buf.write_byte(b'0' + self.fraction);
104        }
105        buf.write_byte(b' ');
106        buf.write_str_infallible(self.prefix);
107        buf.write_str_infallible(self.symbol);
108        f.write_str(unsafe { buf.as_str() })
109    }
110}
111
112const MAX_LEN: usize = 64;
113
114#[rustfmt::skip]
115macro_rules! max_string_len {
116    (u128) => {39};
117    (u64) => {20};
118    (u32) => {10};
119    (u16) => {5};
120}
121
122macro_rules! parameterize {
123    ($(($uint: ident
124        $min_prefix: ident
125        $max_prefix: ident
126        ($($ilog: expr)+)))+) => {
127        paste! {
128            $(
129                impl<const N: usize> Buffer<N> {
130                    #[doc(hidden)]
131                    pub fn [<write_unit_ $uint _1000>]<const MIN: usize, const MAX: usize>(
132                        &mut self,
133                        value: $uint,
134                        symbol: &str,
135                    ) {
136                        let (value, i) = $crate::imp::[<unitify_ $uint _1000>]::<MIN, MAX>(value);
137                        self.[<write_ $uint>](value);
138                        self.write_byte(b' ');
139                        self.write_str_infallible(PREFIXES[i]);
140                        self.write_str_infallible(symbol);
141                    }
142                }
143
144                impl SiDisplay for $uint {
145                    const MAX_STRING_LEN: usize = 64;
146
147                    fn si_display(self, symbol: &str) -> Display<'_, Self> {
148                        Display { number: self, symbol }
149                    }
150                }
151
152                impl core::fmt::Display for Display<'_, $uint> {
153                    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
154                        debug_assert!(
155                            self.symbol.len() <= <$uint as SiDisplay>::MAX_STRING_LEN - max_string_len!($uint),
156                            "The symbol is too long: {} > {}",
157                            self.symbol.len(),
158                            <$uint as SiDisplay>::MAX_STRING_LEN - max_string_len!($uint),
159                        );
160                        let mut buffer: Buffer<{ <$uint as SiDisplay>::MAX_STRING_LEN }> = Buffer::new();
161                        buffer.[<write_unit_ $uint _1000>]::<
162                            { Prefix::$min_prefix as usize},
163                            { Prefix::$max_prefix as usize}
164                        >(self.number, self.symbol);
165                        f.write_str(unsafe { buffer.as_str() })
166                    }
167                }
168
169                impl SiFromStr for $uint {
170                    fn si_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error> {
171                        $crate::imp::[<$uint _unit_from_str>]::<{ 1000 as $uint }>(
172                            string,
173                            symbol,
174                            &PREFIXES[Prefix::$min_prefix as usize..=Prefix::$max_prefix as usize]
175                        )
176                    }
177                }
178
179                impl FormatSiUnit for $uint {
180                    fn format_si_unit(self, symbol: &str) -> FormattedUnit<'_> {
181                        $(
182                            {
183                                const SCALE: $uint = (1000 as $uint).pow($ilog);
184                                if self >= SCALE {
185                                    let integer = self / SCALE;
186                                    let mut fraction = self % SCALE;
187                                    if fraction != 0 {
188                                        // Compute the first digit of the fractional part.
189                                        fraction /= (SCALE / 10);
190                                    }
191                                    debug_assert!(integer <= 999, "integer = {integer}");
192                                    debug_assert!(fraction <= 9, "fraction = {fraction}");
193                                    return FormattedUnit {
194                                        integer: integer as u16,
195                                        fraction: fraction as u8,
196                                        prefix: PREFIXES[Prefix::$min_prefix as usize + $ilog],
197                                        symbol,
198                                    };
199                                }
200                            }
201                        )+
202                        let integer = self;
203                        debug_assert!(integer <= 999, "integer = {integer}");
204                        FormattedUnit {
205                            integer: integer as u16,
206                            fraction: 0,
207                            prefix: PREFIXES[Prefix::$min_prefix as usize],
208                            symbol,
209                        }
210                    }
211                }
212            )+
213
214            #[cfg(test)]
215            mod unitify_tests {
216                use super::*;
217
218                use arbtest::arbtest;
219                use alloc::format;
220                use alloc::string::String;
221                use alloc::string::ToString;
222
223                extern crate alloc;
224
225
226                $(
227                    #[test]
228                    fn [<test_unitify_ $uint>]() {
229                        arbtest(|u| {
230                            let number: $uint = u.arbitrary()?;
231                            let (x, prefix) = $crate::imp::[<unitify_ $uint _1000>]::<
232                                { Prefix::$min_prefix as usize},
233                                { Prefix::$max_prefix as usize}
234                            >(number);
235                            let p = prefix as u32 - Prefix::$min_prefix as u32;
236                            assert_eq!(number, x * (1000 as $uint).pow(p));
237                            Ok(())
238                        });
239                    }
240
241                    #[test]
242                    fn [<test_max_string_len_ $uint>]() {
243                        let string = format!("{}", $uint::MAX);
244                        assert_eq!(max_string_len!($uint), string.len());
245                    }
246
247                    #[test]
248                    fn [<test_buffer_io_ $uint>]() {
249                        arbtest(|u| {
250                            let number: $uint = u.arbitrary()?;
251                            let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
252                            let mut buffer = Buffer::<MAX_LEN>::new();
253                            buffer.[<write_unit_ $uint _1000>]::<
254                                { Prefix::$min_prefix as usize},
255                                { Prefix::$max_prefix as usize}
256                            >(number, &symbol);
257                            let actual = $uint::si_unit_from_str(unsafe { buffer.as_str() }, &symbol).unwrap();
258                            assert_eq!(number, actual);
259                            Ok(())
260                        });
261                    }
262
263                    #[test]
264                    fn [<test_string_io_ $uint>]() {
265                        arbtest(|u| {
266                            let number: $uint = u.arbitrary()?;
267                            let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
268                            let string = format!("{}", number.si_display(&symbol));
269                            let actual = $uint::si_unit_from_str(&string, &symbol).unwrap();
270                            assert_eq!(number, actual);
271                            Ok(())
272                        });
273                    }
274
275                    #[test]
276                    fn [<check_max_prefix_ $uint>]() {
277                        const MAX_POW_OF_1000: $uint = (1000 as $uint).pow($uint::MAX.ilog(1000));
278                        assert_eq!(None, MAX_POW_OF_1000.checked_mul(1000));
279                        assert_eq!(
280                            (1, Prefix::Micro as usize),
281                            $crate::imp::[<unitify_ $uint _1000>]::<
282                                { Prefix::$min_prefix as usize},
283                                { Prefix::$max_prefix as usize}
284                            >(1000)
285                        );
286                        assert_eq!(
287                            (1, Prefix::$max_prefix as usize),
288                            $crate::imp::[<unitify_ $uint _1000>]::<
289                                { Prefix::$min_prefix as usize},
290                                { Prefix::$max_prefix as usize}
291                            >(MAX_POW_OF_1000)
292                        );
293                    }
294
295                    #[test]
296                    fn [<test_format_unit_ $uint>]() {
297                        arbtest(|u| {
298                            let exact: $uint = u.arbitrary()?;
299                            let FormattedUnit { integer, fraction,  prefix, .. } = exact.format_si_unit("");
300                            let i = PREFIXES.iter().position(|p| p == &prefix).unwrap() - Prefix::$min_prefix as usize;
301                            let factor = (1000 as $uint).pow(i as u32);
302                            let inexact = (integer as $uint) * factor + (fraction as $uint) * (factor / 10);
303                            assert!(
304                                exact >= inexact && (exact - inexact) < factor,
305                                "Exact = {exact}, inexact = {inexact}",
306                            );
307                            Ok(())
308                        });
309                    }
310                )+
311            }
312        }
313    };
314}
315
316parameterize! {
317    (u128 Nano Ronna (12 11 10 9 8 7 6 5 4 3 2 1))
318    (u64 Nano Giga (6 5 4 3 2 1))
319    (u32 Nano None (3 2 1))
320    (u16 Nano Micro (1))
321}
322
323/// SI unit prefix.
324#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
325#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
326#[repr(u8)]
327pub enum Prefix {
328    /// "q".
329    Quecto = 0,
330    /// "r".
331    Ronto = 1,
332    /// "y".
333    Yocto = 2,
334    /// "z".
335    Zepto = 3,
336    /// "a".
337    Atto = 4,
338    /// "f".
339    Femto = 5,
340    /// "p".
341    Pico = 6,
342    /// "n".
343    Nano = 7,
344    /// "μ".
345    Micro = 8,
346    /// "m".
347    Milli = 9,
348    /// "".
349    #[default]
350    None = 10,
351    /// "k".
352    Kilo = 11,
353    /// "M".
354    Mega = 12,
355    /// "G".
356    Giga = 13,
357    /// "T".
358    Tera = 14,
359    /// "P".
360    Peta = 15,
361    /// "E".
362    Exa = 16,
363    /// "Z".
364    Zetta = 17,
365    /// "Y".
366    Yotta = 18,
367    /// "R".
368    Ronna = 19,
369    /// "Q".
370    Quetta = 20,
371}
372
373impl Prefix {
374    /// Get unit prefix as a string.
375    pub const fn as_str(self) -> &'static str {
376        crate::imp::SI_PREFIXES[self as usize]
377    }
378
379    /// All prefixes.
380    pub const ALL: [Self; 21] = {
381        use Prefix::*;
382        [
383            Quecto, Ronto, Yocto, Zepto, Atto, Femto, Pico, Nano, Micro, Milli, None, Kilo, Mega,
384            Giga, Tera, Peta, Exa, Zetta, Yotta, Ronna, Quetta,
385        ]
386    };
387}
388
389impl core::fmt::Display for Prefix {
390    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
391        f.write_str(self.as_str())
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    #[test]
398    fn test_min_prefix_len() {
399        assert_ne!(0, u128::MAX % 1000);
400        assert_ne!(0, u64::MAX % 1000);
401        assert_ne!(0, u32::MAX % 1000);
402        assert_ne!(0, u16::MAX % 1000);
403    }
404}