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