parse_size/
lib.rs

1//! `parse-size` is an accurate, customizable, allocation-free library for
2//! parsing byte size into integer.
3//!
4//! ```rust
5//! use parse_size::parse_size;
6//!
7//! assert_eq!(parse_size("0.2 MiB"), Ok(209715));
8//! assert_eq!(parse_size("14.2e+8"), Ok(14_2000_0000));
9//! ```
10//!
11//! # Features
12//!
13//! Supports both binary and decimal based prefix up to exabytes.
14//!
15//! ```rust
16//! # use parse_size::parse_size;
17//! assert_eq!(parse_size("1 B"), Ok(1));
18//! assert_eq!(parse_size("1 KiB"), Ok(1 << 10));
19//! assert_eq!(parse_size("1 MiB"), Ok(1 << 20));
20//! assert_eq!(parse_size("1 GiB"), Ok(1 << 30));
21//! assert_eq!(parse_size("1 TiB"), Ok(1 << 40));
22//! assert_eq!(parse_size("1 PiB"), Ok(1 << 50));
23//! assert_eq!(parse_size("1 EiB"), Ok(1 << 60));
24//! assert_eq!(parse_size("1 KB"), Ok(1_000));
25//! assert_eq!(parse_size("1 MB"), Ok(1_000_000));
26//! assert_eq!(parse_size("1 GB"), Ok(1_000_000_000));
27//! assert_eq!(parse_size("1 TB"), Ok(1_000_000_000_000));
28//! assert_eq!(parse_size("1 PB"), Ok(1_000_000_000_000_000));
29//! assert_eq!(parse_size("1 EB"), Ok(1_000_000_000_000_000_000));
30//! ```
31//!
32//! Numbers can be fractional and/or in scientific notation.
33//! `parse-size` can accurately parse the input using the full 64-bit precision.
34//!
35//! ```rust
36//! # use parse_size::parse_size;
37//! assert_eq!(parse_size("2.999999999999999999e18"), Ok(2999999999999999999));
38//! assert_eq!(parse_size("3.000000000000000001 EB"), Ok(3000000000000000001));
39//! ```
40//!
41//! The unit is case-insensitive. The "B" suffix is also optional.
42//!
43//! ```rust
44//! # use parse_size::parse_size;
45//! assert_eq!(parse_size("5gb"), Ok(5_000_000_000));
46//! assert_eq!(parse_size("2ki"), Ok(2048));
47//! ```
48//!
49//! Fractional bytes are allowed, and rounded to nearest integer.
50//!
51//! ```rust
52//! # use parse_size::parse_size;
53//! assert_eq!(parse_size("0.333333 KB"), Ok(333));
54//! assert_eq!(parse_size("2.666666 KB"), Ok(2667));
55//! ```
56//!
57//! Underscores and spaces in the numbers are ignored to support digit grouping.
58//!
59//! ```rust
60//! # use parse_size::parse_size;
61//! assert_eq!(parse_size(" 69_420_000"), Ok(69_420_000));
62//! ```
63//!
64//! Conventional units (KB, GB, ...) can be configured to use the binary system.
65//!
66//! ```rust
67//! use parse_size::Config;
68//!
69//! let cfg = Config::new().with_binary();
70//! assert_eq!(cfg.parse_size("1 KB"), Ok(1024));
71//! assert_eq!(cfg.parse_size("1 KiB"), Ok(1024));
72//! assert_eq!(cfg.parse_size("1 MB"), Ok(1048576));
73//! assert_eq!(cfg.parse_size("1 MiB"), Ok(1048576));
74//! ```
75//!
76//! # Integration examples
77//!
78//! Use with `clap` v4:
79//!
80//! ```rust
81//! use clap::Parser;
82//! use parse_size::parse_size;
83//!
84//! #[derive(Parser)]
85//! pub struct Opt {
86//      // FIXME: we need a closure because of https://github.com/clap-rs/clap/issues/4939
87//!     #[arg(long, value_parser = |s: &str| parse_size(s))]
88//!     pub size: u64,
89//! }
90//!
91//! let opt = Opt::parse_from(&["./app", "--size", "2.5 K"]);
92//! assert_eq!(opt.size, 2500);
93//! ```
94
95#![no_std]
96
97use core::{convert::TryFrom, fmt, num::IntErrorKind};
98
99/// The system to use when parsing prefixes like "KB" and "GB".
100#[derive(Copy, Clone, PartialEq, Eq, Debug)]
101pub enum UnitSystem {
102    /// Use powers of 1000 for prefixes. Parsing "1 KB" returns 1000.
103    Decimal,
104    /// Use powers of 1024 for prefixes. Parsing "1 KB" returns 1024.
105    Binary,
106}
107
108impl UnitSystem {
109    /// Obtains the power factor for the given prefix character.
110    ///
111    /// Returns None if the input is not a valid prefix.
112    ///
113    /// The only valid prefixes are K, M, G, T, P and E. The higher powers Z and
114    /// Y exceed the `u64` range and thus considered invalid.
115    fn factor(self, prefix: u8) -> Option<u64> {
116        Some(match (self, prefix) {
117            (Self::Decimal, b'k' | b'K') => 1_000,
118            (Self::Decimal, b'm' | b'M') => 1_000_000,
119            (Self::Decimal, b'g' | b'G') => 1_000_000_000,
120            (Self::Decimal, b't' | b'T') => 1_000_000_000_000,
121            (Self::Decimal, b'p' | b'P') => 1_000_000_000_000_000,
122            (Self::Decimal, b'e' | b'E') => 1_000_000_000_000_000_000,
123            (Self::Binary, b'k' | b'K') => 1_u64 << 10,
124            (Self::Binary, b'm' | b'M') => 1_u64 << 20,
125            (Self::Binary, b'g' | b'G') => 1_u64 << 30,
126            (Self::Binary, b't' | b'T') => 1_u64 << 40,
127            (Self::Binary, b'p' | b'P') => 1_u64 << 50,
128            (Self::Binary, b'e' | b'E') => 1_u64 << 60,
129            _ => return None,
130        })
131    }
132}
133
134/// How to deal with the "B" suffix.
135#[derive(Copy, Clone, PartialEq, Eq, Debug)]
136pub enum ByteSuffix {
137    /// The "B" suffix must never appear. Parsing a string with the "B" suffix
138    /// causes [`Error::InvalidDigit`] error.
139    Deny,
140    /// The "B" suffix is optional.
141    Allow,
142    /// The "B" suffix is required. Parsing a string without the "B" suffix
143    /// causes [`Error::InvalidDigit`] error.
144    Require,
145}
146
147/// Configuration of the parser.
148#[derive(Clone, Debug)]
149pub struct Config {
150    unit_system: UnitSystem,
151    default_factor: u64,
152    byte_suffix: ByteSuffix,
153}
154
155impl Config {
156    /// Creates a new parser configuration.
157    #[must_use]
158    pub const fn new() -> Self {
159        Self {
160            unit_system: UnitSystem::Decimal,
161            default_factor: 1,
162            byte_suffix: ByteSuffix::Allow,
163        }
164    }
165
166    /// Changes the configuration's unit system.
167    ///
168    /// The default system is decimal (powers of 1000).
169    #[must_use]
170    pub const fn with_unit_system(mut self, unit_system: UnitSystem) -> Self {
171        self.unit_system = unit_system;
172        self
173    }
174
175    /// Changes the configuration to use the binary unit system, which are
176    /// defined to be powers of 1024.
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// use parse_size::Config;
182    ///
183    /// let cfg = Config::new().with_binary();
184    /// assert_eq!(cfg.parse_size("1 KB"), Ok(1024));
185    /// assert_eq!(cfg.parse_size("1 KiB"), Ok(1024));
186    /// assert_eq!(cfg.parse_size("1 MB"), Ok(1048576));
187    /// assert_eq!(cfg.parse_size("1 MiB"), Ok(1048576));
188    /// ```
189    #[must_use]
190    pub const fn with_binary(self) -> Self {
191        self.with_unit_system(UnitSystem::Binary)
192    }
193
194    /// Changes the configuration to use the decimal unit system, which are
195    /// defined to be powers of 1000. This is the default setting.
196    ///
197    /// # Examples
198    ///
199    /// ```rust
200    /// use parse_size::Config;
201    ///
202    /// let cfg = Config::new().with_decimal();
203    /// assert_eq!(cfg.parse_size("1 KB"), Ok(1000));
204    /// assert_eq!(cfg.parse_size("1 KiB"), Ok(1024));
205    /// assert_eq!(cfg.parse_size("1 MB"), Ok(1000000));
206    /// assert_eq!(cfg.parse_size("1 MiB"), Ok(1048576));
207    /// ```
208    #[must_use]
209    pub const fn with_decimal(self) -> Self {
210        self.with_unit_system(UnitSystem::Decimal)
211    }
212
213    /// Changes the default factor when a byte unit is not provided.
214    ///
215    /// This is useful for keeping backward compatibility when migrating from an
216    /// old user interface expecting non-byte input.
217    ///
218    /// The default value is 1.
219    ///
220    /// # Examples
221    ///
222    /// If the input is a pure number, we treat that as mebibytes.
223    ///
224    /// ```rust
225    /// use parse_size::Config;
226    ///
227    /// let cfg = Config::new().with_default_factor(1048576);
228    /// assert_eq!(cfg.parse_size("10"), Ok(10485760));
229    /// assert_eq!(cfg.parse_size("0.5"), Ok(524288));
230    /// assert_eq!(cfg.parse_size("128 B"), Ok(128)); // explicit units overrides the default
231    /// assert_eq!(cfg.parse_size("16 KiB"), Ok(16384));
232    /// ```
233    #[must_use]
234    pub const fn with_default_factor(mut self, factor: u64) -> Self {
235        self.default_factor = factor;
236        self
237    }
238
239    /// Changes the handling of the "B" suffix.
240    ///
241    /// Normally, the character "B" at the end of the input is optional. This
242    /// can be changed to deny or require such suffix.
243    ///
244    /// Power prefixes (K, Ki, M, Mi, ...) are not affected.
245    ///
246    /// # Examples
247    ///
248    /// Deny the suffix.
249    ///
250    /// ```rust
251    /// use parse_size::{ByteSuffix, Config, Error};
252    ///
253    /// let cfg = Config::new().with_byte_suffix(ByteSuffix::Deny);
254    /// assert_eq!(cfg.parse_size("123"), Ok(123));
255    /// assert_eq!(cfg.parse_size("123k"), Ok(123000));
256    /// assert_eq!(cfg.parse_size("123B"), Err(Error::InvalidDigit));
257    /// assert_eq!(cfg.parse_size("123KB"), Err(Error::InvalidDigit));
258    /// ```
259    ///
260    /// Require the suffix.
261    ///
262    /// ```rust
263    /// use parse_size::{ByteSuffix, Config, Error};
264    ///
265    /// let cfg = Config::new().with_byte_suffix(ByteSuffix::Require);
266    /// assert_eq!(cfg.parse_size("123"), Err(Error::InvalidDigit));
267    /// assert_eq!(cfg.parse_size("123k"), Err(Error::InvalidDigit));
268    /// assert_eq!(cfg.parse_size("123B"), Ok(123));
269    /// assert_eq!(cfg.parse_size("123KB"), Ok(123000));
270    /// ```
271    #[must_use]
272    pub const fn with_byte_suffix(mut self, byte_suffix: ByteSuffix) -> Self {
273        self.byte_suffix = byte_suffix;
274        self
275    }
276
277    /// Parses the string input into the number of bytes it represents.
278    ///
279    /// # Examples
280    ///
281    /// ```rust
282    /// use parse_size::{Config, Error};
283    ///
284    /// let cfg = Config::new().with_binary();
285    /// assert_eq!(cfg.parse_size("10 KB"), Ok(10240));
286    /// assert_eq!(cfg.parse_size("20000"), Ok(20000));
287    /// assert_eq!(cfg.parse_size("^_^"), Err(Error::InvalidDigit));
288    /// ```
289    ///
290    /// # Errors
291    ///
292    /// Returns an [`Error`] if the input has invalid digits or the result
293    /// exceeded 2<sup>64</sup>−1.
294    pub fn parse_size<T: AsRef<[u8]>>(&self, src: T) -> Result<u64, Error> {
295        self.parse_size_inner(src.as_ref())
296    }
297}
298
299impl Default for Config {
300    fn default() -> Self {
301        Self::new()
302    }
303}
304
305/// The error returned when parse failed.
306#[derive(Copy, Clone, PartialEq, Eq, Debug)]
307#[non_exhaustive]
308pub enum Error {
309    /// The input contains no numbers.
310    Empty,
311    /// An invalid character is encountered while parsing.
312    InvalidDigit,
313    /// The resulting number is too large to fit into a `u64`.
314    PosOverflow,
315}
316
317impl fmt::Display for Error {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        f.write_str(match self {
320            Self::Empty => "cannot parse integer from empty string",
321            Self::InvalidDigit => "invalid digit found in string",
322            Self::PosOverflow => "number too large to fit in target type",
323        })
324    }
325}
326
327impl core::error::Error for Error {}
328
329impl From<Error> for IntErrorKind {
330    fn from(e: Error) -> Self {
331        match e {
332            Error::Empty => Self::Empty,
333            Error::InvalidDigit => Self::InvalidDigit,
334            Error::PosOverflow => Self::PosOverflow,
335        }
336    }
337}
338
339/// Parses the string input into the number of bytes it represents using the
340/// default configuration.
341///
342/// Equivalent to calling [`Config::parse_size()`] with the default
343/// configuration ([`Config::new()`]).
344///
345/// # Examples
346///
347/// ```rust
348/// use parse_size::{parse_size, Error};
349///
350/// assert_eq!(parse_size("10 KB"), Ok(10000));
351/// assert_eq!(parse_size("20000"), Ok(20000));
352/// assert_eq!(parse_size("0.2 MiB"), Ok(209715));
353/// assert_eq!(parse_size("^_^"), Err(Error::InvalidDigit));
354/// ```
355///
356/// # Errors
357///
358/// Returns an [`Error`] if the input has invalid digits or the result exceeded
359/// 2<sup>64</sup>−1.
360pub fn parse_size<T: AsRef<[u8]>>(src: T) -> Result<u64, Error> {
361    Config::new().parse_size(src)
362}
363
364impl Config {
365    fn parse_size_inner(&self, mut src: &[u8]) -> Result<u64, Error> {
366        // if it ends with 'B' the default factor is always 1.
367        let mut multiply = if let [init @ .., b'b' | b'B'] = src {
368            if self.byte_suffix == ByteSuffix::Deny {
369                return Err(Error::InvalidDigit);
370            }
371            src = init;
372            1
373        } else {
374            if self.byte_suffix == ByteSuffix::Require {
375                return Err(Error::InvalidDigit);
376            }
377            self.default_factor
378        };
379
380        // if it ends with an 'i' we always use binary prefix.
381        let unit_system = if let [init @ .., b'i' | b'I'] = src {
382            src = init;
383            UnitSystem::Binary
384        } else {
385            self.unit_system
386        };
387
388        if let [init @ .., prefix] = src {
389            if let Some(f) = unit_system.factor(*prefix) {
390                multiply = f;
391                src = init;
392            }
393        }
394
395        parse_size_with_multiple(src, multiply)
396    }
397}
398
399fn parse_size_with_multiple(src: &[u8], multiply: u64) -> Result<u64, Error> {
400    #[derive(Copy, Clone, PartialEq)]
401    enum Ps {
402        Empty,
403        Integer,
404        IntegerOverflow,
405        Fraction,
406        FractionOverflow,
407        PosExponent,
408        NegExponent,
409    }
410
411    macro_rules! append_digit {
412        ($before:expr, $method:ident, $digit_char:expr) => {
413            $before
414                .checked_mul(10)
415                .and_then(|v| v.$method(($digit_char - b'0').into()))
416        };
417    }
418
419    let mut mantissa = 0_u64;
420    let mut fractional_exponent = 0;
421    let mut exponent = 0_i32;
422    let mut state = Ps::Empty;
423
424    for b in src {
425        match (state, *b) {
426            (Ps::Integer | Ps::Empty, b'0'..=b'9') => {
427                if let Some(m) = append_digit!(mantissa, checked_add, *b) {
428                    mantissa = m;
429                    state = Ps::Integer;
430                } else {
431                    if *b >= b'5' {
432                        mantissa += 1;
433                    }
434                    state = Ps::IntegerOverflow;
435                    fractional_exponent += 1;
436                }
437            }
438            (Ps::IntegerOverflow, b'0'..=b'9') => {
439                fractional_exponent += 1;
440            }
441            (Ps::Fraction, b'0'..=b'9') => {
442                if let Some(m) = append_digit!(mantissa, checked_add, *b) {
443                    mantissa = m;
444                    fractional_exponent -= 1;
445                } else {
446                    if *b >= b'5' {
447                        mantissa += 1;
448                    }
449                    state = Ps::FractionOverflow;
450                }
451            }
452            (Ps::PosExponent, b'0'..=b'9') => {
453                if let Some(e) = append_digit!(exponent, checked_add, *b) {
454                    exponent = e;
455                } else {
456                    return Err(Error::PosOverflow);
457                }
458            }
459            (Ps::NegExponent, b'0'..=b'9') => {
460                if let Some(e) = append_digit!(exponent, checked_sub, *b) {
461                    exponent = e;
462                }
463            }
464
465            (_, b'_' | b' ') | (Ps::PosExponent, b'+') | (Ps::FractionOverflow, b'0'..=b'9') => {}
466            (
467                Ps::Integer | Ps::Fraction | Ps::IntegerOverflow | Ps::FractionOverflow,
468                b'e' | b'E',
469            ) => state = Ps::PosExponent,
470            (Ps::PosExponent, b'-') => state = Ps::NegExponent,
471            (Ps::Integer, b'.') => state = Ps::Fraction,
472            (Ps::IntegerOverflow, b'.') => state = Ps::FractionOverflow,
473            _ => return Err(Error::InvalidDigit),
474        }
475    }
476
477    if state == Ps::Empty {
478        return Err(Error::Empty);
479    }
480
481    let exponent = exponent.saturating_add(fractional_exponent);
482    let abs_exponent = exponent.unsigned_abs();
483    if exponent >= 0 {
484        let power = 10_u64.checked_pow(abs_exponent).ok_or(Error::PosOverflow)?;
485        let multiply = multiply.checked_mul(power).ok_or(Error::PosOverflow)?;
486        mantissa.checked_mul(multiply).ok_or(Error::PosOverflow)
487    } else if exponent >= -38 {
488        let power = 10_u128.pow(abs_exponent);
489        let result = (u128::from(mantissa) * u128::from(multiply) + power / 2) / power;
490        u64::try_from(result).map_err(|_| Error::PosOverflow)
491    } else {
492        // (2^128) * 1e-39 < 1, always, and thus saturate to 0.
493        Ok(0)
494    }
495}
496
497#[test]
498fn test_passes() {
499    assert_eq!(parse_size("0"), Ok(0));
500    assert_eq!(parse_size("3"), Ok(3));
501    assert_eq!(parse_size("30"), Ok(30));
502    assert_eq!(parse_size("32"), Ok(32));
503    assert_eq!(parse_size("_5_"), Ok(5));
504    assert_eq!(parse_size("1 234 567"), Ok(1_234_567));
505
506    assert_eq!(
507        parse_size("18_446_744_073_709_551_581"),
508        Ok(18_446_744_073_709_551_581)
509    );
510    assert_eq!(
511        parse_size("18_446_744_073_709_551_615"),
512        Ok(18_446_744_073_709_551_615)
513    );
514    assert_eq!(
515        parse_size("18_446_744_073_709_551_616"),
516        Err(Error::PosOverflow)
517    );
518    assert_eq!(
519        parse_size("18_446_744_073_709_551_620"),
520        Err(Error::PosOverflow)
521    );
522
523    assert_eq!(parse_size("1kB"), Ok(1_000));
524    assert_eq!(parse_size("2MB"), Ok(2_000_000));
525    assert_eq!(parse_size("3GB"), Ok(3_000_000_000));
526    assert_eq!(parse_size("4TB"), Ok(4_000_000_000_000));
527    assert_eq!(parse_size("5PB"), Ok(5_000_000_000_000_000));
528    assert_eq!(parse_size("6EB"), Ok(6_000_000_000_000_000_000));
529
530    assert_eq!(parse_size("7 KiB"), Ok(7 << 10));
531    assert_eq!(parse_size("8 MiB"), Ok(8 << 20));
532    assert_eq!(parse_size("9 GiB"), Ok(9 << 30));
533    assert_eq!(parse_size("10 TiB"), Ok(10 << 40));
534    assert_eq!(parse_size("11 PiB"), Ok(11 << 50));
535    assert_eq!(parse_size("12 EiB"), Ok(12 << 60));
536
537    assert_eq!(parse_size("1mib"), Ok(1_048_576));
538
539    assert_eq!(parse_size("5k"), Ok(5000));
540    assert_eq!(parse_size("1.1 K"), Ok(1100));
541    assert_eq!(parse_size("1.2345 K"), Ok(1235));
542    assert_eq!(parse_size("1.2345m"), Ok(1_234_500));
543    assert_eq!(parse_size("5.k"), Ok(5000));
544    assert_eq!(parse_size("0.0025KB"), Ok(3));
545    assert_eq!(
546        parse_size("3.141_592_653_589_793_238e"),
547        Ok(3_141_592_653_589_793_238)
548    );
549
550    assert_eq!(
551        parse_size("18.446_744_073_709_551_615 EB"),
552        Ok(18_446_744_073_709_551_615)
553    );
554    assert_eq!(
555        parse_size("18.446_744_073_709_551_616 EB"),
556        Err(Error::PosOverflow)
557    );
558    assert_eq!(
559        parse_size("1.000_000_000_000_000_001 EB"),
560        Ok(1_000_000_000_000_000_001)
561    );
562
563    assert_eq!(parse_size("1e2 KIB"), Ok(102_400));
564    assert_eq!(parse_size("1E+6"), Ok(1_000_000));
565    assert_eq!(parse_size("1e-4 MiB"), Ok(105));
566    assert_eq!(parse_size("1.1e2"), Ok(110));
567    assert_eq!(parse_size("5.7E3"), Ok(5700));
568
569    assert_eq!(parse_size("20_000_000_000_000_000_000e-18"), Ok(20));
570    assert_eq!(parse_size("98_765_432_109_876_543_210e-16"), Ok(9877));
571    assert_eq!(parse_size("1e21"), Err(Error::PosOverflow));
572    assert_eq!(parse_size("0.01e21"), Ok(10_000_000_000_000_000_000));
573    assert_eq!(
574        parse_size("3.333_333_333_333_333_333_333_333_333_333_333_333_333_333_333_333 EB"),
575        Ok(3_333_333_333_333_333_333)
576    );
577    assert_eq!(parse_size("2e+0_9"), Ok(2_000_000_000));
578    assert_eq!(
579        parse_size("3e+66666666666666666666"),
580        Err(Error::PosOverflow)
581    );
582    assert_eq!(parse_size("4e-77777777777777777777"), Ok(0));
583    assert_eq!(parse_size("5e-88888888888888888888 EiB"), Ok(0));
584
585    assert_eq!(
586        parse_size("123_456_789_012_345_678_901_234_567.890e-16"),
587        Ok(12_345_678_901)
588    );
589    assert_eq!(parse_size("0.1e-2147483648"), Ok(0));
590    assert_eq!(parse_size("184467440737095516150e-38EiB"), Ok(2));
591}
592
593#[test]
594fn test_parse_errors() {
595    assert_eq!(parse_size(""), Err(Error::Empty));
596    assert_eq!(parse_size("."), Err(Error::InvalidDigit));
597    assert_eq!(parse_size(".5k"), Err(Error::InvalidDigit));
598    assert_eq!(parse_size("k"), Err(Error::Empty));
599    assert_eq!(parse_size("kb"), Err(Error::Empty));
600    assert_eq!(parse_size("kib"), Err(Error::Empty));
601    assert_eq!(parse_size("  "), Err(Error::Empty));
602    assert_eq!(parse_size("__"), Err(Error::Empty));
603    assert_eq!(parse_size("a"), Err(Error::InvalidDigit));
604    assert_eq!(parse_size("-1"), Err(Error::InvalidDigit));
605    assert_eq!(parse_size("1,5"), Err(Error::InvalidDigit));
606    assert_eq!(parse_size("1e+f"), Err(Error::InvalidDigit));
607    assert_eq!(parse_size("1e0.5"), Err(Error::InvalidDigit));
608    assert_eq!(parse_size("1 ZiB"), Err(Error::InvalidDigit));
609    assert_eq!(parse_size("1 YiB"), Err(Error::InvalidDigit));
610}
611
612#[test]
613fn test_config() {
614    let cfg = Config::new().with_binary().with_default_factor(1_048_576);
615    assert_eq!(cfg.parse_size("3.5"), Ok(3_670_016));
616    assert_eq!(cfg.parse_size("35 B"), Ok(35));
617    assert_eq!(cfg.parse_size("5K"), Ok(5120));
618    assert_eq!(cfg.parse_size("1.7e13"), Ok(17_825_792_000_000_000_000));
619    assert_eq!(cfg.parse_size("1.8e13"), Err(Error::PosOverflow));
620    assert_eq!(cfg.parse_size("1.8e18"), Err(Error::PosOverflow));
621
622    let cfg = Config::new()
623        .with_decimal()
624        .with_byte_suffix(ByteSuffix::Require);
625    assert_eq!(cfg.parse_size("3.5MB"), Ok(3_500_000));
626    assert_eq!(cfg.parse_size("2.5KiB"), Ok(2560));
627    assert_eq!(cfg.parse_size("7"), Err(Error::InvalidDigit));
628    assert_eq!(cfg.parse_size("9b"), Ok(9));
629
630    let cfg = Config::default().with_byte_suffix(ByteSuffix::Deny);
631    assert_eq!(cfg.parse_size("3.5MB"), Err(Error::InvalidDigit));
632    assert_eq!(cfg.parse_size("3.5M"), Ok(3_500_000));
633    assert_eq!(cfg.parse_size("7b"), Err(Error::InvalidDigit));
634}
635
636#[test]
637fn test_int_error_kind() {
638    extern crate alloc;
639    use alloc::string::ToString as _;
640
641    let test_cases = [
642        (Error::Empty, ""),
643        (Error::InvalidDigit, "?"),
644        (Error::PosOverflow, "99999999999999999999"),
645    ];
646    for (expected_error, parse_from) in test_cases {
647        let std_error = parse_from.parse::<u64>().unwrap_err();
648        let local_error = parse_size(parse_from).unwrap_err();
649        assert_eq!(local_error, expected_error);
650        assert_eq!(local_error.to_string(), std_error.to_string());
651        assert_eq!(&IntErrorKind::from(local_error), std_error.kind());
652    }
653}