typed_bytesize/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[cfg(test)]
4mod tests;
5
6#[cfg(feature = "serde")]
7mod serde;
8
9use core::{
10    num::{IntErrorKind, ParseFloatError, ParseIntError},
11    ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},
12    str::FromStr,
13};
14
15/// 1 byte
16pub const B: u64 = 1;
17
18/// 1 kilobyte
19pub const KB: u64 = 10u64.pow(3);
20/// 1 megabyte
21pub const MB: u64 = 10u64.pow(6);
22/// 1 gigabyte
23pub const GB: u64 = 10u64.pow(9);
24/// 1 terabyte
25pub const TB: u64 = 10u64.pow(12);
26/// 1 petabyte
27pub const PB: u64 = 10u64.pow(15);
28/// 1 exabyte
29pub const EB: u64 = 10u64.pow(18);
30
31/// Decimal prefix bytesize
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
33pub struct ByteSizeSi(pub u64);
34
35impl ByteSizeSi {
36    const PREFIXES: &'static [char] = &['k', 'M', 'G', 'T', 'P', 'E'];
37}
38
39impl core::fmt::Display for ByteSizeSi {
40    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
41        if self.0 < KB {
42            write!(f, "{}B", self.0)
43        } else {
44            let size = self.0 as f64;
45            let exp = (size.log(KB as f64) as usize).min(Self::PREFIXES.len());
46            let number = size / (KB.pow(exp as u32) as f64);
47            let prefix = Self::PREFIXES[exp - 1];
48
49            write!(f, "{number:.1}{prefix}B")
50        }
51    }
52}
53
54impl FromStr for ByteSizeSi {
55    type Err = Error;
56
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        parse_byte_size(s).map(Self)
59    }
60}
61
62impl ByteSizeSi {
63    /// The smallest value that can be represented.
64    pub const MIN: Self = Self(u64::MIN);
65
66    /// The largest value that can be represented.
67    pub const MAX: Self = Self(u64::MAX);
68
69    #[inline]
70    pub fn b(n: impl Into<u64>) -> Self {
71        Self(n.into())
72    }
73
74    #[inline(always)]
75    pub const fn kb(n: u64) -> Self {
76        Self(n * KB)
77    }
78
79    #[inline(always)]
80    pub const fn mb(n: u64) -> Self {
81        Self(n * MB)
82    }
83
84    #[inline(always)]
85    pub const fn gb(n: u64) -> Self {
86        Self(n * GB)
87    }
88
89    #[inline(always)]
90    pub const fn tb(n: u64) -> Self {
91        Self(n * TB)
92    }
93
94    #[inline(always)]
95    pub const fn pb(n: u64) -> Self {
96        Self(n * PB)
97    }
98
99    #[inline(always)]
100    pub const fn eb(n: u64) -> Self {
101        Self(n * EB)
102    }
103
104    /// Convert into binary prefix unit
105    #[inline(always)]
106    pub const fn iec(self) -> ByteSizeIec {
107        ByteSizeIec(self.0)
108    }
109
110    #[inline(always)]
111    pub const fn as_kb(self) -> f64 {
112        self.0 as f64 / KB as f64
113    }
114
115    #[inline(always)]
116    pub const fn as_mb(self) -> f64 {
117        self.0 as f64 / MB as f64
118    }
119
120    #[inline(always)]
121    pub const fn as_gb(self) -> f64 {
122        self.0 as f64 / GB as f64
123    }
124
125    #[inline(always)]
126    pub const fn as_tb(self) -> f64 {
127        self.0 as f64 / TB as f64
128    }
129
130    #[inline(always)]
131    pub const fn as_pb(self) -> f64 {
132        self.0 as f64 / PB as f64
133    }
134
135    #[inline(always)]
136    pub const fn as_eb(self) -> f64 {
137        self.0 as f64 / EB as f64
138    }
139}
140
141impl From<u64> for ByteSizeSi {
142    fn from(n: u64) -> Self {
143        Self(n)
144    }
145}
146
147impl From<ByteSizeSi> for u64 {
148    fn from(bs: ByteSizeSi) -> Self {
149        bs.0
150    }
151}
152
153impl Add for ByteSizeSi {
154    type Output = Self;
155
156    #[inline(always)]
157    fn add(self, rhs: Self) -> Self::Output {
158        Self(self.0 + rhs.0)
159    }
160}
161
162impl AddAssign for ByteSizeSi {
163    #[inline(always)]
164    fn add_assign(&mut self, rhs: Self) {
165        self.0 += rhs.0;
166    }
167}
168
169impl Sub for ByteSizeSi {
170    type Output = Self;
171
172    #[inline(always)]
173    fn sub(self, rhs: Self) -> Self::Output {
174        Self(self.0 - rhs.0)
175    }
176}
177
178impl SubAssign for ByteSizeSi {
179    #[inline(always)]
180    fn sub_assign(&mut self, rhs: Self) {
181        self.0 -= rhs.0;
182    }
183}
184
185impl<T> Mul<T> for ByteSizeSi
186where
187    T: Into<u64>,
188{
189    type Output = Self;
190
191    #[inline]
192    fn mul(self, rhs: T) -> Self::Output {
193        Self(self.0 * rhs.into())
194    }
195}
196
197impl<T> MulAssign<T> for ByteSizeSi
198where
199    T: Into<u64>,
200{
201    #[inline]
202    fn mul_assign(&mut self, rhs: T) {
203        self.0 *= rhs.into();
204    }
205}
206
207/// 1 kibibyte
208pub const KIB: u64 = 2u64.pow(10);
209/// 1 mebibyte
210pub const MIB: u64 = 2u64.pow(20);
211/// 1 gibibyte
212pub const GIB: u64 = 2u64.pow(30);
213/// 1 tebibyte
214pub const TIB: u64 = 2u64.pow(40);
215/// 1 pebibyte
216pub const PIB: u64 = 2u64.pow(50);
217/// 1 exbibyte
218pub const EIB: u64 = 2u64.pow(60);
219
220/// Binary prefix bytesize
221#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
222pub struct ByteSizeIec(pub u64);
223
224impl ByteSizeIec {
225    const PREFIXES: &'static [char] = &['K', 'M', 'G', 'T', 'P', 'E'];
226}
227
228impl core::fmt::Display for ByteSizeIec {
229    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
230        if self.0 < KIB {
231            write!(f, "{}B", self.0)
232        } else {
233            let size = self.0 as f64;
234            let exp = (size.log(KIB as f64) as usize).min(Self::PREFIXES.len());
235            let number = size / (KIB.pow(exp as u32) as f64);
236            let prefix = Self::PREFIXES[exp - 1];
237
238            write!(f, "{number:.1}{prefix}iB")
239        }
240    }
241}
242
243impl FromStr for ByteSizeIec {
244    type Err = Error;
245
246    fn from_str(s: &str) -> Result<Self, Self::Err> {
247        parse_byte_size(s).map(Self)
248    }
249}
250
251impl ByteSizeIec {
252    /// The smallest value that can be represented.
253    pub const MIN: Self = Self(u64::MIN);
254
255    /// The largest value that can be represented.
256    pub const MAX: Self = Self(u64::MAX);
257
258    #[inline]
259    pub fn b(n: impl Into<u64>) -> Self {
260        Self(n.into())
261    }
262
263    #[inline(always)]
264    pub const fn kib(n: u64) -> Self {
265        Self(n * KIB)
266    }
267
268    #[inline(always)]
269    pub const fn mib(n: u64) -> Self {
270        Self(n * MIB)
271    }
272
273    #[inline(always)]
274    pub const fn gib(n: u64) -> Self {
275        Self(n * GIB)
276    }
277
278    #[inline(always)]
279    pub const fn tib(n: u64) -> Self {
280        Self(n * TIB)
281    }
282
283    #[inline(always)]
284    pub const fn pib(n: u64) -> Self {
285        Self(n * PIB)
286    }
287
288    #[inline(always)]
289    pub const fn eib(n: u64) -> Self {
290        Self(n * EIB)
291    }
292
293    /// Convert into decimal prefix unit
294    #[inline(always)]
295    pub const fn si(self) -> ByteSizeSi {
296        ByteSizeSi(self.0)
297    }
298
299    #[inline(always)]
300    pub const fn as_kib(self) -> f64 {
301        self.0 as f64 / KIB as f64
302    }
303
304    #[inline(always)]
305    pub const fn as_mib(self) -> f64 {
306        self.0 as f64 / MIB as f64
307    }
308
309    #[inline(always)]
310    pub const fn as_gib(self) -> f64 {
311        self.0 as f64 / GIB as f64
312    }
313
314    #[inline(always)]
315    pub const fn as_tib(self) -> f64 {
316        self.0 as f64 / TIB as f64
317    }
318
319    #[inline(always)]
320    pub const fn as_pib(self) -> f64 {
321        self.0 as f64 / PIB as f64
322    }
323
324    #[inline(always)]
325    pub const fn as_eib(self) -> f64 {
326        self.0 as f64 / EIB as f64
327    }
328}
329
330impl From<u64> for ByteSizeIec {
331    fn from(n: u64) -> Self {
332        Self(n)
333    }
334}
335
336impl From<ByteSizeIec> for u64 {
337    fn from(bs: ByteSizeIec) -> Self {
338        bs.0
339    }
340}
341
342impl Add for ByteSizeIec {
343    type Output = Self;
344
345    #[inline(always)]
346    fn add(self, rhs: Self) -> Self::Output {
347        Self(self.0 + rhs.0)
348    }
349}
350
351impl AddAssign for ByteSizeIec {
352    #[inline(always)]
353    fn add_assign(&mut self, rhs: Self) {
354        self.0 += rhs.0;
355    }
356}
357
358impl Sub for ByteSizeIec {
359    type Output = Self;
360
361    #[inline(always)]
362    fn sub(self, rhs: Self) -> Self::Output {
363        Self(self.0 - rhs.0)
364    }
365}
366
367impl SubAssign for ByteSizeIec {
368    #[inline(always)]
369    fn sub_assign(&mut self, rhs: Self) {
370        self.0 -= rhs.0;
371    }
372}
373
374impl<T> Mul<T> for ByteSizeIec
375where
376    T: Into<u64>,
377{
378    type Output = Self;
379
380    #[inline]
381    fn mul(self, rhs: T) -> Self::Output {
382        Self(self.0 * rhs.into())
383    }
384}
385
386impl<T> MulAssign<T> for ByteSizeIec
387where
388    T: Into<u64>,
389{
390    #[inline]
391    fn mul_assign(&mut self, rhs: T) {
392        self.0 *= rhs.into();
393    }
394}
395
396fn parse_byte_size(input: &str) -> Result<u64, Error> {
397    let Some(i) = input.find(|c: char| !c.is_ascii_digit()) else {
398        return input.parse::<u64>().map_err(Into::into);
399    };
400
401    if i == 0 {
402        return Err(Error::Invalid);
403    }
404
405    let (integer, rest) = input.split_at(i);
406
407    if let Some(rest) = rest.strip_prefix('.') {
408        let i = i
409            + 1
410            + rest
411                .find(|c: char| !c.is_ascii_digit())
412                .filter(|n| *n > 0)
413                .ok_or(Error::Invalid)?;
414
415        let (float, rest) = input.split_at(i);
416        let float = float.parse::<f64>()?;
417        let unit = parse_unit(rest.trim_start_matches(' '))?;
418        Ok((float * unit as f64) as u64)
419    } else {
420        let integer = integer.parse::<u64>()?;
421        let unit = parse_unit(rest.trim_start_matches(' '))?;
422        Ok(integer * unit)
423    }
424}
425
426fn parse_unit(input: &str) -> Result<u64, Error> {
427    match input.to_lowercase().as_str() {
428        "b" => Ok(B),
429        // SI
430        "k" | "kb" => Ok(KB),
431        "m" | "mb" => Ok(MB),
432        "g" | "gb" => Ok(GB),
433        "t" | "tb" => Ok(TB),
434        "p" | "pb" => Ok(PB),
435        "e" | "eb" => Ok(EB),
436        // IEC 60027-2
437        "ki" | "kib" => Ok(KIB),
438        "mi" | "mib" => Ok(MIB),
439        "gi" | "gib" => Ok(GIB),
440        "ti" | "tib" => Ok(TIB),
441        "pi" | "pib" => Ok(PIB),
442        "ei" | "eib" => Ok(EIB),
443        _ => Err(Error::Unit),
444    }
445}
446
447impl From<ByteSizeIec> for ByteSizeSi {
448    fn from(iec: ByteSizeIec) -> Self {
449        iec.si()
450    }
451}
452
453impl From<ByteSizeSi> for ByteSizeIec {
454    fn from(si: ByteSizeSi) -> Self {
455        si.iec()
456    }
457}
458
459#[derive(Debug, PartialEq, Eq)]
460pub enum Error {
461    Empty,
462    Invalid,
463    Unit,
464}
465
466impl core::error::Error for Error {}
467
468impl core::fmt::Display for Error {
469    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
470        let msg = match self {
471            Self::Empty => "cannot parse bytesize from empty string",
472            Self::Invalid => "invalid number found in string",
473            Self::Unit => "cannot recognize byte unit in string",
474        };
475        f.write_str(msg)
476    }
477}
478
479impl From<ParseIntError> for Error {
480    fn from(e: ParseIntError) -> Self {
481        match e.kind() {
482            IntErrorKind::Empty => Self::Empty,
483            _ => Self::Invalid,
484        }
485    }
486}
487
488impl From<ParseFloatError> for Error {
489    fn from(_: ParseFloatError) -> Self {
490        Self::Invalid
491    }
492}