1#![cfg_attr(not(feature = "std"), no_std)]
47
48extern crate alloc;
49
50use alloc::string::ToString as _;
51use core::{fmt, iter, ops};
52
53#[cfg(feature = "arbitrary")]
54mod arbitrary;
55mod display;
56mod parse;
57#[cfg(feature = "serde")]
58mod serde;
59
60pub use self::display::Display;
61use self::display::Format;
62pub use self::parse::{Unit, UnitParseError};
63
64pub const KB: u64 = 1_000;
66pub const MB: u64 = 1_000_000;
68pub const GB: u64 = 1_000_000_000;
70pub const TB: u64 = 1_000_000_000_000;
72pub const PB: u64 = 1_000_000_000_000_000;
74pub const EB: u64 = 1_000_000_000_000_000_000;
76
77pub const KIB: u64 = 1_024;
79pub const MIB: u64 = 1_048_576;
81pub const GIB: u64 = 1_073_741_824;
83pub const TIB: u64 = 1_099_511_627_776;
85pub const PIB: u64 = 1_125_899_906_842_624;
87pub const EIB: u64 = 1_152_921_504_606_846_976;
89
90const UNITS_IEC: &str = "KMGTPE";
94
95const UNITS_SI: &str = "kMGTPE";
99
100const LN_KIB: f64 = 6.931_471_805_599_453;
102
103const LN_KB: f64 = 6.907_755_278_982_137;
105
106pub fn kb(size: impl Into<u64>) -> u64 {
108 size.into() * KB
109}
110
111pub fn kib<V: Into<u64>>(size: V) -> u64 {
113 size.into() * KIB
114}
115
116pub fn mb<V: Into<u64>>(size: V) -> u64 {
118 size.into() * MB
119}
120
121pub fn mib<V: Into<u64>>(size: V) -> u64 {
123 size.into() * MIB
124}
125
126pub fn gb<V: Into<u64>>(size: V) -> u64 {
128 size.into() * GB
129}
130
131pub fn gib<V: Into<u64>>(size: V) -> u64 {
133 size.into() * GIB
134}
135
136pub fn tb<V: Into<u64>>(size: V) -> u64 {
138 size.into() * TB
139}
140
141pub fn tib<V: Into<u64>>(size: V) -> u64 {
143 size.into() * TIB
144}
145
146pub fn pb<V: Into<u64>>(size: V) -> u64 {
148 size.into() * PB
149}
150
151pub fn pib<V: Into<u64>>(size: V) -> u64 {
153 size.into() * PIB
154}
155
156pub fn eb<V: Into<u64>>(size: V) -> u64 {
158 size.into() * EB
159}
160
161pub fn eib<V: Into<u64>>(size: V) -> u64 {
163 size.into() * EIB
164}
165
166#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
168pub struct ByteSize(pub u64);
169
170impl ByteSize {
171 #[inline(always)]
173 pub const fn b(size: u64) -> ByteSize {
174 ByteSize(size)
175 }
176
177 #[inline(always)]
179 pub const fn kb(size: u64) -> ByteSize {
180 ByteSize(size * KB)
181 }
182
183 #[inline(always)]
185 pub const fn kib(size: u64) -> ByteSize {
186 ByteSize(size * KIB)
187 }
188
189 #[inline(always)]
191 pub const fn mb(size: u64) -> ByteSize {
192 ByteSize(size * MB)
193 }
194
195 #[inline(always)]
197 pub const fn mib(size: u64) -> ByteSize {
198 ByteSize(size * MIB)
199 }
200
201 #[inline(always)]
203 pub const fn gb(size: u64) -> ByteSize {
204 ByteSize(size * GB)
205 }
206
207 #[inline(always)]
209 pub const fn gib(size: u64) -> ByteSize {
210 ByteSize(size * GIB)
211 }
212
213 #[inline(always)]
215 pub const fn tb(size: u64) -> ByteSize {
216 ByteSize(size * TB)
217 }
218
219 #[inline(always)]
221 pub const fn tib(size: u64) -> ByteSize {
222 ByteSize(size * TIB)
223 }
224
225 #[inline(always)]
227 pub const fn pb(size: u64) -> ByteSize {
228 ByteSize(size * PB)
229 }
230
231 #[inline(always)]
233 pub const fn pib(size: u64) -> ByteSize {
234 ByteSize(size * PIB)
235 }
236
237 #[inline(always)]
239 pub const fn eb(size: u64) -> ByteSize {
240 ByteSize(size * EB)
241 }
242
243 #[inline(always)]
245 pub const fn eib(size: u64) -> ByteSize {
246 ByteSize(size * EIB)
247 }
248
249 #[inline(always)]
251 pub const fn as_u64(&self) -> u64 {
252 self.0
253 }
254
255 #[inline(always)]
257 pub fn as_kb(&self) -> f64 {
258 self.0 as f64 / KB as f64
259 }
260
261 #[inline(always)]
263 pub fn as_kib(&self) -> f64 {
264 self.0 as f64 / KIB as f64
265 }
266
267 #[inline(always)]
269 pub fn as_mb(&self) -> f64 {
270 self.0 as f64 / MB as f64
271 }
272
273 #[inline(always)]
275 pub fn as_mib(&self) -> f64 {
276 self.0 as f64 / MIB as f64
277 }
278
279 #[inline(always)]
281 pub fn as_gb(&self) -> f64 {
282 self.0 as f64 / GB as f64
283 }
284
285 #[inline(always)]
287 pub fn as_gib(&self) -> f64 {
288 self.0 as f64 / GIB as f64
289 }
290
291 #[inline(always)]
293 pub fn as_tb(&self) -> f64 {
294 self.0 as f64 / TB as f64
295 }
296
297 #[inline(always)]
299 pub fn as_tib(&self) -> f64 {
300 self.0 as f64 / TIB as f64
301 }
302
303 #[inline(always)]
305 pub fn as_pb(&self) -> f64 {
306 self.0 as f64 / PB as f64
307 }
308
309 #[inline(always)]
311 pub fn as_pib(&self) -> f64 {
312 self.0 as f64 / PIB as f64
313 }
314
315 #[inline(always)]
317 pub fn as_eb(&self) -> f64 {
318 self.0 as f64 / EB as f64
319 }
320
321 #[inline(always)]
323 pub fn as_eib(&self) -> f64 {
324 self.0 as f64 / EIB as f64
325 }
326
327 pub fn display(&self) -> Display {
329 Display {
330 byte_size: *self,
331 format: Format::Iec,
332 }
333 }
334}
335
336impl fmt::Display for ByteSize {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 let display = self.display();
339
340 if f.width().is_none() {
341 fmt::Display::fmt(&display, f)
343 } else {
344 let content = match f.precision() {
350 Some(precision) => alloc::format!("{display:.precision$}"),
351 None => display.to_string(),
352 };
353
354 let padding = f
355 .width()
356 .unwrap_or(0)
357 .saturating_sub(content.chars().count());
358 if padding == 0 {
359 return f.write_str(&content);
360 }
361
362 let (left, right) = match f.align() {
363 Some(fmt::Alignment::Right) => (padding, 0),
364 Some(fmt::Alignment::Center) => (padding / 2, padding - padding / 2),
365 Some(fmt::Alignment::Left) | None => (0, padding),
366 };
367
368 let mut buf = [0u8; 4];
369 let fill = f.fill().encode_utf8(&mut buf);
370 for _ in 0..left {
371 f.write_str(fill)?;
372 }
373 f.write_str(&content)?;
374 for _ in 0..right {
375 f.write_str(fill)?;
376 }
377 Ok(())
378 }
379 }
380}
381
382impl fmt::Debug for ByteSize {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 write!(f, "{} ({} bytes)", self, self.0)
385 }
386}
387
388macro_rules! commutative_op {
389 ($t:ty) => {
390 impl ops::Add<ByteSize> for $t {
391 type Output = ByteSize;
392 #[inline(always)]
393 fn add(self, rhs: ByteSize) -> ByteSize {
394 ByteSize(rhs.0 + (self as u64))
395 }
396 }
397
398 impl ops::Mul<ByteSize> for $t {
399 type Output = ByteSize;
400 #[inline(always)]
401 fn mul(self, rhs: ByteSize) -> ByteSize {
402 ByteSize(rhs.0 * (self as u64))
403 }
404 }
405 };
406}
407
408commutative_op!(u64);
409commutative_op!(u32);
410commutative_op!(u16);
411commutative_op!(u8);
412
413impl ops::Add<ByteSize> for ByteSize {
414 type Output = ByteSize;
415
416 #[inline(always)]
417 fn add(self, rhs: ByteSize) -> ByteSize {
418 ByteSize(self.0 + rhs.0)
419 }
420}
421
422impl ops::AddAssign<ByteSize> for ByteSize {
423 #[inline(always)]
424 fn add_assign(&mut self, rhs: ByteSize) {
425 self.0 += rhs.0
426 }
427}
428
429impl iter::Sum<ByteSize> for ByteSize {
430 fn sum<I>(iter: I) -> Self
431 where
432 I: Iterator<Item = ByteSize>,
433 {
434 iter.fold(Self::default(), ops::Add::add)
435 }
436}
437
438impl<'a> iter::Sum<&'a ByteSize> for ByteSize {
439 fn sum<I>(iter: I) -> Self
440 where
441 I: Iterator<Item = &'a ByteSize>,
442 {
443 iter.copied().sum()
444 }
445}
446
447impl<T> ops::Add<T> for ByteSize
448where
449 T: Into<u64>,
450{
451 type Output = ByteSize;
452 #[inline(always)]
453 fn add(self, rhs: T) -> ByteSize {
454 ByteSize(self.0 + (rhs.into()))
455 }
456}
457
458impl<T> ops::AddAssign<T> for ByteSize
459where
460 T: Into<u64>,
461{
462 #[inline(always)]
463 fn add_assign(&mut self, rhs: T) {
464 self.0 += rhs.into();
465 }
466}
467
468impl ops::Sub<ByteSize> for ByteSize {
469 type Output = ByteSize;
470
471 #[inline(always)]
472 fn sub(self, rhs: ByteSize) -> ByteSize {
473 ByteSize(self.0 - rhs.0)
474 }
475}
476
477impl ops::SubAssign<ByteSize> for ByteSize {
478 #[inline(always)]
479 fn sub_assign(&mut self, rhs: ByteSize) {
480 self.0 -= rhs.0
481 }
482}
483
484impl<T> ops::Sub<T> for ByteSize
485where
486 T: Into<u64>,
487{
488 type Output = ByteSize;
489 #[inline(always)]
490 fn sub(self, rhs: T) -> ByteSize {
491 ByteSize(self.0 - (rhs.into()))
492 }
493}
494
495impl<T> ops::SubAssign<T> for ByteSize
496where
497 T: Into<u64>,
498{
499 #[inline(always)]
500 fn sub_assign(&mut self, rhs: T) {
501 self.0 -= rhs.into();
502 }
503}
504
505impl<T> ops::Mul<T> for ByteSize
506where
507 T: Into<u64>,
508{
509 type Output = ByteSize;
510 #[inline(always)]
511 fn mul(self, rhs: T) -> ByteSize {
512 ByteSize(self.0 * rhs.into())
513 }
514}
515
516impl<T> ops::MulAssign<T> for ByteSize
517where
518 T: Into<u64>,
519{
520 #[inline(always)]
521 fn mul_assign(&mut self, rhs: T) {
522 self.0 *= rhs.into();
523 }
524}
525
526#[cfg(test)]
527mod core_tests {
528 use super::*;
529
530 #[test]
531 fn test_arithmetic_op() {
532 let mut x = ByteSize::mb(1);
533 let y = ByteSize::kb(100);
534
535 assert_eq!((x + y).as_u64(), 1_100_000u64);
536
537 assert_eq!((x - y).as_u64(), 900_000u64);
538
539 assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
540
541 assert_eq!((x * 2u64).as_u64(), 2_000_000);
542
543 x += y;
544 assert_eq!(x.as_u64(), 1_100_000);
545 x *= 2u64;
546 assert_eq!(x.as_u64(), 2_200_000);
547 }
548
549 #[allow(clippy::unnecessary_cast)]
550 #[test]
551 fn test_arithmetic_primitives() {
552 let mut x = ByteSize::mb(1);
553
554 assert_eq!((x + MB as u64).as_u64(), 2_000_000);
555 assert_eq!((x + MB as u32).as_u64(), 2_000_000);
556 assert_eq!((x + KB as u16).as_u64(), 1_001_000);
557 assert_eq!((x - MB as u64).as_u64(), 0);
558 assert_eq!((x - MB as u32).as_u64(), 0);
559 assert_eq!((x - KB as u32).as_u64(), 999_000);
560
561 x += MB as u64;
562 x += MB as u32;
563 x += 10u16;
564 x += 1u8;
565 assert_eq!(x.as_u64(), 3_000_011);
566 }
567
568 #[test]
569 fn test_sum() {
570 let sizes = [ByteSize::kb(1), ByteSize::mb(1), ByteSize::mib(1)];
571
572 assert_eq!(
573 sizes.into_iter().sum::<ByteSize>(),
574 ByteSize::b(KB + MB + MIB)
575 );
576 assert_eq!(sizes.iter().sum::<ByteSize>(), ByteSize::b(KB + MB + MIB));
577 assert_eq!(
578 core::iter::empty::<ByteSize>().sum::<ByteSize>(),
579 ByteSize::b(0)
580 );
581 }
582
583 #[test]
584 fn test_comparison() {
585 assert!(ByteSize::mb(1) == ByteSize::kb(1000));
586 assert!(ByteSize::mib(1) == ByteSize::kib(1024));
587 assert!(ByteSize::mb(1) != ByteSize::kib(1024));
588 assert!(ByteSize::mb(1) < ByteSize::kib(1024));
589 assert!(ByteSize::b(0) < ByteSize::tib(1));
590 assert!(ByteSize::pib(1) < ByteSize::eb(1));
591 }
592
593 #[test]
594 fn as_unit_conversions() {
595 assert_eq!(41992187.5, ByteSize::gb(43).as_kib());
596 assert_eq!(0.028311552, ByteSize::mib(27).as_gb());
597 assert_eq!(0.0380859375, ByteSize::tib(39).as_pib());
598 assert_eq!(961.482752, ByteSize::kib(938948).as_mb());
599 assert_eq!(4.195428726649908, ByteSize::pb(4837).as_eib());
600 assert_eq!(2.613772153284117, ByteSize::b(2873872874893).as_tib());
601 }
602
603 #[test]
604 fn test_default() {
605 assert_eq!(ByteSize::b(0), ByteSize::default());
606 }
607}
608
609#[cfg(test)]
610mod alloc_tests {
611 use alloc::{format, string::String};
612
613 use super::*;
614
615 impl quickcheck::Arbitrary for ByteSize {
616 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
617 Self(u64::arbitrary(g))
618 }
619 }
620
621 quickcheck::quickcheck! {
622 fn parsing_never_panics(size: String) -> bool {
623 let _ = size.parse::<ByteSize>();
624 true
625 }
626
627 fn to_string_never_blank(size: ByteSize) -> bool {
628 !size.to_string().is_empty()
629 }
630
631 fn to_string_never_large(size: ByteSize) -> bool {
632 size.to_string().len() < 11
633 }
634
635 fn string_round_trip(size: ByteSize) -> bool {
636 if size > ByteSize::pib(1) {
638 return true;
639 }
640
641 size.to_string().parse::<ByteSize>().unwrap() == size
642 }
643 }
644
645 #[track_caller]
646 fn assert_display(expected: &str, b: ByteSize) {
647 assert_eq!(expected, format!("{b}"));
648 }
649
650 #[test]
651 fn test_display() {
652 assert_display("215 B", ByteSize::b(215));
653 assert_display("1.0 KiB", ByteSize::kib(1));
654 assert_display("301.0 KiB", ByteSize::kib(301));
655 assert_display("419.0 MiB", ByteSize::mib(419));
656 assert_display("518.0 GiB", ByteSize::gib(518));
657 assert_display("815.0 TiB", ByteSize::tib(815));
658 assert_display("609.0 PiB", ByteSize::pib(609));
659 assert_display("15.0 EiB", ByteSize::eib(15));
660 }
661
662 #[test]
663 fn test_display_alignment() {
664 assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357)));
665 assert_eq!("| 357 B|", format!("|{:>10}|", ByteSize(357)));
666 assert_eq!("|357 B |", format!("|{:<10}|", ByteSize(357)));
667 assert_eq!("| 357 B |", format!("|{:^10}|", ByteSize(357)));
668
669 assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
670 assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
671 assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
672 }
673 #[test]
674 fn test_display_width_with_precision() {
675 let size = ByteSize::mib(1908);
676 assert_eq!("| 1.86328 GiB|", format!("|{size:>12.5}|"));
679 assert_eq!("|1.86328 GiB |", format!("|{size:<12.5}|"));
680 assert_eq!("|1.86328 GiB |", format!("|{size:12.5}|"));
681 assert_eq!("|--1.86328 GiB--|", format!("|{size:-^15.5}|"));
682 assert_eq!("| 2 GiB|", format!("|{size:>10.0}|"));
683 assert_eq!("1.86328 GiB", format!("{size:3.5}"));
685 }
686}