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 crate::display::Display;
59use crate::display::Format;
60
61pub const KB: u64 = 1_000;
63pub const MB: u64 = 1_000_000;
65pub const GB: u64 = 1_000_000_000;
67pub const TB: u64 = 1_000_000_000_000;
69pub const PB: u64 = 1_000_000_000_000_000;
71pub const EB: u64 = 1_000_000_000_000_000_000;
73
74pub const KIB: u64 = 1_024;
76pub const MIB: u64 = 1_048_576;
78pub const GIB: u64 = 1_073_741_824;
80pub const TIB: u64 = 1_099_511_627_776;
82pub const PIB: u64 = 1_125_899_906_842_624;
84pub const EIB: u64 = 1_152_921_504_606_846_976;
86
87const UNITS_IEC: &str = "KMGTPE";
91
92const UNITS_SI: &str = "kMGTPE";
96
97const LN_KIB: f64 = 6.931_471_805_599_453;
99
100const LN_KB: f64 = 6.907_755_278_982_137;
102
103pub fn kb(size: impl Into<u64>) -> u64 {
105 size.into() * KB
106}
107
108pub fn kib<V: Into<u64>>(size: V) -> u64 {
110 size.into() * KIB
111}
112
113pub fn mb<V: Into<u64>>(size: V) -> u64 {
115 size.into() * MB
116}
117
118pub fn mib<V: Into<u64>>(size: V) -> u64 {
120 size.into() * MIB
121}
122
123pub fn gb<V: Into<u64>>(size: V) -> u64 {
125 size.into() * GB
126}
127
128pub fn gib<V: Into<u64>>(size: V) -> u64 {
130 size.into() * GIB
131}
132
133pub fn tb<V: Into<u64>>(size: V) -> u64 {
135 size.into() * TB
136}
137
138pub fn tib<V: Into<u64>>(size: V) -> u64 {
140 size.into() * TIB
141}
142
143pub fn pb<V: Into<u64>>(size: V) -> u64 {
145 size.into() * PB
146}
147
148pub fn pib<V: Into<u64>>(size: V) -> u64 {
150 size.into() * PIB
151}
152
153pub fn eb<V: Into<u64>>(size: V) -> u64 {
155 size.into() * EB
156}
157
158pub fn eib<V: Into<u64>>(size: V) -> u64 {
160 size.into() * EIB
161}
162
163#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)]
165pub struct ByteSize(pub u64);
166
167impl ByteSize {
168 #[inline(always)]
170 pub const fn b(size: u64) -> ByteSize {
171 ByteSize(size)
172 }
173
174 #[inline(always)]
176 pub const fn kb(size: u64) -> ByteSize {
177 ByteSize(size * KB)
178 }
179
180 #[inline(always)]
182 pub const fn kib(size: u64) -> ByteSize {
183 ByteSize(size * KIB)
184 }
185
186 #[inline(always)]
188 pub const fn mb(size: u64) -> ByteSize {
189 ByteSize(size * MB)
190 }
191
192 #[inline(always)]
194 pub const fn mib(size: u64) -> ByteSize {
195 ByteSize(size * MIB)
196 }
197
198 #[inline(always)]
200 pub const fn gb(size: u64) -> ByteSize {
201 ByteSize(size * GB)
202 }
203
204 #[inline(always)]
206 pub const fn gib(size: u64) -> ByteSize {
207 ByteSize(size * GIB)
208 }
209
210 #[inline(always)]
212 pub const fn tb(size: u64) -> ByteSize {
213 ByteSize(size * TB)
214 }
215
216 #[inline(always)]
218 pub const fn tib(size: u64) -> ByteSize {
219 ByteSize(size * TIB)
220 }
221
222 #[inline(always)]
224 pub const fn pb(size: u64) -> ByteSize {
225 ByteSize(size * PB)
226 }
227
228 #[inline(always)]
230 pub const fn pib(size: u64) -> ByteSize {
231 ByteSize(size * PIB)
232 }
233
234 #[inline(always)]
236 pub const fn eb(size: u64) -> ByteSize {
237 ByteSize(size * EB)
238 }
239
240 #[inline(always)]
242 pub const fn eib(size: u64) -> ByteSize {
243 ByteSize(size * EIB)
244 }
245
246 #[inline(always)]
248 pub const fn as_u64(&self) -> u64 {
249 self.0
250 }
251
252 pub fn display(&self) -> Display {
254 Display {
255 byte_size: *self,
256 format: Format::Iec,
257 }
258 }
259}
260
261impl fmt::Display for ByteSize {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 let display = self.display();
264
265 if f.width().is_none() {
266 fmt::Display::fmt(&display, f)
268 } else {
269 f.pad(&display.to_string())
270 }
271 }
272}
273
274impl fmt::Debug for ByteSize {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 write!(f, "{} ({} bytes)", self, self.0)
277 }
278}
279
280macro_rules! commutative_op {
281 ($t:ty) => {
282 impl ops::Add<ByteSize> for $t {
283 type Output = ByteSize;
284 #[inline(always)]
285 fn add(self, rhs: ByteSize) -> ByteSize {
286 ByteSize(rhs.0 + (self as u64))
287 }
288 }
289
290 impl ops::Mul<ByteSize> for $t {
291 type Output = ByteSize;
292 #[inline(always)]
293 fn mul(self, rhs: ByteSize) -> ByteSize {
294 ByteSize(rhs.0 * (self as u64))
295 }
296 }
297 };
298}
299
300commutative_op!(u64);
301commutative_op!(u32);
302commutative_op!(u16);
303commutative_op!(u8);
304
305impl ops::Add<ByteSize> for ByteSize {
306 type Output = ByteSize;
307
308 #[inline(always)]
309 fn add(self, rhs: ByteSize) -> ByteSize {
310 ByteSize(self.0 + rhs.0)
311 }
312}
313
314impl ops::AddAssign<ByteSize> for ByteSize {
315 #[inline(always)]
316 fn add_assign(&mut self, rhs: ByteSize) {
317 self.0 += rhs.0
318 }
319}
320
321impl<T> ops::Add<T> for ByteSize
322where
323 T: Into<u64>,
324{
325 type Output = ByteSize;
326 #[inline(always)]
327 fn add(self, rhs: T) -> ByteSize {
328 ByteSize(self.0 + (rhs.into()))
329 }
330}
331
332impl<T> ops::AddAssign<T> for ByteSize
333where
334 T: Into<u64>,
335{
336 #[inline(always)]
337 fn add_assign(&mut self, rhs: T) {
338 self.0 += rhs.into();
339 }
340}
341
342impl ops::Sub<ByteSize> for ByteSize {
343 type Output = ByteSize;
344
345 #[inline(always)]
346 fn sub(self, rhs: ByteSize) -> ByteSize {
347 ByteSize(self.0 - rhs.0)
348 }
349}
350
351impl ops::SubAssign<ByteSize> for ByteSize {
352 #[inline(always)]
353 fn sub_assign(&mut self, rhs: ByteSize) {
354 self.0 -= rhs.0
355 }
356}
357
358impl<T> ops::Sub<T> for ByteSize
359where
360 T: Into<u64>,
361{
362 type Output = ByteSize;
363 #[inline(always)]
364 fn sub(self, rhs: T) -> ByteSize {
365 ByteSize(self.0 - (rhs.into()))
366 }
367}
368
369impl<T> ops::SubAssign<T> for ByteSize
370where
371 T: Into<u64>,
372{
373 #[inline(always)]
374 fn sub_assign(&mut self, rhs: T) {
375 self.0 -= rhs.into();
376 }
377}
378
379impl<T> ops::Mul<T> for ByteSize
380where
381 T: Into<u64>,
382{
383 type Output = ByteSize;
384 #[inline(always)]
385 fn mul(self, rhs: T) -> ByteSize {
386 ByteSize(self.0 * rhs.into())
387 }
388}
389
390impl<T> ops::MulAssign<T> for ByteSize
391where
392 T: Into<u64>,
393{
394 #[inline(always)]
395 fn mul_assign(&mut self, rhs: T) {
396 self.0 *= rhs.into();
397 }
398}
399
400#[cfg(test)]
401mod property_tests {
402 use alloc::string::{String, ToString as _};
403
404 use super::*;
405
406 impl quickcheck::Arbitrary for ByteSize {
407 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
408 Self(u64::arbitrary(g))
409 }
410 }
411
412 quickcheck::quickcheck! {
413 fn parsing_never_panics(size: String) -> bool {
414 let _ = size.parse::<ByteSize>();
415 true
416 }
417
418 fn to_string_never_blank(size: ByteSize) -> bool {
419 !size.to_string().is_empty()
420 }
421
422 fn to_string_never_large(size: ByteSize) -> bool {
423 size.to_string().len() < 11
424 }
425
426 fn string_round_trip(size: ByteSize) -> bool {
427 if size > ByteSize::pib(1) {
429 return true;
430 }
431
432 size.to_string().parse::<ByteSize>().unwrap() == size
433 }
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use alloc::format;
440
441 use super::*;
442
443 #[test]
444 fn test_arithmetic_op() {
445 let mut x = ByteSize::mb(1);
446 let y = ByteSize::kb(100);
447
448 assert_eq!((x + y).as_u64(), 1_100_000u64);
449
450 assert_eq!((x - y).as_u64(), 900_000u64);
451
452 assert_eq!((x + (100 * 1000) as u64).as_u64(), 1_100_000);
453
454 assert_eq!((x * 2u64).as_u64(), 2_000_000);
455
456 x += y;
457 assert_eq!(x.as_u64(), 1_100_000);
458 x *= 2u64;
459 assert_eq!(x.as_u64(), 2_200_000);
460 }
461
462 #[allow(clippy::unnecessary_cast)]
463 #[test]
464 fn test_arithmetic_primitives() {
465 let mut x = ByteSize::mb(1);
466
467 assert_eq!((x + MB as u64).as_u64(), 2_000_000);
468 assert_eq!((x + MB as u32).as_u64(), 2_000_000);
469 assert_eq!((x + KB as u16).as_u64(), 1_001_000);
470 assert_eq!((x - MB as u64).as_u64(), 0);
471 assert_eq!((x - MB as u32).as_u64(), 0);
472 assert_eq!((x - KB as u32).as_u64(), 999_000);
473
474 x += MB as u64;
475 x += MB as u32;
476 x += 10u16;
477 x += 1u8;
478 assert_eq!(x.as_u64(), 3_000_011);
479 }
480
481 #[test]
482 fn test_comparison() {
483 assert!(ByteSize::mb(1) == ByteSize::kb(1000));
484 assert!(ByteSize::mib(1) == ByteSize::kib(1024));
485 assert!(ByteSize::mb(1) != ByteSize::kib(1024));
486 assert!(ByteSize::mb(1) < ByteSize::kib(1024));
487 assert!(ByteSize::b(0) < ByteSize::tib(1));
488 assert!(ByteSize::pib(1) < ByteSize::eb(1));
489 }
490
491 #[track_caller]
492 fn assert_display(expected: &str, b: ByteSize) {
493 assert_eq!(expected, format!("{b}"));
494 }
495
496 #[test]
497 fn test_display() {
498 assert_display("215 B", ByteSize::b(215));
499 assert_display("1.0 KiB", ByteSize::kib(1));
500 assert_display("301.0 KiB", ByteSize::kib(301));
501 assert_display("419.0 MiB", ByteSize::mib(419));
502 assert_display("518.0 GiB", ByteSize::gib(518));
503 assert_display("815.0 TiB", ByteSize::tib(815));
504 assert_display("609.0 PiB", ByteSize::pib(609));
505 assert_display("15.0 EiB", ByteSize::eib(15));
506 }
507
508 #[test]
509 fn test_display_alignment() {
510 assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357)));
511 assert_eq!("| 357 B|", format!("|{:>10}|", ByteSize(357)));
512 assert_eq!("|357 B |", format!("|{:<10}|", ByteSize(357)));
513 assert_eq!("| 357 B |", format!("|{:^10}|", ByteSize(357)));
514
515 assert_eq!("|-----357 B|", format!("|{:->10}|", ByteSize(357)));
516 assert_eq!("|357 B-----|", format!("|{:-<10}|", ByteSize(357)));
517 assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
518 }
519
520 #[test]
521 fn test_default() {
522 assert_eq!(ByteSize::b(0), ByteSize::default());
523 }
524}