1#![cfg_attr(not(feature = "std"), no_std)]
45
46extern crate alloc;
47
48use alloc::string::ToString as _;
49use core::{fmt, iter, ops};
50
51#[cfg(feature = "arbitrary")]
52mod arbitrary;
53mod display;
54mod parse;
55#[cfg(feature = "serde")]
56mod serde;
57
58pub use self::display::Display;
59use self::display::Format;
60pub use self::parse::{Unit, UnitParseError};
61
62pub const KB: u64 = 1_000;
64pub const MB: u64 = 1_000_000;
66pub const GB: u64 = 1_000_000_000;
68pub const TB: u64 = 1_000_000_000_000;
70pub const PB: u64 = 1_000_000_000_000_000;
72pub const EB: u64 = 1_000_000_000_000_000_000;
74
75pub const KIB: u64 = 1_024;
77pub const MIB: u64 = 1_048_576;
79pub const GIB: u64 = 1_073_741_824;
81pub const TIB: u64 = 1_099_511_627_776;
83pub const PIB: u64 = 1_125_899_906_842_624;
85pub const EIB: u64 = 1_152_921_504_606_846_976;
87
88const UNITS_IEC: &str = "KMGTPE";
92
93const UNITS_SI: &str = "kMGTPE";
97
98const LN_KIB: f64 = 6.931_471_805_599_453;
100
101const LN_KB: f64 = 6.907_755_278_982_137;
103
104pub fn kb(size: impl Into<u64>) -> u64 {
106 size.into() * KB
107}
108
109pub fn kib<V: Into<u64>>(size: V) -> u64 {
111 size.into() * KIB
112}
113
114pub fn mb<V: Into<u64>>(size: V) -> u64 {
116 size.into() * MB
117}
118
119pub fn mib<V: Into<u64>>(size: V) -> u64 {
121 size.into() * MIB
122}
123
124pub fn gb<V: Into<u64>>(size: V) -> u64 {
126 size.into() * GB
127}
128
129pub fn gib<V: Into<u64>>(size: V) -> u64 {
131 size.into() * GIB
132}
133
134pub fn tb<V: Into<u64>>(size: V) -> u64 {
136 size.into() * TB
137}
138
139pub fn tib<V: Into<u64>>(size: V) -> u64 {
141 size.into() * TIB
142}
143
144pub fn pb<V: Into<u64>>(size: V) -> u64 {
146 size.into() * PB
147}
148
149pub fn pib<V: Into<u64>>(size: V) -> u64 {
151 size.into() * PIB
152}
153
154pub fn eb<V: Into<u64>>(size: V) -> u64 {
156 size.into() * EB
157}
158
159pub fn eib<V: Into<u64>>(size: V) -> u64 {
161 size.into() * EIB
162}
163
164#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
166pub struct ByteSize(pub u64);
167
168impl ByteSize {
169 #[inline(always)]
171 pub const fn b(size: u64) -> ByteSize {
172 ByteSize(size)
173 }
174
175 #[inline(always)]
177 pub const fn kb(size: u64) -> ByteSize {
178 ByteSize(size * KB)
179 }
180
181 #[inline(always)]
183 pub const fn kib(size: u64) -> ByteSize {
184 ByteSize(size * KIB)
185 }
186
187 #[inline(always)]
189 pub const fn mb(size: u64) -> ByteSize {
190 ByteSize(size * MB)
191 }
192
193 #[inline(always)]
195 pub const fn mib(size: u64) -> ByteSize {
196 ByteSize(size * MIB)
197 }
198
199 #[inline(always)]
201 pub const fn gb(size: u64) -> ByteSize {
202 ByteSize(size * GB)
203 }
204
205 #[inline(always)]
207 pub const fn gib(size: u64) -> ByteSize {
208 ByteSize(size * GIB)
209 }
210
211 #[inline(always)]
213 pub const fn tb(size: u64) -> ByteSize {
214 ByteSize(size * TB)
215 }
216
217 #[inline(always)]
219 pub const fn tib(size: u64) -> ByteSize {
220 ByteSize(size * TIB)
221 }
222
223 #[inline(always)]
225 pub const fn pb(size: u64) -> ByteSize {
226 ByteSize(size * PB)
227 }
228
229 #[inline(always)]
231 pub const fn pib(size: u64) -> ByteSize {
232 ByteSize(size * PIB)
233 }
234
235 #[inline(always)]
237 pub const fn eb(size: u64) -> ByteSize {
238 ByteSize(size * EB)
239 }
240
241 #[inline(always)]
243 pub const fn eib(size: u64) -> ByteSize {
244 ByteSize(size * EIB)
245 }
246
247 #[inline(always)]
249 pub const fn as_u64(&self) -> u64 {
250 self.0
251 }
252
253 #[inline(always)]
255 pub fn as_kb(&self) -> f64 {
256 self.0 as f64 / KB as f64
257 }
258
259 #[inline(always)]
261 pub fn as_kib(&self) -> f64 {
262 self.0 as f64 / KIB as f64
263 }
264
265 #[inline(always)]
267 pub fn as_mb(&self) -> f64 {
268 self.0 as f64 / MB as f64
269 }
270
271 #[inline(always)]
273 pub fn as_mib(&self) -> f64 {
274 self.0 as f64 / MIB as f64
275 }
276
277 #[inline(always)]
279 pub fn as_gb(&self) -> f64 {
280 self.0 as f64 / GB as f64
281 }
282
283 #[inline(always)]
285 pub fn as_gib(&self) -> f64 {
286 self.0 as f64 / GIB as f64
287 }
288
289 #[inline(always)]
291 pub fn as_tb(&self) -> f64 {
292 self.0 as f64 / TB as f64
293 }
294
295 #[inline(always)]
297 pub fn as_tib(&self) -> f64 {
298 self.0 as f64 / TIB as f64
299 }
300
301 #[inline(always)]
303 pub fn as_pb(&self) -> f64 {
304 self.0 as f64 / PB as f64
305 }
306
307 #[inline(always)]
309 pub fn as_pib(&self) -> f64 {
310 self.0 as f64 / PIB as f64
311 }
312
313 #[inline(always)]
315 pub fn as_eb(&self) -> f64 {
316 self.0 as f64 / EB as f64
317 }
318
319 #[inline(always)]
321 pub fn as_eib(&self) -> f64 {
322 self.0 as f64 / EIB as f64
323 }
324
325 pub fn display(&self) -> Display {
327 Display {
328 byte_size: *self,
329 format: Format::Iec,
330 }
331 }
332}
333
334impl fmt::Display for ByteSize {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 let display = self.display();
337
338 if f.width().is_none() {
339 fmt::Display::fmt(&display, f)
341 } else {
342 f.pad(&display.to_string())
343 }
344 }
345}
346
347impl fmt::Debug for ByteSize {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 write!(f, "{} ({} bytes)", self, self.0)
350 }
351}
352
353macro_rules! commutative_op {
354 ($t:ty) => {
355 impl ops::Add<ByteSize> for $t {
356 type Output = ByteSize;
357 #[inline(always)]
358 fn add(self, rhs: ByteSize) -> ByteSize {
359 ByteSize(rhs.0 + (self as u64))
360 }
361 }
362
363 impl ops::Mul<ByteSize> for $t {
364 type Output = ByteSize;
365 #[inline(always)]
366 fn mul(self, rhs: ByteSize) -> ByteSize {
367 ByteSize(rhs.0 * (self as u64))
368 }
369 }
370 };
371}
372
373commutative_op!(u64);
374commutative_op!(u32);
375commutative_op!(u16);
376commutative_op!(u8);
377
378impl ops::Add<ByteSize> for ByteSize {
379 type Output = ByteSize;
380
381 #[inline(always)]
382 fn add(self, rhs: ByteSize) -> ByteSize {
383 ByteSize(self.0 + rhs.0)
384 }
385}
386
387impl ops::AddAssign<ByteSize> for ByteSize {
388 #[inline(always)]
389 fn add_assign(&mut self, rhs: ByteSize) {
390 self.0 += rhs.0
391 }
392}
393
394impl iter::Sum<ByteSize> for ByteSize {
395 fn sum<I>(iter: I) -> Self
396 where
397 I: Iterator<Item = ByteSize>,
398 {
399 iter.fold(Self::default(), ops::Add::add)
400 }
401}
402
403impl<'a> iter::Sum<&'a ByteSize> for ByteSize {
404 fn sum<I>(iter: I) -> Self
405 where
406 I: Iterator<Item = &'a ByteSize>,
407 {
408 iter.copied().sum()
409 }
410}
411
412impl<T> ops::Add<T> for ByteSize
413where
414 T: Into<u64>,
415{
416 type Output = ByteSize;
417 #[inline(always)]
418 fn add(self, rhs: T) -> ByteSize {
419 ByteSize(self.0 + (rhs.into()))
420 }
421}
422
423impl<T> ops::AddAssign<T> for ByteSize
424where
425 T: Into<u64>,
426{
427 #[inline(always)]
428 fn add_assign(&mut self, rhs: T) {
429 self.0 += rhs.into();
430 }
431}
432
433impl ops::Sub<ByteSize> for ByteSize {
434 type Output = ByteSize;
435
436 #[inline(always)]
437 fn sub(self, rhs: ByteSize) -> ByteSize {
438 ByteSize(self.0 - rhs.0)
439 }
440}
441
442impl ops::SubAssign<ByteSize> for ByteSize {
443 #[inline(always)]
444 fn sub_assign(&mut self, rhs: ByteSize) {
445 self.0 -= rhs.0
446 }
447}
448
449impl<T> ops::Sub<T> for ByteSize
450where
451 T: Into<u64>,
452{
453 type Output = ByteSize;
454 #[inline(always)]
455 fn sub(self, rhs: T) -> ByteSize {
456 ByteSize(self.0 - (rhs.into()))
457 }
458}
459
460impl<T> ops::SubAssign<T> for ByteSize
461where
462 T: Into<u64>,
463{
464 #[inline(always)]
465 fn sub_assign(&mut self, rhs: T) {
466 self.0 -= rhs.into();
467 }
468}
469
470impl<T> ops::Mul<T> for ByteSize
471where
472 T: Into<u64>,
473{
474 type Output = ByteSize;
475 #[inline(always)]
476 fn mul(self, rhs: T) -> ByteSize {
477 ByteSize(self.0 * rhs.into())
478 }
479}
480
481impl<T> ops::MulAssign<T> for ByteSize
482where
483 T: Into<u64>,
484{
485 #[inline(always)]
486 fn mul_assign(&mut self, rhs: T) {
487 self.0 *= rhs.into();
488 }
489}
490
491#[cfg(test)]
492mod property_tests {
493 use alloc::string::{String, ToString as _};
494
495 use super::*;
496
497 impl quickcheck::Arbitrary for ByteSize {
498 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
499 Self(u64::arbitrary(g))
500 }
501 }
502
503 quickcheck::quickcheck! {
504 fn parsing_never_panics(size: String) -> bool {
505 let _ = size.parse::<ByteSize>();
506 true
507 }
508
509 fn to_string_never_blank(size: ByteSize) -> bool {
510 !size.to_string().is_empty()
511 }
512
513 fn to_string_never_large(size: ByteSize) -> bool {
514 size.to_string().len() < 11
515 }
516
517 fn string_round_trip(size: ByteSize) -> bool {
518 if size > ByteSize::pib(1) {
520 return true;
521 }
522
523 size.to_string().parse::<ByteSize>().unwrap() == size
524 }
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use alloc::format;
531
532 use super::*;
533
534 #[test]
535 fn test_arithmetic_op() {
536 let mut x = ByteSize::mb(1);
537 let y = ByteSize::kb(100);
538
539 assert_eq!((x + y).as_u64(), 1_100_000u64);
540
541 assert_eq!((x - y).as_u64(), 900_000u64);
542
543 assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
544
545 assert_eq!((x * 2u64).as_u64(), 2_000_000);
546
547 x += y;
548 assert_eq!(x.as_u64(), 1_100_000);
549 x *= 2u64;
550 assert_eq!(x.as_u64(), 2_200_000);
551 }
552
553 #[allow(clippy::unnecessary_cast)]
554 #[test]
555 fn test_arithmetic_primitives() {
556 let mut x = ByteSize::mb(1);
557
558 assert_eq!((x + MB as u64).as_u64(), 2_000_000);
559 assert_eq!((x + MB as u32).as_u64(), 2_000_000);
560 assert_eq!((x + KB as u16).as_u64(), 1_001_000);
561 assert_eq!((x - MB as u64).as_u64(), 0);
562 assert_eq!((x - MB as u32).as_u64(), 0);
563 assert_eq!((x - KB as u32).as_u64(), 999_000);
564
565 x += MB as u64;
566 x += MB as u32;
567 x += 10u16;
568 x += 1u8;
569 assert_eq!(x.as_u64(), 3_000_011);
570 }
571
572 #[test]
573 fn test_sum() {
574 let sizes = [ByteSize::kb(1), ByteSize::mb(1), ByteSize::mib(1)];
575
576 assert_eq!(
577 sizes.into_iter().sum::<ByteSize>(),
578 ByteSize::b(KB + MB + MIB)
579 );
580 assert_eq!(sizes.iter().sum::<ByteSize>(), ByteSize::b(KB + MB + MIB));
581 assert_eq!(
582 core::iter::empty::<ByteSize>().sum::<ByteSize>(),
583 ByteSize::b(0)
584 );
585 }
586
587 #[test]
588 fn test_comparison() {
589 assert!(ByteSize::mb(1) == ByteSize::kb(1000));
590 assert!(ByteSize::mib(1) == ByteSize::kib(1024));
591 assert!(ByteSize::mb(1) != ByteSize::kib(1024));
592 assert!(ByteSize::mb(1) < ByteSize::kib(1024));
593 assert!(ByteSize::b(0) < ByteSize::tib(1));
594 assert!(ByteSize::pib(1) < ByteSize::eb(1));
595 }
596
597 #[test]
598 fn as_unit_conversions() {
599 assert_eq!(41992187.5, ByteSize::gb(43).as_kib());
600 assert_eq!(0.028311552, ByteSize::mib(27).as_gb());
601 assert_eq!(0.0380859375, ByteSize::tib(39).as_pib());
602 assert_eq!(961.482752, ByteSize::kib(938948).as_mb());
603 assert_eq!(4.195428726649908, ByteSize::pb(4837).as_eib());
604 assert_eq!(2.613772153284117, ByteSize::b(2873872874893).as_tib());
605 }
606
607 #[track_caller]
608 fn assert_display(expected: &str, b: ByteSize) {
609 assert_eq!(expected, format!("{b}"));
610 }
611
612 #[test]
613 fn test_display() {
614 assert_display("215 B", ByteSize::b(215));
615 assert_display("1.0 KiB", ByteSize::kib(1));
616 assert_display("301.0 KiB", ByteSize::kib(301));
617 assert_display("419.0 MiB", ByteSize::mib(419));
618 assert_display("518.0 GiB", ByteSize::gib(518));
619 assert_display("815.0 TiB", ByteSize::tib(815));
620 assert_display("609.0 PiB", ByteSize::pib(609));
621 assert_display("15.0 EiB", ByteSize::eib(15));
622 }
623
624 #[test]
625 fn test_display_alignment() {
626 assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357)));
627 assert_eq!("| 357 B|", format!("|{:>10}|", ByteSize(357)));
628 assert_eq!("|357 B |", format!("|{:<10}|", ByteSize(357)));
629 assert_eq!("| 357 B |", format!("|{:^10}|", ByteSize(357)));
630
631 assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
632 assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
633 assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
634 }
635
636 #[test]
637 fn test_default() {
638 assert_eq!(ByteSize::b(0), ByteSize::default());
639 }
640}