1#![cfg_attr(not(feature = "std"), no_std)]
45
46extern crate alloc;
47
48use alloc::string::ToString as _;
49use core::{fmt, 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<T> ops::Add<T> for ByteSize
395where
396 T: Into<u64>,
397{
398 type Output = ByteSize;
399 #[inline(always)]
400 fn add(self, rhs: T) -> ByteSize {
401 ByteSize(self.0 + (rhs.into()))
402 }
403}
404
405impl<T> ops::AddAssign<T> for ByteSize
406where
407 T: Into<u64>,
408{
409 #[inline(always)]
410 fn add_assign(&mut self, rhs: T) {
411 self.0 += rhs.into();
412 }
413}
414
415impl ops::Sub<ByteSize> for ByteSize {
416 type Output = ByteSize;
417
418 #[inline(always)]
419 fn sub(self, rhs: ByteSize) -> ByteSize {
420 ByteSize(self.0 - rhs.0)
421 }
422}
423
424impl ops::SubAssign<ByteSize> for ByteSize {
425 #[inline(always)]
426 fn sub_assign(&mut self, rhs: ByteSize) {
427 self.0 -= rhs.0
428 }
429}
430
431impl<T> ops::Sub<T> for ByteSize
432where
433 T: Into<u64>,
434{
435 type Output = ByteSize;
436 #[inline(always)]
437 fn sub(self, rhs: T) -> ByteSize {
438 ByteSize(self.0 - (rhs.into()))
439 }
440}
441
442impl<T> ops::SubAssign<T> for ByteSize
443where
444 T: Into<u64>,
445{
446 #[inline(always)]
447 fn sub_assign(&mut self, rhs: T) {
448 self.0 -= rhs.into();
449 }
450}
451
452impl<T> ops::Mul<T> for ByteSize
453where
454 T: Into<u64>,
455{
456 type Output = ByteSize;
457 #[inline(always)]
458 fn mul(self, rhs: T) -> ByteSize {
459 ByteSize(self.0 * rhs.into())
460 }
461}
462
463impl<T> ops::MulAssign<T> for ByteSize
464where
465 T: Into<u64>,
466{
467 #[inline(always)]
468 fn mul_assign(&mut self, rhs: T) {
469 self.0 *= rhs.into();
470 }
471}
472
473#[cfg(test)]
474mod property_tests {
475 use alloc::string::{String, ToString as _};
476
477 use super::*;
478
479 impl quickcheck::Arbitrary for ByteSize {
480 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
481 Self(u64::arbitrary(g))
482 }
483 }
484
485 quickcheck::quickcheck! {
486 fn parsing_never_panics(size: String) -> bool {
487 let _ = size.parse::<ByteSize>();
488 true
489 }
490
491 fn to_string_never_blank(size: ByteSize) -> bool {
492 !size.to_string().is_empty()
493 }
494
495 fn to_string_never_large(size: ByteSize) -> bool {
496 size.to_string().len() < 11
497 }
498
499 fn string_round_trip(size: ByteSize) -> bool {
500 if size > ByteSize::pib(1) {
502 return true;
503 }
504
505 size.to_string().parse::<ByteSize>().unwrap() == size
506 }
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use alloc::format;
513
514 use super::*;
515
516 #[test]
517 fn test_arithmetic_op() {
518 let mut x = ByteSize::mb(1);
519 let y = ByteSize::kb(100);
520
521 assert_eq!((x + y).as_u64(), 1_100_000u64);
522
523 assert_eq!((x - y).as_u64(), 900_000u64);
524
525 assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
526
527 assert_eq!((x * 2u64).as_u64(), 2_000_000);
528
529 x += y;
530 assert_eq!(x.as_u64(), 1_100_000);
531 x *= 2u64;
532 assert_eq!(x.as_u64(), 2_200_000);
533 }
534
535 #[allow(clippy::unnecessary_cast)]
536 #[test]
537 fn test_arithmetic_primitives() {
538 let mut x = ByteSize::mb(1);
539
540 assert_eq!((x + MB as u64).as_u64(), 2_000_000);
541 assert_eq!((x + MB as u32).as_u64(), 2_000_000);
542 assert_eq!((x + KB as u16).as_u64(), 1_001_000);
543 assert_eq!((x - MB as u64).as_u64(), 0);
544 assert_eq!((x - MB as u32).as_u64(), 0);
545 assert_eq!((x - KB as u32).as_u64(), 999_000);
546
547 x += MB as u64;
548 x += MB as u32;
549 x += 10u16;
550 x += 1u8;
551 assert_eq!(x.as_u64(), 3_000_011);
552 }
553
554 #[test]
555 fn test_comparison() {
556 assert!(ByteSize::mb(1) == ByteSize::kb(1000));
557 assert!(ByteSize::mib(1) == ByteSize::kib(1024));
558 assert!(ByteSize::mb(1) != ByteSize::kib(1024));
559 assert!(ByteSize::mb(1) < ByteSize::kib(1024));
560 assert!(ByteSize::b(0) < ByteSize::tib(1));
561 assert!(ByteSize::pib(1) < ByteSize::eb(1));
562 }
563
564 #[test]
565 fn as_unit_conversions() {
566 assert_eq!(41992187.5, ByteSize::gb(43).as_kib());
567 assert_eq!(0.028311552, ByteSize::mib(27).as_gb());
568 assert_eq!(0.0380859375, ByteSize::tib(39).as_pib());
569 assert_eq!(961.482752, ByteSize::kib(938948).as_mb());
570 assert_eq!(4.195428726649908, ByteSize::pb(4837).as_eib());
571 assert_eq!(2.613772153284117, ByteSize::b(2873872874893).as_tib());
572 }
573
574 #[track_caller]
575 fn assert_display(expected: &str, b: ByteSize) {
576 assert_eq!(expected, format!("{b}"));
577 }
578
579 #[test]
580 fn test_display() {
581 assert_display("215 B", ByteSize::b(215));
582 assert_display("1.0 KiB", ByteSize::kib(1));
583 assert_display("301.0 KiB", ByteSize::kib(301));
584 assert_display("419.0 MiB", ByteSize::mib(419));
585 assert_display("518.0 GiB", ByteSize::gib(518));
586 assert_display("815.0 TiB", ByteSize::tib(815));
587 assert_display("609.0 PiB", ByteSize::pib(609));
588 assert_display("15.0 EiB", ByteSize::eib(15));
589 }
590
591 #[test]
592 fn test_display_alignment() {
593 assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357)));
594 assert_eq!("| 357 B|", format!("|{:>10}|", ByteSize(357)));
595 assert_eq!("|357 B |", format!("|{:<10}|", ByteSize(357)));
596 assert_eq!("| 357 B |", format!("|{:^10}|", ByteSize(357)));
597
598 assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
599 assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
600 assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
601 }
602
603 #[test]
604 fn test_default() {
605 assert_eq!(ByteSize::b(0), ByteSize::default());
606 }
607}