Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Unreleased

- Use SI format by default with `Display`.
- Use "KiB" for SI unit.
- Use IEC (binary) format by default with `Display`.
- Use "kB" for SI unit.
- Implement `Sub<ByteSize>` for `ByteSize`.
- Implement `Sub<impl Into<u64>>` for `ByteSize`.
- Implement `SubAssign<ByteSize>` for `ByteSize`.
Expand Down
109 changes: 71 additions & 38 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
//! ```
//! use bytesize::ByteSize;
//!
//! assert_eq!("482.4 GiB", ByteSize::gb(518).to_string_as(true));
//! assert_eq!("518.0 GB", ByteSize::gb(518).to_string_as(false));
//! assert_eq!("482.4 GiB", ByteSize::gb(518).to_string_as(false));
//! assert_eq!("518.0 GB", ByteSize::gb(518).to_string_as(true));
Comment on lines +26 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rust doc also needs to be updated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be, this fn still exists for now

//! ```

mod parse;
Expand Down Expand Up @@ -58,10 +58,29 @@ pub const TIB: u64 = 1_099_511_627_776;
/// bytes size for 1 pebibyte
pub const PIB: u64 = 1_125_899_906_842_624;

static UNITS: &str = "KMGTPE";
static UNITS_SI: &str = "KMGTPE";
static LN_KB: f64 = 6.931471806; // ln 1024
static LN_KIB: f64 = 6.907755279; // ln 1000
/// IEC (binary) units.
///
/// See <https://en.wikipedia.org/wiki/Kilobyte>.
const UNITS_IEC: &str = "KMGTPE";

/// SI (decimal) units.
///
///
/// See <https://en.wikipedia.org/wiki/Kilobyte>.
const UNITS_SI: &str = "kMGTPE";

/// `ln(1024) ~= 6.931`
const LN_KIB: f64 = 6.931471806;

/// `ln(1000) ~= 6.908`
const LN_KB: f64 = 6.907755279;

#[derive(Debug, Clone, Default)]
pub enum Format {
#[default]
IEC,
SI,
}
Comment on lines +77 to +82
Copy link
Member Author

@robjtede robjtede Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this enum may or may not stay public (i have an idea to present in a follow up PR) but it really makes this PR easier to reason about


pub fn kb<V: Into<u64>>(size: V) -> u64 {
size.into() * KB
Expand Down Expand Up @@ -175,15 +194,28 @@ impl ByteSize {
}
}

