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