human_units/
iec.rs

1//! IEC (International Electrotechnical Commission) units.
2//!
3//! All units start with no prefix, end with _quebi_ prefix, and use [`u64`](::core::u64) as the underlying type.
4
5use crate::imp::IEC_PREFIXES as PREFIXES;
6use crate::Buffer;
7use crate::Error;
8use paste::paste;
9
10#[cfg(feature = "iec-units")]
11mod units;
12
13#[cfg(feature = "iec-units")]
14pub use self::units::*;
15
16/// Add IEC unit parsing and formatting functions.
17///
18/// The macro adds the following trait implementations:
19/// - [`Display`](::core::fmt::Display)
20/// - [`FromStr`](::core::str::FromStr)
21#[cfg_attr(
22    all(feature = "derive", feature = "serde"),
23    doc = " - [`Serialize`](serde::Serialize) and [`Deserialize`](serde::Deserialize) when `serde` feature is enabled."
24)]
25///
26/// The macro also adds the following constants:
27/// - `Self::SYMBOL` — the symbol,
28/// - `Self::MAX_STRING_LEN` — max. length in string form.
29///
30/// Macro parameters:
31/// - `symbol` is the unit name without IEC prefix, e.g. `"Hart"`, `"bit"`.
32/// - `min_prefix` is the minimum IEC prefix, e.g. `"Ki"`, `"Mi"`, `""`. No prefix by default.
33/// - `max_prefix` is the maximum IEC prefix, e.g. `"Gi"`, `"Ti"`.
34///   By default equals the largest prefix an underlying integer type can hold.
35///
36/// # Example
37///
38#[cfg_attr(
39    feature = "derive",
40    doc = r##"```rust
41use human_units::iec::iec_unit;
42
43#[iec_unit(symbol = "nit")]
44struct Nit(pub u64);
45```"##
46)]
47#[cfg(feature = "derive")]
48pub use human_units_derive::iec_unit;
49
50/// Display IEC unit value.
51pub trait IecDisplay {
52    /// Maximum allowed length in string form.
53    const MAX_STRING_LEN: usize;
54
55    /// Display IEC unit value with the specified unit symbol.
56    fn iec_display(self, symbol: &str) -> Display<'_, Self>
57    where
58        Self: Sized;
59}
60
61/// Implements [`Display`](::core::fmt::Display) for IEC unit value.
62pub struct Display<'a, T> {
63    number: T,
64    symbol: &'a str,
65}
66
67/// Parse value from a string with IEC unit.
68pub trait IecFromStr {
69    /// Parse value that has the specified unit symbol from string.
70    fn iec_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error>
71    where
72        Self: Sized;
73}
74
75/// Format the value as a number using the largest possible IEC prefix.
76#[deprecated(
77    since = "0.5.3",
78    note = "`format_iec` method is generated by `iec_unit` macro as `const` without this trait."
79)]
80pub trait FormatIec {
81    /// Represent the value as a number using the largest possible unit prefix.
82    ///
83    /// The number has integer part in the range `0..=1023` (`0..1024*1024*1024` for `u128`) and fractional part in the range `0..=9`.
84    fn format_iec(&self) -> FormattedUnit<'static>;
85}
86
87/// Format the value as a number using the largest possible IEC prefix.
88pub trait FormatIecUnit {
89    /// Represent the value as a number using the largest possible unit prefix.
90    ///
91    /// The number has integer part in the range `0..=1023` (`0..1024*1024*1024` for `u128`) and fractional part in the range `0..=9`.
92    fn format_iec_unit(self, symbol: &str) -> FormattedUnit<'_>;
93}
94
95/// An approximate value that consists of integer and fraction parts, prefix and symbol.
96pub struct FormattedUnit<'symbol> {
97    pub(crate) prefix: &'static str,
98    pub(crate) symbol: &'symbol str,
99    pub(crate) integer: u16,
100    pub(crate) fraction: u8,
101}
102
103impl<'symbol> FormattedUnit<'symbol> {
104    /// Create new instance.
105    pub const fn new(
106        prefix: &'static str,
107        symbol: &'symbol str,
108        integer: u16,
109        fraction: u8,
110    ) -> Self {
111        Self {
112            prefix,
113            symbol,
114            integer,
115            fraction,
116        }
117    }
118
119    /// Unit prefix.
120    pub const fn prefix(&self) -> &'static str {
121        self.prefix
122    }
123
124    /// Unit symbol.
125    pub const fn symbol(&self) -> &'symbol str {
126        self.symbol
127    }
128
129    /// Integer part. Max. value is 1023.
130    pub const fn integer(&self) -> u16 {
131        self.integer
132    }
133
134    /// Fraction part. Max. value is 9.
135    pub const fn fraction(&self) -> u8 {
136        self.fraction
137    }
138}
139
140impl core::fmt::Display for FormattedUnit<'_> {
141    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
142        let mut buf = Buffer::<MAX_LEN>::new();
143        buf.write_u16(self.integer);
144        if self.fraction != 0 {
145            buf.write_byte(b'.');
146            buf.write_byte(b'0' + self.fraction);
147        }
148        buf.write_byte(b' ');
149        buf.write_str_infallible(self.prefix);
150        buf.write_str_infallible(self.symbol);
151        f.write_str(unsafe { buf.as_str() })
152    }
153}
154
155const MAX_LEN: usize = 64;
156
157#[rustfmt::skip]
158macro_rules! max_string_len {
159    (u128) => {39};
160    (u64) => {20};
161    (u32) => {10};
162    (u16) => {5};
163}
164
165macro_rules! parameterize {
166    ($((
167        $uint: ident
168        $max_prefix: ident
169        ($max_prefix_integer: expr)
170        ($max_integer: expr)
171        ($($ilog: expr)+)
172    ))+) => {
173        paste! {
174            $(
175                impl<const N: usize> Buffer<N> {
176                    #[doc(hidden)]
177                    pub fn [<write_unit_ $uint _1024>]<const MIN: usize, const MAX: usize>(
178                        &mut self,
179                        value: $uint,
180                        symbol: &str,
181                    ) {
182                        let (value, i) = $crate::imp::[<unitify_ $uint _1024>]::<MIN, MAX>(value);
183                        self.[<write_ $uint>](value);
184                        self.write_byte(b' ');
185                        self.write_str_infallible(PREFIXES[i]);
186                        self.write_str_infallible(symbol);
187                    }
188                }
189
190                impl IecDisplay for $uint {
191                    const MAX_STRING_LEN: usize = 64;
192
193                    fn iec_display(self, symbol: &str) -> Display<'_, Self> {
194                        Display { number: self, symbol }
195                    }
196                }
197
198                impl core::fmt::Display for Display<'_, $uint> {
199                    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
200                        debug_assert!(
201                            self.symbol.len() <= <$uint as IecDisplay>::MAX_STRING_LEN - max_string_len!($uint),
202                            "The symbol is too long: {} > {}",
203                            self.symbol.len(),
204                            <$uint as IecDisplay>::MAX_STRING_LEN - max_string_len!($uint),
205                        );
206                        let mut buffer: Buffer<{ <$uint as IecDisplay>::MAX_STRING_LEN }> = Buffer::new();
207                        buffer.[<write_unit_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(self.number, self.symbol);
208                        f.write_str(unsafe { buffer.as_str() })
209                    }
210                }
211
212                impl IecFromStr for $uint {
213                    fn iec_unit_from_str(string: &str, symbol: &str) -> Result<Self, Error> {
214                        $crate::imp::[<$uint _unit_from_str>]::<1024>(
215                            string,
216                            symbol,
217                            &PREFIXES[..=Prefix::$max_prefix as usize]
218                        )
219                    }
220                }
221
222                impl FormatIecUnit for $uint {
223                    fn format_iec_unit(self, symbol: &str) -> FormattedUnit<'_> {
224                        #![allow(clippy::int_plus_one)]
225                        $(
226                            {
227                                const SCALE: $uint = (1024 as $uint).pow($ilog);
228                                if self >= SCALE {
229                                    let integer = self / SCALE;
230                                    let mut fraction = self % SCALE;
231                                    if fraction != 0 {
232                                        // Compute the first digit of the fractional part,
233                                        // i.e. `fraction = fraction * 10 / SCALE` without an
234                                        // overflow.
235                                        fraction = match fraction.checked_mul(5) {
236                                            Some(numerator) => numerator / (SCALE / 2),
237                                            None => {
238                                                debug_assert_eq!(0, SCALE % 16);
239                                                (fraction / 8) * 5 / (SCALE / 16)
240                                            }
241                                        };
242                                    }
243                                    debug_assert!(integer <= $max_integer, "integer = {integer}");
244                                    debug_assert!(fraction <= 9, "fraction = {fraction}");
245                                    return FormattedUnit {
246                                        integer: integer as u16,
247                                        fraction: fraction as u8,
248                                        prefix: PREFIXES[$ilog],
249                                        symbol,
250                                    };
251                                }
252                            }
253                        )+
254                        let integer = self;
255                        debug_assert!(integer <= $max_integer, "integer = {integer}");
256                        FormattedUnit {
257                            integer: integer as u16,
258                            fraction: 0,
259                            prefix: PREFIXES[0],
260                            symbol,
261                        }
262                    }
263                }
264            )+
265
266            #[cfg(test)]
267            mod unitify_tests {
268                use super::*;
269
270                use arbtest::arbtest;
271                use alloc::format;
272                use alloc::string::String;
273                use alloc::string::ToString;
274
275                extern crate alloc;
276
277                $(
278                    #[test]
279                    fn [<test_unitify_ $uint>]() {
280                        arbtest(|u| {
281                            let number: $uint = u.arbitrary()?;
282                            let (x, prefix) = $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(number);
283                            let p = prefix as u32 - Prefix::None as u32;
284                            let multiplier = (1024 as $uint).pow(p);
285                            assert_eq!(number, x * multiplier, "x = {x}, multiplier = {multiplier}");
286                            Ok(())
287                        });
288                    }
289
290                    #[test]
291                    fn [<test_max_string_len_ $uint>]() {
292                        let string = format!("{}", $uint::MAX);
293                        assert_eq!(max_string_len!($uint), string.len());
294                    }
295
296                    #[test]
297                    fn [<test_buffer_io_ $uint>]() {
298                        arbtest(|u| {
299                            let number: $uint = u.arbitrary()?;
300                            let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
301                            let mut buffer = Buffer::<MAX_LEN>::new();
302                            buffer.[<write_unit_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(number, &symbol);
303                            let actual = $uint::iec_unit_from_str(unsafe { buffer.as_str() }, &symbol)
304                                .unwrap_or_else(|_| panic!("String = {:?}, number = {number}, symbol = {symbol:?}", unsafe { buffer.as_str() }));
305                            assert_eq!(number, actual);
306                            Ok(())
307                        });
308                    }
309
310                    #[test]
311                    fn [<test_string_io_ $uint>]() {
312                        arbtest(|u| {
313                            let number: $uint = u.arbitrary()?;
314                            let symbol: String = char::from_u32(u.int_in_range(b'a'..=b'z')? as u32).unwrap().to_string();
315                            let string = format!("{}", number.iec_display(&symbol));
316                            let actual = $uint::iec_unit_from_str(&string, &symbol).unwrap();
317                            assert_eq!(number, actual);
318                            Ok(())
319                        });
320                    }
321
322                    #[test]
323                    fn [<check_prefix_ $uint>]() {
324                        const MAX_POW_OF_1024: $uint = (1024 as $uint).pow($uint::MAX.ilog(1024));
325                        assert_eq!(None, MAX_POW_OF_1024.checked_mul(1024));
326                        assert_eq!(
327                            (1, Prefix::Kibi as usize),
328                            $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(1024)
329                        );
330                        assert_eq!(
331                            ($max_prefix_integer, Prefix::$max_prefix as usize),
332                            $crate::imp::[<unitify_ $uint _1024>]::<0, { Prefix::$max_prefix as usize }>(MAX_POW_OF_1024),
333                            "MAX_POW_OF_1024 = {MAX_POW_OF_1024}"
334                        );
335                    }
336
337                    #[test]
338                    fn [<test_format_unit_ $uint>]() {
339                        arbtest(|u| {
340                            let exact: $uint = u.arbitrary()?;
341                            let FormattedUnit { integer, fraction,  prefix, .. } = exact.format_iec_unit("");
342                            let i = PREFIXES.iter().position(|p| p == &prefix).unwrap();
343                            let factor = (1024 as $uint).pow(i as u32);
344                            let inexact = (integer as $uint) * factor + (fraction as $uint) * (factor / 10);
345                            assert!(
346                                exact >= inexact && (exact - inexact) < factor.saturating_mul($max_integer),
347                                "Exact   = {exact},\ninexact = {inexact},\nexact - inexact = {}, factor = {factor},\ninteger = {integer}, fraction = {fraction}, prefix = {prefix:?}",
348                                exact - inexact,
349                            );
350                            Ok(())
351                        });
352                    }
353                )+
354            }
355        }
356    };
357}
358
359parameterize! {
360    (u128 Quebi (1024*1024) (1024*1024*1024 - 1) (10 9 8 7 6 5 4 3 2 1))
361    (u64 Exbi (1) (1023) (6 5 4 3 2 1))
362    (u32 Gibi (1) (1023) (3 2 1))
363    (u16 Kibi (1) (1023) (1))
364}
365
366/// IEC unit prefix.
367#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
368#[cfg_attr(all(test, feature = "std"), derive(arbitrary::Arbitrary))]
369#[repr(u8)]
370pub enum Prefix {
371    /// "", 1024^0.
372    #[default]
373    None = 0,
374    /// "Ki", 1024^1.
375    Kibi = 1,
376    /// "Mi", 1024^2.
377    Mebi = 2,
378    /// "Gi", 1024^3.
379    Gibi = 3,
380    /// "Ti", 1024^4.
381    Tebi = 4,
382    /// "Pi", 1024^5.
383    Pebi = 5,
384    /// "Ei", 1024^6.
385    Exbi = 6,
386    /// "Zi", 1024^7.
387    Zebi = 7,
388    /// "Yi", 1024^8.
389    Yobi = 8,
390    /// "Ri", 1024^9.
391    Robi = 9,
392    /// "Qi", 1024^10.
393    Quebi = 10,
394}
395
396impl Prefix {
397    /// Get unit prefix as a string.
398    pub const fn as_str(self) -> &'static str {
399        crate::imp::IEC_PREFIXES[self as usize]
400    }
401
402    /// All prefixes.
403    pub const ALL: [Self; 11] = {
404        use Prefix::*;
405        [
406            None, Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi, Robi, Quebi,
407        ]
408    };
409}
410
411impl core::fmt::Display for Prefix {
412    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
413        f.write_str(self.as_str())
414    }
415}
416
417#[cfg(all(test, feature = "std"))]
418mod tests {
419    use super::*;
420
421    use alloc::format;
422    use arbitrary::Arbitrary;
423    use arbitrary::Unstructured;
424    use arbtest::arbtest;
425
426    extern crate alloc;
427
428    #[test]
429    fn test_io() {
430        arbtest(|u| {
431            let expected: FormattedUnit = u.arbitrary()?;
432            let string = expected.to_string();
433            let mut words = string.splitn(2, ' ');
434            let number_str = words.next().unwrap();
435            let unit = words.next().unwrap().to_string();
436            let mut words = number_str.splitn(2, '.');
437            let integer: u16 = words.next().unwrap().parse().unwrap();
438            let fraction: u8 = match words.next() {
439                Some(word) => word.parse().unwrap(),
440                None => 0,
441            };
442            assert_eq!(expected.integer, integer, "string = {string:?}");
443            assert_eq!(expected.fraction, fraction);
444            assert_eq!(
445                format!("{}{}", expected.prefix, expected.symbol),
446                unit,
447                "expected = `{}`",
448                expected
449            );
450            Ok(())
451        });
452    }
453
454    impl<'a> Arbitrary<'a> for FormattedUnit<'static> {
455        fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
456            Ok(Self {
457                prefix: *u.choose(&PREFIXES[..])?,
458                symbol: "",
459                integer: u.int_in_range(0..=999)?,
460                fraction: u.int_in_range(0..=9)?,
461            })
462        }
463    }
464}