pub fn to_string(bytes: u64, si_prefix: bool) -> String {
let unit = if si_prefix { KIB } else { KB };
let unit_base = if si_prefix { LN_KIB } else { LN_KB };
let unit_prefix = if si_prefix {
UNITS_SI.as_bytes()
} else {
UNITS.as_bytes()
pub fn to_string(bytes: u64, si_unit: bool) -> String {
to_string_format(bytes, if si_unit { Format::SI } else { Format::IEC })
}

pub fn to_string_format(bytes: u64, format: Format) -> String {
let unit = match format {
Format::IEC => KIB,
Format::SI => KB,
};
let unit_base = match format {
Format::IEC => LN_KIB,
Format::SI => LN_KB,
};

let unit_prefix = match format {
Format::IEC => UNITS_IEC.as_bytes(),
Format::SI => UNITS_SI.as_bytes(),
};
let unit_suffix = match format {
Format::IEC => "iB",
Format::SI => "B",
};
let unit_suffix = if si_prefix { "iB" } else { "B" };

if bytes < unit {
format!("{} B", bytes)
Expand All @@ -205,13 +237,13 @@ pub fn to_string(bytes: u64, si_prefix: bool) -> String {

impl Display for ByteSize {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(&to_string(self.0, true))
f.pad(&to_string_format(self.0, Format::IEC))
}
}

impl Debug for ByteSize {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self)
<Self as Display>::fmt(self, f)
}
}

Expand Down Expand Up @@ -395,6 +427,7 @@ mod tests {
assert!(ByteSize::b(0) < ByteSize::tib(1));
}

#[track_caller]
fn assert_display(expected: &str, b: ByteSize) {
assert_eq!(expected, format!("{}", b));
}
Expand Down Expand Up @@ -422,39 +455,39 @@ mod tests {
assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
}

fn assert_to_string(expected: &str, b: ByteSize, si: bool) {
assert_eq!(expected.to_string(), b.to_string_as(si));
#[track_caller]
fn assert_to_string(expected: &str, b: ByteSize, format: Format) {
assert_eq!(expected.to_string(), to_string_format(b.0, format));
}

#[test]
fn test_to_string_as() {
assert_to_string("215 B", ByteSize::b(215), true);
assert_to_string("215 B", ByteSize::b(215), false);
assert_to_string("215 B", ByteSize::b(215), Format::IEC);
assert_to_string("215 B", ByteSize::b(215), Format::SI);

assert_to_string("1.0 KiB", ByteSize::kib(1), true);
assert_to_string("1.0 KB", ByteSize::kib(1), false);
assert_to_string("1.0 KiB", ByteSize::kib(1), Format::IEC);
assert_to_string("1.0 kB", ByteSize::kib(1), Format::SI);

assert_to_string("293.9 KiB", ByteSize::kb(301), true);
assert_to_string("301.0 KB", ByteSize::kb(301), false);
assert_to_string("293.9 KiB", ByteSize::kb(301), Format::IEC);
assert_to_string("301.0 kB", ByteSize::kb(301), Format::SI);

assert_to_string("1.0 MiB", ByteSize::mib(1), true);
assert_to_string("1048.6 KB", ByteSize::mib(1), false);
assert_to_string("1024.0 KiB", ByteSize::mib(1), Format::IEC);
assert_to_string("1.0 MB", ByteSize::mib(1), Format::SI);

// a bug case: https://github.com/flang-project/bytesize/issues/8
assert_to_string("1.9 GiB", ByteSize::mib(1907), true);
assert_to_string("2.0 GB", ByteSize::mib(1908), false);
assert_to_string("1.9 GiB", ByteSize::mib(1907), Format::IEC);
assert_to_string("2.0 GB", ByteSize::mib(1908), Format::SI);

assert_to_string("399.6 MiB", ByteSize::mb(419), true);
assert_to_string("419.0 MB", ByteSize::mb(419), false);
assert_to_string("399.6 MiB", ByteSize::mb(419), Format::IEC);
assert_to_string("419.0 MB", ByteSize::mb(419), Format::SI);

assert_to_string("482.4 GiB", ByteSize::gb(518), true);
assert_to_string("518.0 GB", ByteSize::gb(518), false);
assert_to_string("482.4 GiB", ByteSize::gb(518), Format::IEC);
assert_to_string("518.0 GB", ByteSize::gb(518), Format::SI);

assert_to_string("741.2 TiB", ByteSize::tb(815), true);
assert_to_string("815.0 TB", ByteSize::tb(815), false);
assert_to_string("741.2 TiB", ByteSize::tb(815), Format::IEC);
assert_to_string("815.0 TB", ByteSize::tb(815), Format::SI);

assert_to_string("540.9 PiB", ByteSize::pb(609), true);
assert_to_string("609.0 PB", ByteSize::pb(609), false);
assert_to_string("540.9 PiB", ByteSize::pb(609), Format::IEC);
assert_to_string("609.0 PB", ByteSize::pb(609), Format::SI);
}

#[test]
Expand All @@ -464,6 +497,6 @@ mod tests {

#[test]
fn test_to_string() {
assert_to_string("609.0 PB", ByteSize::pb(609), false);
assert_to_string("609.0 PB", ByteSize::pb(609), Format::SI);
}
}
6 changes: 4 additions & 2 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ impl std::str::FromStr for Unit {

#[cfg(test)]
mod tests {
use crate::to_string_format;

use super::*;

#[test]
Expand Down Expand Up @@ -234,8 +236,8 @@ mod tests {

assert_eq!(parse(&format!("{}", parse("128GB"))), 128 * Unit::GigaByte);
assert_eq!(
parse(&crate::to_string(parse("128.000 GiB"), true)),
128 * Unit::GibiByte
parse(&to_string_format(parse("128.000 GiB"), crate::Format::IEC)),
128 * Unit::GibiByte,
);
}
}
3 changes: 1 addition & 2 deletions src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,9 @@ mod tests {
}

#[test]

fn test_serde_json() {
let json = serde_json::to_string(&ByteSize::mib(1)).unwrap();
assert_eq!(json, "\"1.0 MiB\"");
assert_eq!(json, "\"1024.0 KiB\"");
Copy link
Collaborator

@MrCroxx MrCroxx Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the output here "1024.0 KiB" instead of "1.0 MiB"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put it down to a floating point error, but I agree that it's worth figuring out what changed. I notice now that the original PR didn't have to make this change 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plot thickens...

assert_to_string("1.0 MiB", ByteSize::mib(1), false);

in the original PR fails the assertion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved! It was related to the floating point situation. I've increased the precision of the LN_ constants and now "1.0 MiB" is generated.


let deserialized = serde_json::from_str::<ByteSize>(&json).unwrap();
assert_eq!(deserialized.0, 1048576);
Expand Down