From 3d2a7bd057a31bdaa54fdfe9b7b8ec2a02fa0ddc Mon Sep 17 00:00:00 2001 From: tison Date: Fri, 31 Jul 2026 22:03:41 +0800 Subject: [PATCH 1/2] docs: clarify display precision contract --- README.md | 31 ++++++++++++++++++++++++++++++- bsize/src/display.rs | 36 +++++++++++++++++++++++++++++------- bsize/src/lib.rs | 16 ++++++++++++---- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b24fb35..a9bbc36 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ This crate provides multiple semantic wrappers and utilities for byte size repre * `#![no_std]`-capable, no heap allocation, and no runtime dependencies by default. * `ByteSize` wrappers over supported unsigned integer base types, with `BSize` as the `usize` alias and `BSize8`, `BSize16`, `BSize32`, and `BSize64` aliases for fixed-width base types. * `FromStr` impl for `ByteSize`, allowing for parsing string size representations like "1.5 KiB" and "521 TB". -* `Display` impl for `ByteSize`, allowing for formatting byte sizes as human-readable strings in both binary (e.g., "1.5 MiB") and decimal (e.g., "1.5 MB") styles. +* Exact `Display` impl for `ByteSize`, rendering the underlying byte count in base bytes (e.g., "1572864 B"). +* Configurable, approximate human-readable formatting in both binary (e.g., "1.5 MiB") and decimal (e.g., "1.6 MB") styles. * Optional `serde` support for binary and human-readable format. * Optional `nightly` support for a broader const-friendly API surface powered by nightly-only Rust features. @@ -35,6 +36,34 @@ With the `nightly` feature enabled on a nightly compiler, this crate can use uns Read the online documents at https://docs.rs/bsize. +## Formatting + +The standard `Display` implementation for `ByteSize` preserves the exact integer byte count and +always uses the base-byte unit: + +```rust +use bsize::BSize64; + +let size = BSize64::mib(1); +assert_eq!("1048576 B", size.to_string()); +``` + +Use `ByteSize::display` or the free `display` function for configurable, human-readable output: + +```rust +use bsize::BSize64; + +let size = BSize64::mib(1); +assert_eq!("1.0 MiB", size.display().to_string()); +assert_eq!("1.0 MB", size.display().decimal().to_string()); +``` + +The human-readable `Display` wrapper stores and scales values as `f64`. Integer inputs are +converted to `f64` first, so sufficiently large integers may lose precision or cross an automatic +scale boundary. Its output is intended for presentation and is not guaranteed to parse back to the +original byte count. Use the standard `ByteSize` formatting shown above when an exact or +round-trippable representation is required. + ## Why Yet Another Byte Size Crate? There are already several crates that provide functionality for parsing, formatting, and/or representing byte sizes. diff --git a/bsize/src/display.rs b/bsize/src/display.rs index 2ccda73..41c7ab6 100644 --- a/bsize/src/display.rs +++ b/bsize/src/display.rs @@ -18,18 +18,21 @@ use core::fmt::Write as _; use crate::BaseByteSize; use crate::ByteSize; -/// Create a [`Display`] instance for displaying a byte size. +/// Create a [`Display`] instance for approximate, human-readable formatting. /// -/// See [`Display`] for examples. Use [`Display::new`] when the byte count is already represented -/// as an `f64`. +/// The integer byte count is converted to `f64` before scaling and formatting. See [`Display`] for +/// the precision contract and examples. Use [`Display::new`] when the byte count is already +/// represented as an `f64`. pub fn display(size: impl BaseByteSize) -> Display { Display::new(size.to_f64()) } impl ByteSize { - /// Returns a [`Display`] wrapper. + /// Returns a [`Display`] wrapper for approximate, human-readable formatting. /// - /// See [`Display`] for examples. + /// The underlying integer byte count is converted to `f64` before scaling and formatting. Use + /// this type's standard [`fmt::Display`] implementation when an exact base-byte representation + /// is required. See [`Display`] for the full precision contract and examples. pub fn display(&self) -> Display { Display::new(self.bytes().to_f64()) } @@ -40,6 +43,22 @@ impl ByteSize { /// You may create this wrapper with [`Display::new`], [`display`], or [`ByteSize::display`], then /// pass custom [`DisplayOptions`] with [`Display::options`]. /// +/// # Precision and round trips +/// +/// This wrapper is intended for approximate presentation, not exact serialization. Values are +/// stored, scaled, and formatted as `f64`. Integer byte counts supplied through [`display`] or +/// [`ByteSize::display`] are converted to `f64` first, so sufficiently large integers may lose +/// precision. Automatic scale selection also observes the converted value and can therefore cross +/// a scale boundary when conversion rounds an integer upward. +/// +/// Formatter precision controls only the number of displayed fractional digits; it cannot recover +/// precision lost during conversion to `f64`. Output from this wrapper is not guaranteed to parse +/// back to the original byte count. Use [`ByteSize`]'s standard [`fmt::Display`] implementation for +/// an exact, round-trippable base-byte representation. +/// +/// Without an explicit formatter precision, values in the base scale use the default `f64` +/// representation and values with a unit prefix use one fractional digit. +/// /// # Examples /// /// Display with the [`DisplayOptions::BINARY`] and [`DisplayOptions::DECIMAL`] presets. @@ -312,10 +331,13 @@ impl Display { self } - /// Create a [`Display`] instance from a byte count. + /// Create a [`Display`] instance from an approximate byte count. /// /// This constructor is useful when the byte count is already represented as an `f64`. For - /// supported integer byte counts, use [`display`] or [`ByteSize::display`]. + /// supported integer byte counts, use [`display`] or [`ByteSize::display`]. The wrapper follows + /// `f64` precision semantics and is intended for presentation rather than exact serialization. + /// Positive infinity is accepted and uses the largest available unit prefix when the scale is + /// selected automatically. /// /// # Examples /// diff --git a/bsize/src/lib.rs b/bsize/src/lib.rs index 6246a08..61d4e16 100644 --- a/bsize/src/lib.rs +++ b/bsize/src/lib.rs @@ -29,8 +29,10 @@ //! for fixed-width base types. //! * `FromStr` impl for `ByteSize`, allowing for parsing string size representations like "1.5 KiB" //! and "521 TB". -//! * [`Display`] impl for `ByteSize`, allowing for formatting byte sizes as human-readable strings -//! in both binary (e.g., "1.5 MiB") and decimal (e.g., "1.5 MB") styles. +//! * Exact [`core::fmt::Display`] impl for [`ByteSize`], rendering the underlying byte count in +//! base bytes (e.g., "1572864 B"). +//! * Configurable, approximate human-readable formatting in both binary (e.g., "1.5 MiB") and +//! decimal (e.g., "1.6 MB") styles. //! * Optional `serde` support for binary and human-readable format. //! * Optional `nightly` support for a broader const-friendly API surface powered by nightly-only //! Rust features. @@ -66,7 +68,7 @@ //! assert_eq!(BSize64::mib(1).map(|bytes| bytes + 512 * 1024), size); //! ``` //! -//! Display as human-readable string. +//! Format the exact byte count or an approximate human-readable string. //! //! ``` //! use bsize::BSize; @@ -74,6 +76,10 @@ //! use bsize::DisplayOptions; //! use bsize::DisplayScale; //! +//! let size = BSize::mib(1); +//! assert_eq!("1048576 B", size.to_string()); +//! assert_eq!("1.0 MiB", size.display().to_string()); +//! //! assert_eq!("518.0 GiB", BSize::gib(518).display().binary().to_string()); //! //! assert_eq!("556.2 GB", BSize::gib(518).display().decimal().to_string()); @@ -138,7 +144,9 @@ pub use self::traits::TeraByteSize; /// Byte size representation. /// /// Use [`ByteSize::b`] to construct a value from bytes and [`ByteSize::bytes`] to get -/// the exact underlying byte count. +/// the exact underlying byte count. Its standard [`core::fmt::Display`] implementation renders +/// that exact count in base bytes. Use [`ByteSize::display`] for configurable, approximate +/// human-readable formatting. #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ByteSize(T); From 919d49ea10a298b7227204ae8cb684c16e7de119 Mon Sep 17 00:00:00 2001 From: tison Date: Fri, 31 Jul 2026 22:12:29 +0800 Subject: [PATCH 2/2] docs: keep display details in API docs --- README.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/README.md b/README.md index a9bbc36..5e6d607 100644 --- a/README.md +++ b/README.md @@ -36,34 +36,6 @@ With the `nightly` feature enabled on a nightly compiler, this crate can use uns Read the online documents at https://docs.rs/bsize. -## Formatting - -The standard `Display` implementation for `ByteSize` preserves the exact integer byte count and -always uses the base-byte unit: - -```rust -use bsize::BSize64; - -let size = BSize64::mib(1); -assert_eq!("1048576 B", size.to_string()); -``` - -Use `ByteSize::display` or the free `display` function for configurable, human-readable output: - -```rust -use bsize::BSize64; - -let size = BSize64::mib(1); -assert_eq!("1.0 MiB", size.display().to_string()); -assert_eq!("1.0 MB", size.display().decimal().to_string()); -``` - -The human-readable `Display` wrapper stores and scales values as `f64`. Integer inputs are -converted to `f64` first, so sufficiently large integers may lose precision or cross an automatic -scale boundary. Its output is intended for presentation and is not guaranteed to parse back to the -original byte count. Use the standard `ByteSize` formatting shown above when an exact or -round-trippable representation is required. - ## Why Yet Another Byte Size Crate? There are already several crates that provide functionality for parsing, formatting, and/or representing byte sizes.