bytesize/
lib.rs

1//! ByteSize is an utility that easily makes bytes size representation
2//! and helps its arithmetic operations.
3//!
4//! ## Example
5//!
6//! ```no_run
7//! extern crate bytesize;
8//!
9//! use bytesize::ByteSize;
10//!
11//! fn byte_arithmetic_operator() {
12//!   let x = ByteSize::mb(1);
13//!   let y = ByteSize::kb(100);
14//!
15//!   let plus = x + y;
16//!   print!("{} bytes", plus.as_u64());
17//! }
18//! ```
19//!
20//! It also provides its human readable string as follows:
21//!
22//! ```no_run
23//! # use bytesize::ByteSize;
24//! assert_eq!("482 GiB".to_string(), ByteSize::gb(518).to_string());
25//! assert_eq!("518 GB".to_string(), ByteSize::gb(518).to_string());
26//! ```
27
28mod parse;
29
30#[cfg(feature = "serde")]
31extern crate serde;
32#[cfg(feature = "serde")]
33use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
34#[cfg(feature = "serde")]
35use std::convert::TryFrom;
36
37use std::fmt::{self, Debug, Display, Formatter};
38use std::ops::{Add, AddAssign, Mul, MulAssign};
39
40/// byte size for 1 byte
41pub const B: u64 = 1;
42/// bytes size for 1 kilobyte
43pub const KB: u64 = 1_000;
44/// bytes size for 1 megabyte
45pub const MB: u64 = 1_000_000;
46/// bytes size for 1 gigabyte
47pub const GB: u64 = 1_000_000_000;
48/// bytes size for 1 terabyte
49pub const TB: u64 = 1_000_000_000_000;
50/// bytes size for 1 petabyte
51pub const PB: u64 = 1_000_000_000_000_000;
52
53/// bytes size for 1 kibibyte
54pub const KIB: u64 = 1_024;
55/// bytes size for 1 mebibyte
56pub const MIB: u64 = 1_048_576;
57/// bytes size for 1 gibibyte
58pub const GIB: u64 = 1_073_741_824;
59/// bytes size for 1 tebibyte
60pub const TIB: u64 = 1_099_511_627_776;
61/// bytes size for 1 pebibyte
62pub const PIB: u64 = 1_125_899_906_842_624;
63
64static UNITS: &str = "KMGTPE";
65static UNITS_SI: &str = "kMGTPE";
66static LN_KB: f64 = 6.931471806; // ln 1024
67static LN_KIB: f64 = 6.907755279; // ln 1000
68
69pub fn kb<V: Into<u64>>(size: V) -> u64 {
70    size.into() * KB
71}
72
73pub fn kib<V: Into<u64>>(size: V) -> u64 {
74    size.into() * KIB
75}
76
77pub fn mb<V: Into<u64>>(size: V) -> u64 {
78    size.into() * MB
79}
80
81pub fn mib<V: Into<u64>>(size: V) -> u64 {
82    size.into() * MIB
83}
84
85pub fn gb<V: Into<u64>>(size: V) -> u64 {
86    size.into() * GB
87}
88
89pub fn gib<V: Into<u64>>(size: V) -> u64 {
90    size.into() * GIB
91}
92
93pub fn tb<V: Into<u64>>(size: V) -> u64 {
94    size.into() * TB
95}
96
97pub fn tib<V: Into<u64>>(size: V) -> u64 {
98    size.into() * TIB
99}
100
101pub fn pb<V: Into<u64>>(size: V) -> u64 {
102    size.into() * PB
103}
104
105pub fn pib<V: Into<u64>>(size: V) -> u64 {
106    size.into() * PIB
107}
108
109/// Byte size representation
110#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
111pub struct ByteSize(pub u64);
112
113impl ByteSize {
114    #[inline(always)]
115    pub const fn b(size: u64) -> ByteSize {
116        ByteSize(size)
117    }
118
119    #[inline(always)]
120    pub const fn kb(size: u64) -> ByteSize {
121        ByteSize(size * KB)
122    }
123
124    #[inline(always)]
125    pub const fn kib(size: u64) -> ByteSize {
126        ByteSize(size * KIB)
127    }
128
129    #[inline(always)]
130    pub const fn mb(size: u64) -> ByteSize {
131        ByteSize(size * MB)
132    }
133
134    #[inline(always)]
135    pub const fn mib(size: u64) -> ByteSize {
136        ByteSize(size * MIB)
137    }
138
139    #[inline(always)]
140    pub const fn gb(size: u64) -> ByteSize {
141        ByteSize(size * GB)
142    }
143
144    #[inline(always)]
145    pub const fn gib(size: u64) -> ByteSize {
146        ByteSize(size * GIB)
147    }
148
149    #[inline(always)]
150    pub const fn tb(size: u64) -> ByteSize {
151        ByteSize(size * TB)
152    }
153
154    #[inline(always)]
155    pub const fn tib(size: u64) -> ByteSize {
156        ByteSize(size * TIB)
157    }
158
159    #[inline(always)]
160    pub const fn pb(size: u64) -> ByteSize {
161        ByteSize(size * PB)
162    }
163
164    #[inline(always)]
165    pub const fn pib(size: u64) -> ByteSize {
166        ByteSize(size * PIB)
167    }
168
169    #[inline(always)]
170    pub const fn as_u64(&self) -> u64 {
171        self.0
172    }
173
174    #[inline(always)]
175    pub fn to_string_as(&self, si_unit: bool) -> String {
176        to_string(self.0, si_unit)
177    }
178}
179
180pub fn to_string(bytes: u64, si_prefix: bool) -> String {
181    let unit = if si_prefix { KIB } else { KB };
182    let unit_base = if si_prefix { LN_KIB } else { LN_KB };
183    let unit_prefix = if si_prefix {
184        UNITS_SI.as_bytes()
185    } else {
186        UNITS.as_bytes()
187    };
188    let unit_suffix = if si_prefix { "iB" } else { "B" };
189
190    if bytes < unit {
191        format!("{} B", bytes)
192    } else {
193        let size = bytes as f64;
194        let exp = match (size.ln() / unit_base) as usize {
195            e if e == 0 => 1,
196            e => e,
197        };
198
199        format!(
200            "{:.1} {}{}",
201            (size / unit.pow(exp as u32) as f64),
202            unit_prefix[exp - 1] as char,
203            unit_suffix
204        )
205    }
206}
207
208impl Display for ByteSize {
209    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
210        f.pad(&to_string(self.0, false))
211    }
212}
213
214impl Debug for ByteSize {
215    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
216        write!(f, "{}", self)
217    }
218}
219
220macro_rules! commutative_op {
221    ($t:ty) => {
222        impl Add<ByteSize> for $t {
223            type Output = ByteSize;
224            #[inline(always)]
225            fn add(self, rhs: ByteSize) -> ByteSize {
226                ByteSize(rhs.0 + (self as u64))
227            }
228        }
229
230        impl Mul<ByteSize> for $t {
231            type Output = ByteSize;
232            #[inline(always)]
233            fn mul(self, rhs: ByteSize) -> ByteSize {
234                ByteSize(rhs.0 * (self as u64))
235            }
236        }
237    };
238}
239
240commutative_op!(u64);
241commutative_op!(u32);
242commutative_op!(u16);
243commutative_op!(u8);
244
245impl Add<ByteSize> for ByteSize {
246    type Output = ByteSize;
247
248    #[inline(always)]
249    fn add(self, rhs: ByteSize) -> ByteSize {
250        ByteSize(self.0 + rhs.0)
251    }
252}
253
254impl AddAssign<ByteSize> for ByteSize {
255    #[inline(always)]
256    fn add_assign(&mut self, rhs: ByteSize) {
257        self.0 += rhs.0
258    }
259}
260
261impl<T> Add<T> for ByteSize
262where
263    T: Into<u64>,
264{
265    type Output = ByteSize;
266    #[inline(always)]
267    fn add(self, rhs: T) -> ByteSize {
268        ByteSize(self.0 + (rhs.into() as u64))
269    }
270}
271
272impl<T> AddAssign<T> for ByteSize
273where
274    T: Into<u64>,
275{
276    #[inline(always)]
277    fn add_assign(&mut self, rhs: T) {
278        self.0 += rhs.into() as u64;
279    }
280}
281
282impl<T> Mul<T> for ByteSize
283where
284    T: Into<u64>,
285{
286    type Output = ByteSize;
287    #[inline(always)]
288    fn mul(self, rhs: T) -> ByteSize {
289        ByteSize(self.0 * (rhs.into() as u64))
290    }
291}
292
293impl<T> MulAssign<T> for ByteSize
294where
295    T: Into<u64>,
296{
297    #[inline(always)]
298    fn mul_assign(&mut self, rhs: T) {
299        self.0 *= rhs.into() as u64;
300    }
301}
302
303#[cfg(feature = "serde")]
304impl<'de> Deserialize<'de> for ByteSize {
305    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
306    where
307        D: Deserializer<'de>,
308    {
309        struct ByteSizeVistor;
310
311        impl<'de> de::Visitor<'de> for ByteSizeVistor {
312            type Value = ByteSize;
313
314            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
315                formatter.write_str("an integer or string")
316            }
317
318            fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
319                if let Ok(val) = u64::try_from(value) {
320                    Ok(ByteSize(val))
321                } else {
322                    Err(E::invalid_value(
323                        de::Unexpected::Signed(value),
324                        &"integer overflow",
325                    ))
326                }
327            }
328
329            fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
330                Ok(ByteSize(value))
331            }
332
333            fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
334                if let Ok(val) = value.parse() {
335                    Ok(val)
336                } else {
337                    Err(E::invalid_value(
338                        de::Unexpected::Str(value),
339                        &"parsable string",
340                    ))
341                }
342            }
343        }
344
345        if deserializer.is_human_readable() {
346            deserializer.deserialize_any(ByteSizeVistor)
347        } else {
348            deserializer.deserialize_u64(ByteSizeVistor)
349        }
350    }
351}
352
353#[cfg(feature = "serde")]
354impl Serialize for ByteSize {
355    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
356    where
357        S: Serializer,
358    {
359        if serializer.is_human_readable() {
360            <str>::serialize(self.to_string().as_str(), serializer)
361        } else {
362            self.0.serialize(serializer)
363        }
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_arithmetic_op() {
373        let mut x = ByteSize::mb(1);
374        let y = ByteSize::kb(100);
375
376        assert_eq!((x + y).as_u64(), 1_100_000u64);
377
378        assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
379
380        assert_eq!((x * 2u64).as_u64(), 2_000_000);
381
382        x += y;
383        assert_eq!(x.as_u64(), 1_100_000);
384        x *= 2u64;
385        assert_eq!(x.as_u64(), 2_200_000);
386    }
387
388    #[test]
389    fn test_arithmetic_primitives() {
390        let mut x = ByteSize::mb(1);
391
392        assert_eq!((x + MB as u64).as_u64(), 2_000_000);
393
394        assert_eq!((x + MB as u32).as_u64(), 2_000_000);
395
396        assert_eq!((x + KB as u16).as_u64(), 1_001_000);
397
398        assert_eq!((x + B as u8).as_u64(), 1_000_001);
399
400        x += MB as u64;
401        x += MB as u32;
402        x += 10u16;
403        x += 1u8;
404        assert_eq!(x.as_u64(), 3_000_011);
405    }
406
407    #[test]
408    fn test_comparison() {
409        assert!(ByteSize::mb(1) == ByteSize::kb(1000));
410        assert!(ByteSize::mib(1) == ByteSize::kib(1024));
411        assert!(ByteSize::mb(1) != ByteSize::kib(1024));
412        assert!(ByteSize::mb(1) < ByteSize::kib(1024));
413        assert!(ByteSize::b(0) < ByteSize::tib(1));
414    }
415
416    fn assert_display(expected: &str, b: ByteSize) {
417        assert_eq!(expected, format!("{}", b));
418    }
419
420    #[test]
421    fn test_display() {
422        assert_display("215 B", ByteSize::b(215));
423        assert_display("1.0 KB", ByteSize::kb(1));
424        assert_display("301.0 KB", ByteSize::kb(301));
425        assert_display("419.0 MB", ByteSize::mb(419));
426        assert_display("518.0 GB", ByteSize::gb(518));
427        assert_display("815.0 TB", ByteSize::tb(815));
428        assert_display("609.0 PB", ByteSize::pb(609));
429    }
430
431    #[test]
432    fn test_display_alignment() {
433        assert_eq!("|357 B     |", format!("|{:10}|", ByteSize(357)));
434        assert_eq!("|     357 B|", format!("|{:>10}|", ByteSize(357)));
435        assert_eq!("|357 B     |", format!("|{:<10}|", ByteSize(357)));
436        assert_eq!("|  357 B   |", format!("|{:^10}|", ByteSize(357)));
437
438        assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
439        assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
440        assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
441    }
442
443    fn assert_to_string(expected: &str, b: ByteSize, si: bool) {
444        assert_eq!(expected.to_string(), b.to_string_as(si));
445    }
446
447    #[test]
448    fn test_to_string_as() {
449        assert_to_string("215 B", ByteSize::b(215), true);
450        assert_to_string("215 B", ByteSize::b(215), false);
451
452        assert_to_string("1.0 kiB", ByteSize::kib(1), true);
453        assert_to_string("1.0 KB", ByteSize::kib(1), false);
454
455        assert_to_string("293.9 kiB", ByteSize::kb(301), true);
456        assert_to_string("301.0 KB", ByteSize::kb(301), false);
457
458        assert_to_string("1.0 MiB", ByteSize::mib(1), true);
459        assert_to_string("1048.6 KB", ByteSize::mib(1), false);
460
461        // a bug case: https://github.com/flang-project/bytesize/issues/8
462        assert_to_string("1.9 GiB", ByteSize::mib(1907), true);
463        assert_to_string("2.0 GB", ByteSize::mib(1908), false);
464
465        assert_to_string("399.6 MiB", ByteSize::mb(419), true);
466        assert_to_string("419.0 MB", ByteSize::mb(419), false);
467
468        assert_to_string("482.4 GiB", ByteSize::gb(518), true);
469        assert_to_string("518.0 GB", ByteSize::gb(518), false);
470
471        assert_to_string("741.2 TiB", ByteSize::tb(815), true);
472        assert_to_string("815.0 TB", ByteSize::tb(815), false);
473
474        assert_to_string("540.9 PiB", ByteSize::pb(609), true);
475        assert_to_string("609.0 PB", ByteSize::pb(609), false);
476    }
477
478    #[test]
479    fn test_default() {
480        assert_eq!(ByteSize::b(0), ByteSize::default());
481    }
482
483    #[test]
484    fn test_to_string() {
485        assert_to_string("609.0 PB", ByteSize::pb(609), false);
486    }
487
488    #[cfg(feature = "serde")]
489    #[test]
490    fn test_serde() {
491        #[derive(Serialize, Deserialize)]
492        struct S {
493            x: ByteSize,
494        }
495
496        let s: S = serde_json::from_str(r#"{ "x": "5 B" }"#).unwrap();
497        assert_eq!(s.x, ByteSize(5));
498
499        let s: S = serde_json::from_str(r#"{ "x": 1048576 }"#).unwrap();
500        assert_eq!(s.x, "1 MiB".parse::<ByteSize>().unwrap());
501
502        let s: S = toml::from_str(r#"x = "2.5 MiB""#).unwrap();
503        assert_eq!(s.x, "2.5 MiB".parse::<ByteSize>().unwrap());
504
505        // i64 MAX
506        let s: S = toml::from_str(r#"x = "9223372036854775807""#).unwrap();
507        assert_eq!(s.x, "9223372036854775807".parse::<ByteSize>().unwrap());
508    }
509}