byte-calc 0.1.0

Helper crate to work with bit, byte, and block sizes.
Documentation

Helper crate to work with bit, byte, and block sizes.

This crate provides three dedicated types, [NumBits], [NumBytes], and [NumBlocks], to represent numbers of bits, bytes, and blocks, respectively. It implements the usual traits for numeric operators such that calculations on them can be carried out with succinct syntax. All operations will panic on errors, such as over- or underflows. This is an intentional design decision to prevent subtly incorrect results and behavior. In addition, this crate provides formatting and parsing for byte sizes.

This crate is no_std-compatible.

Conversions

The provided types support convenient conversions to each other:

# use byte_calc::{NumBits, NumBytes, NumBlocks};
assert_eq!(NumBits::new(15).to_bytes_ceil(), NumBytes::bytes(2));
assert_eq!(NumBits::new(15).to_bytes_floor(), NumBytes::bytes(1));

assert_eq!(NumBytes::bytes(2).to_bits(), NumBits::new(16));
assert_eq!(NumBits::from(NumBytes::bytes(2)), NumBits::new(16));

const BLOCK_SIZE: NumBytes = NumBytes::kibibytes(4);
assert_eq!(NumBytes::bytes(8193).to_blocks_ceil(BLOCK_SIZE), NumBlocks::new(3));
assert_eq!(NumBytes::bytes(8193).to_blocks_floor(BLOCK_SIZE), NumBlocks::new(2));

assert_eq!(NumBlocks::new(2).to_bytes(BLOCK_SIZE), NumBytes::kibibytes(8));

Calculations

Calculations can be performed with the types as well as with [u64] integers:

# use byte_calc::{NumBits, NumBytes, NumBlocks};
assert_eq!(NumBytes::bytes(10) / NumBytes::bytes(2), NumBytes::bytes(5));
assert_eq!(NumBytes::bytes(10) / 2, NumBytes::bytes(5));

assert_eq!(NumBits::new(5) + NumBits::new(8), NumBits::new(13));
assert_eq!(NumBits::new(5) * 2, NumBits::new(10));

assert_eq!(NumBlocks::new(10) + 2, NumBlocks::new(12));

assert_eq!(NumBits::new(2) + NumBytes::bytes(1), NumBits::new(10));

Comparisons

Comparisons are supported on the types as well as with [u64] integers:

# use byte_calc::{NumBits, NumBytes, NumBlocks};
assert!(NumBytes::bytes(10) < 20);
assert!(NumBytes::bytes(10) != 0);

assert_eq!(NumBits::new(5), 5);

assert_eq!(NumBits::new(16), NumBytes::new(2));
assert!(NumBits::new(15) < NumBytes::new(2));

Formatting

Formatting of byte sizes maximizes the unit while minimizing the integer part towards one. For example:

# use byte_calc::NumBytes;
assert_eq!(NumBytes::mebibytes(128).to_string(), "128MiB");
assert_eq!(NumBytes::gigabytes(1).to_string(), "1GB");
assert_eq!(NumBytes::bytes(1023).to_string(), "1.023kB");
assert_eq!(NumBytes::bytes(1000).to_string(), "1kB");
assert_eq!(NumBytes::bytes(999).to_string(), "999B");
assert_eq!(NumBytes::bytes(2560).to_string(), "2.5KiB");

The usual formatting syntax can be used to limit the precision:

# use byte_calc::NumBytes;
assert_eq!(format!("{:.2}", NumBytes::terabytes(2)), "1.81TiB");

Parsing

Byte sizes must follow the following syntax:

⟨byte-size⟩  ::=  ⟨int⟩ [ '.' ⟨int⟩ ] [ ' '* ⟨unit⟩ ]
⟨int⟩  ::=  [0-9_]+
⟨unit⟩ ::=  'B' | 'K' … 'E' | 'kB' … 'EB' | 'KiB' … 'EiB' (case-insensitive)

The units (K ... E) are interpreted as binary units (KiB ... EiB). Generally, unit parsing is case-insensitive.

# use core::str::FromStr;
# use byte_calc::NumBytes;
assert_eq!(NumBytes::from_str("5").unwrap(), NumBytes::bytes(5));
assert_eq!(NumBytes::from_str("2.5KiB").unwrap(), NumBytes::bytes(2560));
assert_eq!(NumBytes::from_str("2_000kB").unwrap(), NumBytes::megabytes(2));

Parsing also works in const contexts using [NumBytes::parse_str] or [NumBytes::parse_ascii]:

# use core::str::FromStr;
# use byte_calc::NumBytes;
const BLOCK_SIZE: NumBytes = match NumBytes::parse_str("4KiB") {
    Ok(value) => value,
    Err(_) => panic!("invalid format"),
};

The parser has been extensively fuzz-tested, ensuring that no input leads to panics.

Serialization and Deserialization

By enabling the serde feature, [NumBits], [NumBytes], and [NumBlocks] can be serialized and deserialized. All tree types always serialize as [u64] integers. Deserialization of [NumBytes] is also supported from strings.