byte_calc/
lib.rs

1#![no_std]
2
3//! Helper crate to work with _bit, byte, and block sizes_.
4//!
5//! This crate provides three dedicated types, [`NumBits`], [`NumBytes`], and
6//! [`NumBlocks`], to represent numbers of bits, bytes, and blocks, respectively. It
7//! implements the usual traits for numeric operators such that calculations on them can
8//! be carried out with succinct syntax. All operations will panic on errors, such as
9//! over- or underflows. This is an intentional design decision to prevent subtly
10//! incorrect results and behavior. In addition, this crate provides formatting and
11//! parsing for byte sizes.
12//!
13//! This crate is `no_std`-compatible.
14//!
15//!
16//! ## Conversions
17//!
18//! The provided types support convenient conversions to each other:
19//!
20//! ```rust
21//! # use byte_calc::{NumBits, NumBytes, NumBlocks};
22//! assert_eq!(NumBits::new(15).to_bytes_ceil(), NumBytes::bytes(2));
23//! assert_eq!(NumBits::new(15).to_bytes_floor(), NumBytes::bytes(1));
24//!
25//! assert_eq!(NumBytes::bytes(2).to_bits(), NumBits::new(16));
26//! assert_eq!(NumBits::from(NumBytes::bytes(2)), NumBits::new(16));
27//!
28//! const BLOCK_SIZE: NumBytes = NumBytes::kibibytes(4);
29//! assert_eq!(NumBytes::bytes(8193).to_blocks_ceil(BLOCK_SIZE), NumBlocks::new(3));
30//! assert_eq!(NumBytes::bytes(8193).to_blocks_floor(BLOCK_SIZE), NumBlocks::new(2));
31//!
32//! assert_eq!(NumBlocks::new(2).to_bytes(BLOCK_SIZE), NumBytes::kibibytes(8));
33//! ```
34//!
35//!
36//! ## Calculations
37//!
38//! Calculations can be performed with the types as well as with [`u64`] integers:
39//!
40//! ```rust
41//! # use byte_calc::{NumBits, NumBytes, NumBlocks};
42//! assert_eq!(NumBytes::bytes(10) / NumBytes::bytes(2), NumBytes::bytes(5));
43//! assert_eq!(NumBytes::bytes(10) / 2, NumBytes::bytes(5));
44//!
45//! assert_eq!(NumBits::new(5) + NumBits::new(8), NumBits::new(13));
46//! assert_eq!(NumBits::new(5) * 2, NumBits::new(10));
47//!
48//! assert_eq!(NumBlocks::new(10) + 2, NumBlocks::new(12));
49//!
50//! assert_eq!(NumBits::new(2) + NumBytes::bytes(1), NumBits::new(10));
51//! ```
52//!
53//!
54//! ## Comparisons
55//!
56//! Comparisons are supported on the types as well as with [`u64`] integers:
57//!
58//! ```rust
59//! # use byte_calc::{NumBits, NumBytes, NumBlocks};
60//! assert!(NumBytes::bytes(10) < 20);
61//! assert!(NumBytes::bytes(10) != 0);
62//!
63//! assert_eq!(NumBits::new(5), 5);
64//!
65//! assert_eq!(NumBits::new(16), NumBytes::new(2));
66//! assert!(NumBits::new(15) < NumBytes::new(2));
67//! ```
68//!
69//!
70//! ## Formatting
71//!
72//! Formatting of byte sizes maximizes the unit while minimizing the integer part towards
73//! one. For example:
74//!
75//! ```rust
76//! # use byte_calc::NumBytes;
77//! assert_eq!(NumBytes::mebibytes(128).to_string(), "128MiB");
78//! assert_eq!(NumBytes::gigabytes(1).to_string(), "1GB");
79//! assert_eq!(NumBytes::bytes(1023).to_string(), "1.023kB");
80//! assert_eq!(NumBytes::bytes(1000).to_string(), "1kB");
81//! assert_eq!(NumBytes::bytes(999).to_string(), "999B");
82//! assert_eq!(NumBytes::bytes(2560).to_string(), "2.5KiB");
83//! ```
84//!
85//! The usual formatting syntax can be used to limit the precision:
86//!
87//! ```rust
88//! # use byte_calc::NumBytes;
89//! assert_eq!(format!("{:.2}", NumBytes::terabytes(2)), "1.81TiB");
90//! ```
91//!
92//!
93//! ## Parsing
94//!
95//! Byte sizes must follow the following syntax:
96//!
97//! ```plain
98//! ⟨byte-size⟩  ::=  ⟨int⟩ [ '.' ⟨int⟩ ] [ ' '* ⟨unit⟩ ]
99//! ⟨int⟩  ::=  [0-9_]+
100//! ⟨unit⟩ ::=  'B' | 'K' … 'E' | 'kB' … 'EB' | 'KiB' … 'EiB' (case-insensitive)
101//! ```
102//!
103//! The units (`K` ... `E`) are interpreted as binary units (`KiB` ...  `EiB`). Generally,
104//! unit parsing is case-insensitive.
105//!
106//! ```rust
107//! # use core::str::FromStr;
108//! # use byte_calc::NumBytes;
109//! assert_eq!(NumBytes::from_str("5").unwrap(), NumBytes::bytes(5));
110//! assert_eq!(NumBytes::from_str("2.5KiB").unwrap(), NumBytes::bytes(2560));
111//! assert_eq!(NumBytes::from_str("2_000kB").unwrap(), NumBytes::megabytes(2));
112//! ```
113//!
114//! Parsing also works in `const` contexts using [`NumBytes::parse_str`] or
115//! [`NumBytes::parse_ascii`]:
116//!
117//! ```rust
118//! # use core::str::FromStr;
119//! # use byte_calc::NumBytes;
120//! const BLOCK_SIZE: NumBytes = match NumBytes::parse_str("4KiB") {
121//!     Ok(value) => value,
122//!     Err(_) => panic!("invalid format"),
123//! };
124//! ```
125//!
126//! The parser has been extensively fuzz-tested, ensuring that no input leads to panics.
127//!
128//!
129//! ## Serialization and Deserialization
130//!
131//! By enabling the `serde` feature, [`NumBits`], [`NumBytes`], and [`NumBlocks`] can be
132//! serialized and deserialized. All tree types always serialize as [`u64`] integers.
133//! Deserialization of [`NumBytes`] is also supported from strings.
134
135pub mod errors;
136
137use crate::errors::{ByteUnitParseError, NumBytesParseError};
138
139/// Auxiliary macro as a replacement for `?` in `const` contexts.
140macro_rules! const_try {
141    ($expr:expr) => {
142        match $expr {
143            Ok(value) => value,
144            Err(error) => return Err(error),
145        }
146    };
147}
148
149/// Auxiliary macro for the definition of [`u64`] wrapper types.
150macro_rules! define_types {
151    ($($name:ident, $type:literal;)*) => {
152        $(
153            #[doc = concat!("Represents a number of _", $type, "_.")]
154            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
155            pub struct $name {
156                #[doc = concat!("Raw number of ", $type, ".")]
157                pub raw: u64,
158            }
159
160            impl $name {
161                #[doc = concat!(
162                    "Construct [`", stringify!($name),
163                    "`] from the provided raw number of ", $type, "."
164                )]
165                pub const fn new(raw: u64) -> Self {
166                    Self { raw }
167                }
168            }
169
170            impl core::ops::Add for $name {
171                type Output = $name;
172
173                fn add(self, rhs: Self) -> Self::Output {
174                    self + rhs.raw
175                }
176            }
177
178            impl core::ops::Add<u64> for $name {
179                type Output = $name;
180
181                fn add(self, rhs: u64) -> Self::Output {
182                    Self::new(self.raw.checked_add(rhs).expect(concat!("overflow adding ", $type)))
183                }
184            }
185
186            impl core::ops::AddAssign for $name {
187                fn add_assign(&mut self, rhs: Self) {
188                    *self = *self + rhs;
189                }
190            }
191
192            impl core::ops::AddAssign<u64> for $name {
193                fn add_assign(&mut self, rhs: u64) {
194                    *self = *self + rhs;
195                }
196            }
197
198            impl core::ops::Sub for $name {
199                type Output = $name;
200
201                fn sub(self, rhs: Self) -> Self::Output {
202                    self - rhs.raw
203                }
204            }
205
206            impl core::ops::Sub<u64> for $name {
207                type Output = $name;
208
209                fn sub(self, rhs: u64) -> Self::Output {
210                    Self::new(self.raw.checked_sub(rhs).expect(concat!("underflow subtracting ", $type)))
211                }
212            }
213
214            impl core::ops::SubAssign for $name {
215                fn sub_assign(&mut self, rhs: Self) {
216                    *self = *self - rhs;
217                }
218            }
219
220            impl core::ops::SubAssign<u64> for $name {
221                fn sub_assign(&mut self, rhs: u64) {
222                    *self = *self - rhs;
223                }
224            }
225
226            impl core::ops::Mul for $name {
227                type Output = $name;
228
229                fn mul(self, rhs: Self) -> Self::Output {
230                    self * rhs.raw
231                }
232            }
233
234            impl core::ops::Mul<u64> for $name {
235                type Output = $name;
236
237                fn mul(self, rhs: u64) -> Self::Output {
238                    Self::new(self.raw.checked_mul(rhs).expect(concat!("overflow multiplying ", $type)))
239                }
240            }
241
242            impl core::ops::MulAssign for $name {
243                fn mul_assign(&mut self, rhs: Self) {
244                    *self = *self * rhs;
245                }
246            }
247
248            impl core::ops::MulAssign<u64> for $name {
249                fn mul_assign(&mut self, rhs: u64) {
250                    *self = *self * rhs;
251                }
252            }
253
254            impl core::ops::Div for $name {
255                type Output = $name;
256
257                fn div(self, rhs: Self) -> Self::Output {
258                    self / rhs.raw
259                }
260            }
261
262            impl core::ops::Div<u64> for $name {
263                type Output = $name;
264
265                fn div(self, rhs: u64) -> Self::Output {
266                    Self::new(self.raw.checked_div(rhs).expect("division by zero"))
267                }
268            }
269
270            impl core::ops::DivAssign for $name {
271                fn div_assign(&mut self, rhs: Self) {
272                    *self = *self / rhs;
273                }
274            }
275
276            impl core::ops::DivAssign<u64> for $name {
277                fn div_assign(&mut self, rhs: u64) {
278                    *self = *self / rhs;
279                }
280            }
281
282            impl PartialEq<u64> for $name {
283                fn eq(&self, other: &u64) -> bool {
284                    self.raw == *other
285                }
286            }
287
288            impl PartialEq<$name> for u64 {
289                fn eq(&self, other: &$name) -> bool {
290                    *self == other.raw
291                }
292            }
293
294            impl PartialOrd<u64> for $name {
295                fn partial_cmp(&self, other: &u64) -> Option<core::cmp::Ordering> {
296                    self.raw.partial_cmp(other)
297                }
298            }
299
300            impl PartialOrd<$name> for u64 {
301                fn partial_cmp(&self, other: &$name) -> Option<core::cmp::Ordering> {
302                    self.partial_cmp(&other.raw)
303                }
304            }
305        )*
306    };
307}
308
309define_types! {
310    NumBits, "bits";
311    NumBytes, "bytes";
312    NumBlocks, "blocks";
313}
314
315impl NumBits {
316    /// Convert the number of bits to a number of bytes rounding down.
317    pub const fn to_bytes_floor(self) -> NumBytes {
318        NumBytes::new(self.raw / 8)
319    }
320
321    /// Convert the number of bits to a number of bytes rounding up.
322    pub const fn to_bytes_ceil(self) -> NumBytes {
323        NumBytes::new(self.raw.div_ceil(8))
324    }
325}
326
327#[cfg(feature = "serde")]
328impl serde::Serialize for NumBits {
329    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
330    where
331        S: serde::Serializer,
332    {
333        serializer.serialize_u64(self.raw)
334    }
335}
336
337#[cfg(feature = "serde")]
338impl<'de> serde::Deserialize<'de> for NumBits {
339    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
340    where
341        D: serde::Deserializer<'de>,
342    {
343        Ok(Self::new(u64::deserialize(deserializer)?))
344    }
345}
346
347impl core::fmt::Display for NumBits {
348    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
349        write!(f, "{}bits", self.raw)
350    }
351}
352
353/// Auxiliary macro for implementing an operator for a combination of bits and bytes.
354macro_rules! impl_bit_byte_op {
355    ($($trait:ident, $func:ident, $trait_assign:ident, $func_assign:ident;)*) => {
356        $(
357            impl core::ops::$trait<NumBytes> for NumBits {
358                type Output = NumBits;
359
360                fn $func(self, rhs: NumBytes) -> Self::Output {
361                    self.$func(rhs.to_bits())
362                }
363            }
364
365            impl core::ops::$trait<NumBits> for NumBytes {
366                type Output = NumBits;
367
368                fn $func(self, rhs: NumBits) -> Self::Output {
369                    self.to_bits().$func(rhs)
370                }
371            }
372
373            impl core::ops::$trait_assign<NumBytes> for NumBits {
374                fn $func_assign(&mut self, rhs: NumBytes) {
375                    self.$func_assign(rhs.to_bits());
376                }
377            }
378        )*
379    };
380}
381
382impl_bit_byte_op! {
383    Add, add, AddAssign, add_assign;
384    Sub, sub, SubAssign, sub_assign;
385}
386
387impl From<NumBytes> for NumBits {
388    fn from(value: NumBytes) -> Self {
389        value.to_bits()
390    }
391}
392
393impl PartialEq<NumBits> for NumBytes {
394    fn eq(&self, other: &NumBits) -> bool {
395        self.to_bits() == *other
396    }
397}
398
399impl PartialEq<NumBytes> for NumBits {
400    fn eq(&self, other: &NumBytes) -> bool {
401        *self == other.to_bits()
402    }
403}
404
405impl PartialOrd<NumBits> for NumBytes {
406    fn partial_cmp(&self, other: &NumBits) -> Option<core::cmp::Ordering> {
407        self.to_bits().partial_cmp(other)
408    }
409}
410
411impl PartialOrd<NumBytes> for NumBits {
412    fn partial_cmp(&self, other: &NumBytes) -> Option<core::cmp::Ordering> {
413        self.partial_cmp(&other.to_bits())
414    }
415}
416
417impl NumBytes {
418    /// Construct [`NumBytes`] from the provided raw number of bytes.
419    pub const fn from_usize(n: usize) -> Self {
420        if usize::BITS > u64::BITS && n > (u64::MAX as usize) {
421            panic!("unable to convert `usize` to `NumBytes`, number exceeds 64 bits")
422        } else {
423            // We just made sure that the following conversion never truncates.
424            Self::new(n as u64)
425        }
426    }
427
428    /// Convert the number of bytes to a number of bits.
429    pub const fn to_bits(self) -> NumBits {
430        NumBits::new(
431            self.raw
432                .checked_mul(8)
433                .expect("overflow converting bytes to bits"),
434        )
435    }
436
437    /// Compute a number of blocks rounding down.
438    pub const fn to_blocks_floor(self, block_size: NumBytes) -> NumBlocks {
439        NumBlocks::new(
440            self.raw
441                .checked_div(block_size.raw)
442                .expect("division by zero, block size is zero"),
443        )
444    }
445
446    /// Compute a number of blocks rounding up.
447    pub const fn to_blocks_ceil(self, block_size: NumBytes) -> NumBlocks {
448        if block_size.raw == 0 {
449            panic!("division by zero, block size is zero")
450        }
451        NumBlocks::new(self.raw.div_ceil(block_size.raw))
452    }
453
454    /// Splits the number into a whole and a fractional part based on the provided unit.
455    pub const fn split_fractional(self, unit: ByteUnit) -> (u64, u64) {
456        let whole = self.raw / unit.num_bytes().raw;
457        let fractional = self.raw % unit.num_bytes().raw;
458        (whole, fractional)
459    }
460
461    /// Unit to use when displaying the number.
462    pub const fn display_unit(self) -> ByteUnit {
463        let mut unit_idx = ByteUnit::UNITS.len() - 1;
464        while unit_idx > 0 {
465            if ByteUnit::UNITS[unit_idx].num_bytes().raw <= self.raw {
466                break;
467            }
468            unit_idx -= 1;
469        }
470        ByteUnit::UNITS[unit_idx]
471    }
472
473    /// Parse a byte size string.
474    pub const fn parse_str(s: &str) -> Result<NumBytes, NumBytesParseError> {
475        Self::parse_ascii(s.as_bytes())
476    }
477
478    /// Parse a byte size ASCII string.
479    pub const fn parse_ascii(mut buffer: &[u8]) -> Result<NumBytes, NumBytesParseError> {
480        const fn expect_int(
481            buffer: &mut &[u8],
482            truncate: bool,
483        ) -> Result<(u64, u128), NumBytesParseError> {
484            let mut value = 0u64;
485            let mut base = 1;
486            while let Some((head, tail)) = buffer.split_first() {
487                match *head {
488                    b'0'..=b'9' => {
489                        match value.checked_mul(10) {
490                            Some(shifted) => {
491                                let Some(new_value) = shifted.checked_add((*head - b'0') as u64)
492                                else {
493                                    if !truncate {
494                                        return Err(NumBytesParseError::Overflow);
495                                    } else {
496                                        continue;
497                                    }
498                                };
499                                value = new_value;
500                                if value != 0 {
501                                    base *= 10;
502                                }
503                            }
504                            None if !truncate => {
505                                return Err(NumBytesParseError::Overflow);
506                            }
507                            _ => { /* truncate */ }
508                        };
509                    }
510                    b'_' => { /* skip underscores */ }
511                    _ => break,
512                }
513                *buffer = tail;
514            }
515            if base > 1 {
516                Ok((value, base))
517            } else {
518                // We expected an integer but there was none.
519                Err(NumBytesParseError::Format)
520            }
521        }
522        let (whole, _) = const_try!(expect_int(&mut buffer, false));
523        let mut fractional = (0, 1);
524        if let Some((b'.', tail)) = buffer.split_first() {
525            buffer = tail;
526            fractional = const_try!(expect_int(&mut buffer, true));
527        }
528        while let Some((b' ', tail)) = buffer.split_first() {
529            buffer = tail;
530        }
531        let unit = if buffer.is_empty() {
532            ByteUnit::Byte
533        } else {
534            match ByteUnit::parse_ascii(buffer) {
535                Ok(unit) => unit,
536                Err(_) => return Err(NumBytesParseError::Format),
537            }
538        };
539        let Some(value) = whole.checked_mul(unit.num_bytes().raw) else {
540            return Err(NumBytesParseError::Overflow);
541        };
542        let (mut fractional_value, mut fractional_base) = fractional;
543        if fractional_value != 0 && !matches!(unit, ByteUnit::Byte) {
544            let unit_divisor = unit.base10_fractional_divisor() as u128;
545            // Strip insignificant digits.
546            while fractional_base >= unit_divisor {
547                fractional_value /= 10;
548                fractional_base /= 10;
549            }
550            // We carry out the following operations with 128-bit integers to prevent
551            // overflows in intermediate values of the calculation. We need to do those
552            // calculations as `.5` means `1/2` of the unit's value.
553            let fractional_value = fractional_value as u128 * unit_divisor / fractional_base;
554            let unit_value = unit.num_bytes().raw as u128;
555            let unit_divisor = unit_divisor as u128;
556            let fractional_part = fractional_value * unit_value / unit_divisor;
557            if let Some(value) = value.checked_add(fractional_part as u64) {
558                return Ok(NumBytes::new(value));
559            } else {
560                return Err(NumBytesParseError::Overflow);
561            }
562        }
563        Ok(NumBytes::new(value))
564    }
565}
566
567impl core::fmt::Display for NumBytes {
568    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
569        use core::fmt::Write;
570        let unit = self.display_unit();
571        let (whole, fractional) = self.split_fractional(unit);
572        write!(f, "{}", whole)?;
573        // Compute the desired precision limited by the unit.
574        let precision = f
575            .precision()
576            .map(|p| p as u32)
577            .unwrap_or(u32::MAX)
578            .min(unit.base10_fractional_digits());
579        let mut fractional_base = 10u64.pow(precision);
580        // Convert the fractional part to base 10 (required for binary units). We carry
581        // out the computation with 128-bit integers to prevent overflows. This will also
582        // truncate the fractional part to the specified precision.
583        let mut fractional_value = ((fractional as u128) * (fractional_base as u128)
584            / (unit.num_bytes().raw as u128)) as u64;
585        if let Some(_) = f.precision() {
586            f.write_char('.')?;
587            write!(f, "{fractional_value:0p$}", p = precision as usize)?;
588        } else if fractional_value != 0 {
589            f.write_char('.')?;
590            fractional_base /= 10;
591            while fractional_base > 0 && fractional_value > 0 {
592                let digit = fractional_value / fractional_base;
593                write!(f, "{digit}")?;
594                fractional_value %= fractional_base;
595                fractional_base /= 10;
596            }
597        }
598        f.write_str(unit.as_str())
599    }
600}
601
602impl core::str::FromStr for NumBytes {
603    type Err = NumBytesParseError;
604
605    fn from_str(s: &str) -> Result<Self, Self::Err> {
606        NumBytes::parse_str(s)
607    }
608}
609
610#[cfg(feature = "serde")]
611impl serde::Serialize for NumBytes {
612    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
613    where
614        S: serde::Serializer,
615    {
616        serializer.serialize_u64(self.raw)
617    }
618}
619
620#[cfg(feature = "serde")]
621impl<'de> serde::Deserialize<'de> for NumBytes {
622    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
623    where
624        D: serde::Deserializer<'de>,
625    {
626        struct Visitor;
627
628        impl<'de> serde::de::Visitor<'de> for Visitor {
629            type Value = NumBytes;
630
631            fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
632                f.write_str("byte size")
633            }
634
635            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
636            where
637                E: serde::de::Error,
638            {
639                NumBytes::parse_str(v)
640                    .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &"byte size"))
641            }
642
643            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
644            where
645                E: serde::de::Error,
646            {
647                Ok(NumBytes::new(v))
648            }
649        }
650
651        deserializer.deserialize_u64(Visitor)
652    }
653}
654
655impl NumBlocks {
656    /// Convert the number of blocks to a number of bytes.
657    pub const fn to_bytes(self, block_size: NumBytes) -> NumBytes {
658        NumBytes::new(
659            self.raw
660                .checked_mul(block_size.raw)
661                .expect("overflow converting blocks to bytes"),
662        )
663    }
664}
665
666impl core::fmt::Display for NumBlocks {
667    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
668        write!(f, "{}blocks", self.raw)
669    }
670}
671
672/// Auxiliary macro for the definition of byte units.
673macro_rules! define_units {
674    ($($name:ident, $name_lower:ident, $suffix:literal, $suffix_lower:literal, $value:expr;)*) => {
675        /// A _byte unit_ like Megabyte (MB) or Kibibyte (KiB).
676        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
677        pub enum ByteUnit {
678            /// Base unit of one byte.
679            Byte,
680            $(
681                #[doc = concat!("1 ", $suffix, " is `", stringify!($value), "` bytes.")]
682                $name,
683            )*
684        }
685
686        impl ByteUnit {
687            /// Slice of all units.
688            pub const UNITS: &[Self] = &[Self::Byte, $(Self::$name),*];
689
690            /// String representation of the unit.
691            pub const fn as_str(self) -> &'static str {
692                match self {
693                    Self::Byte => "B",
694                    $(
695                        Self::$name => $suffix,
696                    )*
697                }
698            }
699
700            /// Number of bytes the unit corresponds to.
701            pub const fn num_bytes(self) -> NumBytes {
702                NumBytes::new(match self {
703                    Self::Byte => 1,
704                    $(
705                        Self::$name => $value,
706                    )*
707                })
708            }
709
710            /// Parse the string representation of the unit (lowercase).
711            const fn parse_ascii_lowercase(ascii: &[u8]) -> Result<Self, ByteUnitParseError> {
712                #![expect(non_upper_case_globals)]
713                $(
714                    const $name: &[u8] = $suffix_lower.as_bytes();
715                )*
716                match ascii {
717                    b"b" => Ok(Self::Byte),
718                    b"k" => Ok(Self::Kibibyte),
719                    b"m" => Ok(Self::Mebibyte),
720                    b"g" => Ok(Self::Gibibyte),
721                    b"t" => Ok(Self::Tebibyte),
722                    b"p" => Ok(Self::Pebibyte),
723                    b"e" => Ok(Self::Exbibyte),
724                    $(
725                        $name => Ok(Self::$name),
726                    )*
727                    _ => Err(ByteUnitParseError)
728                }
729            }
730
731            /// The maximal number of digits of the fractional part of the unit.
732            ///
733            /// For instance, the maximal number of digits of `kB` is three. Everything
734            /// beyond three digits represents fractional bytes.
735            const fn base10_fractional_digits(self) -> u32 {
736                match self {
737                    Self::Byte => 0,
738                    $(
739                        Self::$name => (Self::$name.num_bytes().raw * 10 - 1).ilog10(),
740                    )*
741                }
742            }
743
744            /// The base-10 divisor of the fractional part.
745            const fn base10_fractional_divisor(self) -> u64 {
746                10u64.pow(self.base10_fractional_digits())
747            }
748        }
749
750        impl NumBytes {
751            /// Construct [`NumBytes`] from the given number `n` of bytes.
752            pub const fn bytes(n: u64) -> Self {
753                Self::new(n)
754            }
755
756            $(
757                #[doc = concat!("Construct [`NumBytes`] from the given number `n` of ", stringify!($name), "s.")]
758                pub const fn $name_lower(n: u64) -> NumBytes {
759                    NumBytes::new(n * $value)
760                }
761            )*
762        }
763    };
764}
765
766define_units! {
767    Kilobyte, kilobytes, "kB", "kb", 10u64.pow(3);
768    Kibibyte, kibibytes, "KiB", "kib", 1 << 10;
769    Megabyte, megabytes, "MB", "mb", 10u64.pow(6);
770    Mebibyte, mebibytes, "MiB", "mib", 1 << 20;
771    Gigabyte, gigabytes, "GB", "gb", 10u64.pow(9);
772    Gibibyte, gibibytes, "GiB", "gib", 1 << 30;
773    Terabyte, terabytes, "TB", "tb", 10u64.pow(12);
774    Tebibyte, tebibytes, "TiB", "tib", 1 << 40;
775    Petabyte, petabytes, "PB", "pb", 10u64.pow(15);
776    Pebibyte, pebibytes, "PiB", "pib", 1 << 50;
777    Exabyte, exabytes, "EB", "eb", 10u64.pow(18);
778    Exbibyte, exbibytes, "EiB", "eib", 1 << 60;
779}
780
781impl ByteUnit {
782    /// Parse the string representation of a unit (case insensitive).
783    pub const fn parse_ascii(ascii: &[u8]) -> Result<Self, ByteUnitParseError> {
784        // We exploit the fact that we know the maximal length to make this work in `const`.
785        match ascii {
786            [b1] => Self::parse_ascii_lowercase(&[b1.to_ascii_lowercase()]),
787            [b1, b2] => {
788                Self::parse_ascii_lowercase(&[b1.to_ascii_lowercase(), b2.to_ascii_lowercase()])
789            }
790            [b1, b2, b3] => Self::parse_ascii_lowercase(&[
791                b1.to_ascii_lowercase(),
792                b2.to_ascii_lowercase(),
793                b3.to_ascii_lowercase(),
794            ]),
795            _ => Err(ByteUnitParseError),
796        }
797    }
798
799    /// Parse the string representation of the unit (case insensitive).
800    pub const fn parse_str(string: &str) -> Result<Self, ByteUnitParseError> {
801        Self::parse_ascii(string.as_bytes())
802    }
803}
804
805impl core::str::FromStr for ByteUnit {
806    type Err = ByteUnitParseError;
807
808    fn from_str(s: &str) -> Result<Self, Self::Err> {
809        Self::parse_str(s)
810    }
811}
812
813impl From<ByteUnit> for NumBytes {
814    fn from(value: ByteUnit) -> Self {
815        value.num_bytes()
816    }
817}
818
819/// Type which has a well-defined length in bytes.
820pub trait ByteLen {
821    /// Length in bytes of the value.
822    fn byte_len(&self) -> NumBytes;
823}
824
825impl ByteLen for str {
826    fn byte_len(&self) -> NumBytes {
827        NumBytes::from_usize(self.len())
828    }
829}
830
831impl ByteLen for [u8] {
832    fn byte_len(&self) -> NumBytes {
833        NumBytes::from_usize(self.len())
834    }
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840
841    #[test]
842    pub fn test_byte_unit_order() {
843        let mut unit_iter = ByteUnit::UNITS.iter().peekable();
844        while let Some(this) = unit_iter.next() {
845            if let Some(next) = unit_iter.peek() {
846                assert!(this.num_bytes() < next.num_bytes())
847            }
848        }
849    }
850
851    #[test]
852    pub fn test_byte_display_unit() {
853        assert_eq!(NumBytes::new(0).display_unit(), ByteUnit::Byte);
854        for unit in ByteUnit::UNITS {
855            assert_eq!(unit.num_bytes().display_unit(), *unit);
856        }
857    }
858
859    #[test]
860    pub fn test_base10_fractional_digits() {
861        assert_eq!(ByteUnit::Byte.base10_fractional_digits(), 0);
862        assert_eq!(ByteUnit::Kilobyte.base10_fractional_digits(), 3);
863        assert_eq!(ByteUnit::Kibibyte.base10_fractional_digits(), 4);
864        assert_eq!(ByteUnit::Exabyte.base10_fractional_digits(), 18);
865        assert_eq!(ByteUnit::Exbibyte.base10_fractional_digits(), 19);
866    }
867}