diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a443f..025988f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,13 @@ All significant changes to this software be documented in this file. ### Breaking changes * Renamed `BSize::with` to `BSize::map`. A new `BSize::with` method has been added that accepts a closure returning arbitrary type, allowing for mapping a `BSize` to any other type. This follows `LocalKey::with`'s naming conventions. +* Removed the `Displayable` trait. `ByteSize` now has a `to_f64` method that is the same as the `Displayable::canonicalize` method. ### New features * Added `nightly` feature for using `BSize` with nightly-only features like `const_ops` and `const_trait_impl`. +* Added `ByteSize::to_f64` for converting supported byte size underlying types to approximate `f64` values. +* Added `BSize::as_b` for returning the byte count as an approximate `f64`. ## v0.2.1 (2026-06-27) diff --git a/Cargo.lock b/Cargo.lock index b6c1a29..788094f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,7 @@ name = "bsize" version = "0.3.0-rc.1" dependencies = [ "insta", + "macroweave", "quickcheck", "serde", "serde_core", @@ -266,6 +267,16 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "macroweave" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ab787669b4105aad23153b64c43ba63499e90b3e9df0d5643cda24382d3dfe" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "memchr" version = "2.8.2" diff --git a/README.md b/README.md index 8859546..20c6615 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This crate provides multiple semantic wrappers and utilities for byte size repre ## Features -* `#![no_std]`-capable, no dependencies, and uses no heap allocation. +* `#![no_std]`-capable, no heap allocation, and no runtime dependencies by default. * `BSize` wrappers over `u8`, `u16`, `u32`, `u64`, and `usize` for representing byte sizes with different underlying types. * `FromStr` impl for `BSize`, allowing for parsing string size representations like "1.5 KiB" and "521 TB". * `Display` impl for `BSize`, 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. diff --git a/bsize/Cargo.toml b/bsize/Cargo.toml index d2ed5d5..cf4ed7e 100644 --- a/bsize/Cargo.toml +++ b/bsize/Cargo.toml @@ -38,6 +38,7 @@ nightly = [] serde = ["dep:serde_core"] [dependencies] +macroweave = "0.1.0" serde_core = { version = "1", default-features = false, optional = true } [dev-dependencies] diff --git a/bsize/src/display.rs b/bsize/src/display.rs index f2a86db..51bb3d6 100644 --- a/bsize/src/display.rs +++ b/bsize/src/display.rs @@ -16,22 +16,22 @@ use core::fmt; use core::fmt::Write as _; use crate::BSize; -use crate::Displayable; +use crate::ByteSize; /// Create a [`Display`] instance for displaying a byte size. /// /// See [`Display`] for examples. Use [`Display::new`] when the byte count is already represented /// as an `f64`. -pub fn display(size: impl Displayable) -> Display { - Display::new(size.canonicalize()) +pub fn display(size: impl ByteSize) -> Display { + Display::new(size.to_f64()) } -impl BSize { +impl BSize { /// Returns a [`Display`] wrapper. /// /// See [`Display`] for examples. pub fn display(&self) -> Display { - Display::new(self.0.canonicalize()) + Display::new(self.0.to_f64()) } } diff --git a/bsize/src/lib.rs b/bsize/src/lib.rs index 6ee6530..b964584 100644 --- a/bsize/src/lib.rs +++ b/bsize/src/lib.rs @@ -20,7 +20,7 @@ //! //! # Features //! -//! * `#![no_std]`-capable, no dependencies, and uses no heap allocation. +//! * `#![no_std]`-capable, no heap allocation, and no runtime dependencies by default. //! * [`BSize`] wrappers over `u8`, `u16`, `u32`, `u64`, and `usize` for representing byte sizes //! with different underlying types. //! * `FromStr` impl for `BSize`, allowing for parsing string size representations like "1.5 KiB" @@ -119,7 +119,6 @@ pub use self::display::DisplayUnitSystem; pub use self::display::display; pub use self::parse::ParseError; pub use self::traits::ByteSize; -pub use self::traits::Displayable; pub use self::traits::ExaByteSize; pub use self::traits::GigaByteSize; pub use self::traits::KiloByteSize; @@ -128,6 +127,17 @@ pub use self::traits::PetaByteSize; pub use self::traits::TeraByteSize; pub use self::types::BSize; +#[cfg(test)] +fn assert_close(actual: f64, expected: f64) { + let delta = (actual - expected).abs(); + let tolerance = f64::EPSILON; + + assert!( + delta <= tolerance, + "actual: {actual}, expected: {expected}, delta: {delta}, tolerance: {tolerance}", + ); +} + #[cfg(test)] mod property_tests { use alloc::string::String; diff --git a/bsize/src/ops.rs b/bsize/src/ops/mod.rs similarity index 57% rename from bsize/src/ops.rs rename to bsize/src/ops/mod.rs index 015f579..388269c 100644 --- a/bsize/src/ops.rs +++ b/bsize/src/ops/mod.rs @@ -12,53 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::ops; - -use crate::types::BSize; - -macro_rules! impl_ops { - ($($ty:ty),* $(,)?) => { - $( - impl ops::Add> for BSize<$ty> { - type Output = Self; - - #[inline(always)] - fn add(self, rhs: BSize<$ty>) -> Self::Output { - BSize(self.0 + rhs.0) - } - } - - impl ops::AddAssign> for BSize<$ty> { - #[inline(always)] - fn add_assign(&mut self, rhs: BSize<$ty>) { - self.0 += rhs.0; - } - } - - impl ops::Sub> for BSize<$ty> { - type Output = Self; - - #[inline(always)] - fn sub(self, rhs: BSize<$ty>) -> Self::Output { - BSize(self.0 - rhs.0) - } - } - - impl ops::SubAssign> for BSize<$ty> { - #[inline(always)] - fn sub_assign(&mut self, rhs: BSize<$ty>) { - self.0 -= rhs.0; - } - } - )* - }; -} - -impl_ops!(u8, u16, u32, u64, usize); +#[cfg(feature = "nightly")] +mod nightly; +#[cfg(not(feature = "nightly"))] +mod stable; #[cfg(test)] mod tests { - use super::BSize; + use crate::BSize; #[test] fn adds_byte_sizes() { diff --git a/bsize/src/ops/nightly.rs b/bsize/src/ops/nightly.rs new file mode 100644 index 0000000..94cc58e --- /dev/null +++ b/bsize/src/ops/nightly.rs @@ -0,0 +1,83 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::ops; + +use crate::traits::ByteSize; +use crate::types::BSize; + +const impl ops::Add> for BSize +where + T: [const] ByteSize + [const] ops::Add, +{ + type Output = Self; + + #[inline(always)] + fn add(self, rhs: BSize) -> Self::Output { + BSize(self.0 + rhs.0) + } +} + +const impl ops::AddAssign> for BSize +where + T: [const] ByteSize + [const] ops::AddAssign, +{ + #[inline(always)] + fn add_assign(&mut self, rhs: BSize) { + self.0 += rhs.0; + } +} + +const impl ops::Sub> for BSize +where + T: [const] ByteSize + [const] ops::Sub, +{ + type Output = Self; + + #[inline(always)] + fn sub(self, rhs: BSize) -> Self::Output { + BSize(self.0 - rhs.0) + } +} + +const impl ops::SubAssign> for BSize +where + T: [const] ByteSize + [const] ops::SubAssign, +{ + #[inline(always)] + fn sub_assign(&mut self, rhs: BSize) { + self.0 -= rhs.0; + } +} + +#[cfg(test)] +mod tests { + use crate::BSize; + + #[test] + fn arithmetic_is_const() { + const SUM: BSize = BSize::b(3_u64) + BSize::b(5_u64); + const DIFFERENCE: BSize = BSize::b(8_u64) - BSize::b(5_u64); + const ASSIGNED: BSize = { + let mut size = BSize::b(3_u64); + size += BSize::b(5_u64); + size -= BSize::b(2_u64); + size + }; + + assert_eq!(SUM, BSize::b(8)); + assert_eq!(DIFFERENCE, BSize::b(3)); + assert_eq!(ASSIGNED, BSize::b(6)); + } +} diff --git a/bsize/src/ops/stable.rs b/bsize/src/ops/stable.rs new file mode 100644 index 0000000..49dcecb --- /dev/null +++ b/bsize/src/ops/stable.rs @@ -0,0 +1,51 @@ +// Copyright 2026 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::ops; + +use crate::types::BSize; + +macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] { + impl ops::Add> for BSize { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: BSize) -> Self::Output { + BSize(self.0 + rhs.0) + } + } + + impl ops::AddAssign> for BSize { + #[inline(always)] + fn add_assign(&mut self, rhs: BSize) { + self.0 += rhs.0; + } + } + + impl ops::Sub> for BSize { + type Output = Self; + + #[inline(always)] + fn sub(self, rhs: BSize) -> Self::Output { + BSize(self.0 - rhs.0) + } + } + + impl ops::SubAssign> for BSize { + #[inline(always)] + fn sub_assign(&mut self, rhs: BSize) { + self.0 -= rhs.0; + } + } +}); diff --git a/bsize/src/parse.rs b/bsize/src/parse.rs index 7800783..8b181e1 100644 --- a/bsize/src/parse.rs +++ b/bsize/src/parse.rs @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::convert::TryFrom; use core::fmt; use core::str::FromStr; use crate::BSize; +use crate::ByteSize; /// The error returned when parsing a byte size fails. #[derive(Debug, Clone, Eq, PartialEq)] @@ -41,26 +43,24 @@ impl fmt::Display for ParseError { impl core::error::Error for ParseError {} -macro_rules! impl_from_str { - ($($ty:ty),* $(,)?) => { - $( - impl FromStr for BSize<$ty> { - type Err = ParseError; +macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] { + impl FromStr for BSize { + type Err = ParseError; - fn from_str(s: &str) -> Result { - let size = parse_size(s.as_bytes())?; - if size <= <$ty>::MAX as u64 { - Ok(BSize(size as $ty)) - } else { - Err(ParseError::Overflow) - } - } - } - )* - }; -} + fn from_str(s: &str) -> Result { + bsize_from_u64(parse_size(s.as_bytes())?) + } + } +}); -impl_from_str!(u8, u16, u32, u64, usize); +fn bsize_from_u64(size: u64) -> Result, ParseError> +where + T: ByteSize + TryFrom, +{ + T::try_from(size) + .map(BSize) + .map_err(|_| ParseError::Overflow) +} // This is derived from `parse-size` [1]. // diff --git a/bsize/src/traits/mod.rs b/bsize/src/traits/mod.rs index 462229a..c76936c 100644 --- a/bsize/src/traits/mod.rs +++ b/bsize/src/traits/mod.rs @@ -12,186 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -macro_rules! impl_marker { - ($t:ident for $($ty:ty),* $(,)?) => ($( - impl $t for $ty {} - )*) -} - mod private { pub trait Sealed {} - impl_marker!(Sealed for u8, u16, u32, u64, usize); -} - -/// A marker trait for all supported byte size underneath type. -pub trait ByteSize: private::Sealed {} -impl_marker!(ByteSize for u8, u16, u32, u64, usize); - -macro_rules! define_unit_traits { - ( - trait_prefix = [$($trait_prefix:tt)*]; - const_bound = [$($const_bound:tt)*]; - ) => { - /// A trait for byte size payload types that can represent kilobyte-scale units. - pub $($trait_prefix)* trait KiloByteSize: ByteSize + $($const_bound)* Mul + Sized { - /// Number of bytes in one kilobyte. - const KB: Self; - - /// Number of bytes in one kibibyte. - const KIB: Self; - } - - /// A trait for byte size payload types that can represent megabyte-scale units. - pub $($trait_prefix)* trait MegaByteSize: $($const_bound)* KiloByteSize { - /// Number of bytes in one megabyte. - const MB: Self; - - /// Number of bytes in one mebibyte. - const MIB: Self; - } - - /// A trait for byte size payload types that can represent gigabyte-scale units. - pub $($trait_prefix)* trait GigaByteSize: $($const_bound)* MegaByteSize { - /// Number of bytes in one gigabyte. - const GB: Self; - - /// Number of bytes in one gibibyte. - const GIB: Self; - } - - /// A trait for byte size payload types that can represent terabyte-scale units. - pub $($trait_prefix)* trait TeraByteSize: $($const_bound)* GigaByteSize { - /// Number of bytes in one terabyte. - const TB: Self; - - /// Number of bytes in one tebibyte. - const TIB: Self; - } - - /// A trait for byte size payload types that can represent petabyte-scale units. - pub $($trait_prefix)* trait PetaByteSize: $($const_bound)* TeraByteSize { - /// Number of bytes in one petabyte. - const PB: Self; - - /// Number of bytes in one pebibyte. - const PIB: Self; - } - - /// A trait for byte size payload types that can represent exabyte-scale units. - pub $($trait_prefix)* trait ExaByteSize: $($const_bound)* PetaByteSize { - /// Number of bytes in one exabyte. - const EB: Self; - - /// Number of bytes in one exbibyte. - const EIB: Self; - } - }; -} - -macro_rules! impl_size_trait { - ([$($impl_prefix:tt)*] $trait:ident for $ty:ty { $($name:ident = $value:literal),* $(,)? }) => { - $($impl_prefix)* impl $trait for $ty { - $(const $name: Self = $value;)* - } - }; -} - -macro_rules! impl_usize_size_traits { - ([$($impl_prefix:tt)*] through_kilo) => { - impl_size_trait!([$($impl_prefix)*] KiloByteSize for usize { - KB = 1_000, - KIB = 1_024, - }); - }; - ([$($impl_prefix:tt)*] through_giga) => { - impl_usize_size_traits!([$($impl_prefix)*] through_kilo); - impl_size_trait!([$($impl_prefix)*] MegaByteSize for usize { - MB = 1_000_000, - MIB = 1_048_576, - }); - impl_size_trait!([$($impl_prefix)*] GigaByteSize for usize { - GB = 1_000_000_000, - GIB = 1_073_741_824, - }); - }; - ([$($impl_prefix:tt)*] through_exa) => { - impl_usize_size_traits!([$($impl_prefix)*] through_giga); - impl_size_trait!([$($impl_prefix)*] TeraByteSize for usize { - TB = 1_000_000_000_000, - TIB = 1_099_511_627_776, - }); - impl_size_trait!([$($impl_prefix)*] PetaByteSize for usize { - PB = 1_000_000_000_000_000, - PIB = 1_125_899_906_842_624, - }); - impl_size_trait!([$($impl_prefix)*] ExaByteSize for usize { - EB = 1_000_000_000_000_000_000, - EIB = 1_152_921_504_606_846_976, - }); - }; -} - -macro_rules! impl_unit_traits { - (impl_prefix = [$($impl_prefix:tt)*];) => { - impl_size_trait!([$($impl_prefix)*] KiloByteSize for u16 { - KB = 1_000, - KIB = 1_024, - }); - - impl_size_trait!([$($impl_prefix)*] KiloByteSize for u32 { - KB = 1_000, - KIB = 1_024, - }); - - impl_size_trait!([$($impl_prefix)*] MegaByteSize for u32 { - MB = 1_000_000, - MIB = 1_048_576, - }); - - impl_size_trait!([$($impl_prefix)*] GigaByteSize for u32 { - GB = 1_000_000_000, - GIB = 1_073_741_824, - }); - - impl_size_trait!([$($impl_prefix)*] KiloByteSize for u64 { - KB = 1_000, - KIB = 1_024, - }); - - impl_size_trait!([$($impl_prefix)*] MegaByteSize for u64 { - MB = 1_000_000, - MIB = 1_048_576, - }); - - impl_size_trait!([$($impl_prefix)*] GigaByteSize for u64 { - GB = 1_000_000_000, - GIB = 1_073_741_824, - }); - - impl_size_trait!([$($impl_prefix)*] TeraByteSize for u64 { - TB = 1_000_000_000_000, - TIB = 1_099_511_627_776, - }); - - impl_size_trait!([$($impl_prefix)*] PetaByteSize for u64 { - PB = 1_000_000_000_000_000, - PIB = 1_125_899_906_842_624, - }); - - impl_size_trait!([$($impl_prefix)*] ExaByteSize for u64 { - EB = 1_000_000_000_000_000_000, - EIB = 1_152_921_504_606_846_976, - }); - - #[cfg(target_pointer_width = "16")] - impl_usize_size_traits!([$($impl_prefix)*] through_kilo); - - #[cfg(target_pointer_width = "32")] - impl_usize_size_traits!([$($impl_prefix)*] through_giga); - - #[cfg(target_pointer_width = "64")] - impl_usize_size_traits!([$($impl_prefix)*] through_exa); - }; + impl Sealed for u8 {} + impl Sealed for u16 {} + impl Sealed for u32 {} + impl Sealed for u64 {} + impl Sealed for usize {} } #[cfg(feature = "nightly")] @@ -203,22 +30,3 @@ mod stable; pub use self::nightly::*; #[cfg(not(feature = "nightly"))] pub use self::stable::*; - -/// A trait for all displayable byte size underneath type. -pub trait Displayable: ByteSize { - /// Convert the byte size payload to a canonicalized floating point representation, - /// which will then be used for display purposes. - fn canonicalize(&self) -> f64; -} - -macro_rules! impl_displayable { - ($($ty:ty),* $(,)?) => ($( - impl Displayable for $ty { - fn canonicalize(&self) -> f64 { - *self as f64 - } - } - )*) -} - -impl_displayable!(u8, u16, u32, u64, usize); diff --git a/bsize/src/traits/nightly.rs b/bsize/src/traits/nightly.rs index 9e9c41a..1ad77d1 100644 --- a/bsize/src/traits/nightly.rs +++ b/bsize/src/traits/nightly.rs @@ -14,13 +14,109 @@ use core::ops::Mul; -use super::ByteSize; +use super::private; -define_unit_traits! { - trait_prefix = [const]; - const_bound = [[const]]; +/// A marker trait for all supported byte size underlying types. +pub const trait ByteSize: private::Sealed + Clone + Copy + Sized { + /// Returns the byte size as an approximate `f64`. + fn to_f64(&self) -> f64; } -impl_unit_traits! { - impl_prefix = [const]; +/// A trait for byte size payload types that can represent kilobyte-scale units. +pub const trait KiloByteSize: [const] ByteSize + [const] Mul { + /// Number of bytes in one kilobyte. + const KB: Self; + + /// Number of bytes in one kibibyte. + const KIB: Self; +} + +/// A trait for byte size payload types that can represent megabyte-scale units. +pub const trait MegaByteSize: [const] KiloByteSize { + /// Number of bytes in one megabyte. + const MB: Self; + + /// Number of bytes in one mebibyte. + const MIB: Self; +} + +/// A trait for byte size payload types that can represent gigabyte-scale units. +pub const trait GigaByteSize: [const] MegaByteSize { + /// Number of bytes in one gigabyte. + const GB: Self; + + /// Number of bytes in one gibibyte. + const GIB: Self; +} + +/// A trait for byte size payload types that can represent terabyte-scale units. +pub const trait TeraByteSize: [const] GigaByteSize { + /// Number of bytes in one terabyte. + const TB: Self; + + /// Number of bytes in one tebibyte. + const TIB: Self; } + +/// A trait for byte size payload types that can represent petabyte-scale units. +pub const trait PetaByteSize: [const] TeraByteSize { + /// Number of bytes in one petabyte. + const PB: Self; + + /// Number of bytes in one pebibyte. + const PIB: Self; +} + +/// A trait for byte size payload types that can represent exabyte-scale units. +pub const trait ExaByteSize: [const] PetaByteSize { + /// Number of bytes in one exabyte. + const EB: Self; + + /// Number of bytes in one exbibyte. + const EIB: Self; +} + +macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] { + const impl ByteSize for Ty { + fn to_f64(&self) -> f64 { + *self as f64 + } + } +}); + +macroweave::repeat!((Trait, Ty, DecimalName, BinaryName, Scale) in [ + (KiloByteSize, u16, KB, KIB, 1), + (KiloByteSize, u32, KB, KIB, 1), + (MegaByteSize, u32, MB, MIB, 2), + (GigaByteSize, u32, GB, GIB, 3), + (KiloByteSize, u64, KB, KIB, 1), + (MegaByteSize, u64, MB, MIB, 2), + (GigaByteSize, u64, GB, GIB, 3), + (TeraByteSize, u64, TB, TIB, 4), + (PetaByteSize, u64, PB, PIB, 5), + (ExaByteSize, u64, EB, EIB, 6), +] { + const impl Trait for Ty { + const DecimalName: Self = Ty::pow(1000, Scale); + const BinaryName: Self = Ty::pow(1024, Scale); + } +}); + +macroweave::repeat!((PointerWidth, Trait, DecimalName, BinaryName, Scale) in [ + ("16", KiloByteSize, KB, KIB, 1), + ("32", KiloByteSize, KB, KIB, 1), + ("32", MegaByteSize, MB, MIB, 2), + ("32", GigaByteSize, GB, GIB, 3), + ("64", KiloByteSize, KB, KIB, 1), + ("64", MegaByteSize, MB, MIB, 2), + ("64", GigaByteSize, GB, GIB, 3), + ("64", TeraByteSize, TB, TIB, 4), + ("64", PetaByteSize, PB, PIB, 5), + ("64", ExaByteSize, EB, EIB, 6), +] { + #[cfg(target_pointer_width = PointerWidth)] + const impl Trait for usize { + const DecimalName: Self = usize::pow(1000, Scale); + const BinaryName: Self = usize::pow(1024, Scale); + } +}); diff --git a/bsize/src/traits/stable.rs b/bsize/src/traits/stable.rs index 8181837..982f0d0 100644 --- a/bsize/src/traits/stable.rs +++ b/bsize/src/traits/stable.rs @@ -14,13 +14,109 @@ use core::ops::Mul; -use super::ByteSize; +use super::private; -define_unit_traits! { - trait_prefix = []; - const_bound = []; +/// A marker trait for all supported byte size underlying types. +pub trait ByteSize: private::Sealed + Clone + Copy + Sized { + /// Returns the byte size as an approximate `f64`. + fn to_f64(&self) -> f64; } -impl_unit_traits! { - impl_prefix = []; +/// A trait for byte size payload types that can represent kilobyte-scale units. +pub trait KiloByteSize: ByteSize + Mul { + /// Number of bytes in one kilobyte. + const KB: Self; + + /// Number of bytes in one kibibyte. + const KIB: Self; +} + +/// A trait for byte size payload types that can represent megabyte-scale units. +pub trait MegaByteSize: KiloByteSize { + /// Number of bytes in one megabyte. + const MB: Self; + + /// Number of bytes in one mebibyte. + const MIB: Self; +} + +/// A trait for byte size payload types that can represent gigabyte-scale units. +pub trait GigaByteSize: MegaByteSize { + /// Number of bytes in one gigabyte. + const GB: Self; + + /// Number of bytes in one gibibyte. + const GIB: Self; +} + +/// A trait for byte size payload types that can represent terabyte-scale units. +pub trait TeraByteSize: GigaByteSize { + /// Number of bytes in one terabyte. + const TB: Self; + + /// Number of bytes in one tebibyte. + const TIB: Self; } + +/// A trait for byte size payload types that can represent petabyte-scale units. +pub trait PetaByteSize: TeraByteSize { + /// Number of bytes in one petabyte. + const PB: Self; + + /// Number of bytes in one pebibyte. + const PIB: Self; +} + +/// A trait for byte size payload types that can represent exabyte-scale units. +pub trait ExaByteSize: PetaByteSize { + /// Number of bytes in one exabyte. + const EB: Self; + + /// Number of bytes in one exbibyte. + const EIB: Self; +} + +macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] { + impl ByteSize for Ty { + fn to_f64(&self) -> f64 { + *self as f64 + } + } +}); + +macroweave::repeat!((Trait, Ty, DecimalName, BinaryName, Scale) in [ + (KiloByteSize, u16, KB, KIB, 1), + (KiloByteSize, u32, KB, KIB, 1), + (MegaByteSize, u32, MB, MIB, 2), + (GigaByteSize, u32, GB, GIB, 3), + (KiloByteSize, u64, KB, KIB, 1), + (MegaByteSize, u64, MB, MIB, 2), + (GigaByteSize, u64, GB, GIB, 3), + (TeraByteSize, u64, TB, TIB, 4), + (PetaByteSize, u64, PB, PIB, 5), + (ExaByteSize, u64, EB, EIB, 6), +] { + impl Trait for Ty { + const DecimalName: Self = Ty::pow(1000, Scale); + const BinaryName: Self = Ty::pow(1024, Scale); + } +}); + +macroweave::repeat!((PointerWidth, Trait, DecimalName, BinaryName, Scale) in [ + ("16", KiloByteSize, KB, KIB, 1), + ("32", KiloByteSize, KB, KIB, 1), + ("32", MegaByteSize, MB, MIB, 2), + ("32", GigaByteSize, GB, GIB, 3), + ("64", KiloByteSize, KB, KIB, 1), + ("64", MegaByteSize, MB, MIB, 2), + ("64", GigaByteSize, GB, GIB, 3), + ("64", TeraByteSize, TB, TIB, 4), + ("64", PetaByteSize, PB, PIB, 5), + ("64", ExaByteSize, EB, EIB, 6), +] { + #[cfg(target_pointer_width = PointerWidth)] + impl Trait for usize { + const DecimalName: Self = usize::pow(1000, Scale); + const BinaryName: Self = usize::pow(1024, Scale); + } +}); diff --git a/bsize/src/types/mod.rs b/bsize/src/types/mod.rs index 6c9e3f0..736401b 100644 --- a/bsize/src/types/mod.rs +++ b/bsize/src/types/mod.rs @@ -56,92 +56,6 @@ impl BSize { } } -macro_rules! impl_accessors { - ($ty:ty => { $($name:ident = $trait:ident::$size:ident => $unit:literal),* $(,)? }) => { - impl BSize<$ty> { - $( - #[doc = concat!("Returns byte count as ", $unit, ".")] - /// - /// The result is approximate when the byte count cannot be - /// represented exactly as `f64`. - #[inline(always)] - pub const fn $name(&self) -> f64 { - (self.0 as f64) / (<$ty as $crate::traits::$trait>::$size as f64) - } - )* - } - }; -} - -macro_rules! impl_usize_accessors { - (through_kilo) => { - impl_accessors!(usize => { - as_kb = KiloByteSize::KB => "kilobytes", - as_kib = KiloByteSize::KIB => "kibibytes", - }); - }; - (through_giga) => { - impl_usize_accessors!(through_kilo); - impl_accessors!(usize => { - as_mb = MegaByteSize::MB => "megabytes", - as_mib = MegaByteSize::MIB => "mebibytes", - as_gb = GigaByteSize::GB => "gigabytes", - as_gib = GigaByteSize::GIB => "gibibytes", - }); - }; - (through_exa) => { - impl_usize_accessors!(through_giga); - impl_accessors!(usize => { - as_tb = TeraByteSize::TB => "terabytes", - as_tib = TeraByteSize::TIB => "tebibytes", - as_pb = PetaByteSize::PB => "petabytes", - as_pib = PetaByteSize::PIB => "pebibytes", - as_eb = ExaByteSize::EB => "exabytes", - as_eib = ExaByteSize::EIB => "exbibytes", - }); - }; -} - -macro_rules! impl_unit_accessors { - () => { - impl_accessors!(u16 => { - as_kb = KiloByteSize::KB => "kilobytes", - as_kib = KiloByteSize::KIB => "kibibytes", - }); - - impl_accessors!(u32 => { - as_kb = KiloByteSize::KB => "kilobytes", - as_kib = KiloByteSize::KIB => "kibibytes", - as_mb = MegaByteSize::MB => "megabytes", - as_mib = MegaByteSize::MIB => "mebibytes", - as_gb = GigaByteSize::GB => "gigabytes", - as_gib = GigaByteSize::GIB => "gibibytes", - }); - - impl_accessors!(u64 => { - as_kb = KiloByteSize::KB => "kilobytes", - as_kib = KiloByteSize::KIB => "kibibytes", - as_mb = MegaByteSize::MB => "megabytes", - as_mib = MegaByteSize::MIB => "mebibytes", - as_gb = GigaByteSize::GB => "gigabytes", - as_gib = GigaByteSize::GIB => "gibibytes", - as_tb = TeraByteSize::TB => "terabytes", - as_tib = TeraByteSize::TIB => "tebibytes", - as_pb = PetaByteSize::PB => "petabytes", - as_pib = PetaByteSize::PIB => "pebibytes", - as_eb = ExaByteSize::EB => "exabytes", - as_eib = ExaByteSize::EIB => "exbibytes", - }); - - #[cfg(target_pointer_width = "16")] - impl_usize_accessors!(through_kilo); - #[cfg(target_pointer_width = "32")] - impl_usize_accessors!(through_giga); - #[cfg(target_pointer_width = "64")] - impl_usize_accessors!(through_exa); - }; -} - #[cfg(feature = "nightly")] mod nightly; #[cfg(not(feature = "nightly"))] @@ -150,16 +64,7 @@ mod stable; #[cfg(test)] mod tests { use super::BSize; - - fn assert_close(actual: f64, expected: f64) { - let delta = (actual - expected).abs(); - let tolerance = f64::EPSILON; - - assert!( - delta <= tolerance, - "actual: {actual}, expected: {expected}, delta: {delta}, tolerance: {tolerance}", - ); - } + use crate::assert_close; #[test] fn defaults() { @@ -175,6 +80,15 @@ mod tests { assert_eq!(BSize::::b(2).0, 2); } + #[test] + fn returns_byte_units() { + assert_close(BSize::::b(2).as_b(), 2.0); + assert_close(BSize::::b(2).as_b(), 2.0); + assert_close(BSize::::b(2).as_b(), 2.0); + assert_close(BSize::::b(2).as_b(), 2.0); + assert_close(BSize::::b(2).as_b(), 2.0); + } + #[test] fn maps_underlying_byte_count() { assert_eq!( @@ -249,6 +163,45 @@ mod tests { assert_close(BSize::::eib(2).as_eib(), 2.0); } + #[cfg(target_pointer_width = "16")] + #[test] + fn returns_usize_units() { + assert_eq!(BSize::::kb(2).0, 2_000); + assert_eq!(BSize::::kib(2).0, 2_048); + assert_close(BSize::::kb(2).as_kb(), 2.0); + assert_close(BSize::::kib(2).as_kib(), 2.0); + } + + #[cfg(target_pointer_width = "32")] + #[test] + fn returns_usize_units() { + assert_eq!(BSize::::kb(2).0, 2_000); + assert_eq!(BSize::::kib(2).0, 2_048); + assert_close(BSize::::kb(2).as_kb(), 2.0); + assert_close(BSize::::kib(2).as_kib(), 2.0); + assert_eq!(BSize::::gb(2).0, 2_000_000_000); + assert_eq!(BSize::::gib(2).0, 2_147_483_648); + assert_close(BSize::::gb(2).as_gb(), 2.0); + assert_close(BSize::::gib(2).as_gib(), 2.0); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn returns_usize_units() { + assert_eq!(BSize::::kb(2).0, 2_000); + assert_eq!(BSize::::kib(2).0, 2_048); + assert_close(BSize::::kb(2).as_kb(), 2.0); + assert_close(BSize::::kib(2).as_kib(), 2.0); + assert_eq!(BSize::::gb(2).0, 2_000_000_000); + assert_eq!(BSize::::gib(2).0, 2_147_483_648); + assert_close(BSize::::gb(2).as_gb(), 2.0); + assert_close(BSize::::gib(2).as_gib(), 2.0); + assert_eq!(BSize::::eb(2).0, 2_000_000_000_000_000_000); + assert_eq!(BSize::::eib(2).0, 2_305_843_009_213_693_952); + assert_close(BSize::::eb(2).as_eb(), 2.0); + assert_close(BSize::::eib(2).as_eib(), 2.0); + } + #[test] fn returns_large_fractional_u64_units() { let bytes = 9_876_543_210_987_654_321_u64; diff --git a/bsize/src/types/nightly.rs b/bsize/src/types/nightly.rs index fc564ea..3cf6602 100644 --- a/bsize/src/types/nightly.rs +++ b/bsize/src/types/nightly.rs @@ -13,6 +13,7 @@ // limitations under the License. use super::BSize; +use crate::traits::ByteSize; use crate::traits::ExaByteSize; use crate::traits::GigaByteSize; use crate::traits::KiloByteSize; @@ -20,72 +21,289 @@ use crate::traits::MegaByteSize; use crate::traits::PetaByteSize; use crate::traits::TeraByteSize; -macro_rules! impl_constructors { - ($trait:ident => { $($name:ident = $size:ident),* $(,)? }) => { - impl BSize { - $( - #[doc = concat!( - "Constructs a byte size wrapper from a quantity of `", - stringify!($name), - "` units." - )] - #[inline(always)] - pub const fn $name(size: T) -> Self - where - T: [const] $trait, - { - BSize(size * T::$size) - } - )* - } - }; +impl BSize { + /// Returns byte count as bytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. Use `.0` or [`BSize::with`] for the exact underlying + /// integer value. + #[inline(always)] + pub const fn as_b(&self) -> f64 + where + T: [const] ByteSize, + { + self.0.to_f64() + } } -impl_constructors!(KiloByteSize => { - kb = KB, - kib = KIB, -}); +impl BSize { + /// Constructs a byte size wrapper from a quantity of `kb` units. + #[inline(always)] + pub const fn kb(size: T) -> Self + where + T: [const] KiloByteSize, + { + BSize(size * T::KB) + } -impl_constructors!(MegaByteSize => { - mb = MB, - mib = MIB, -}); + /// Constructs a byte size wrapper from a quantity of `kib` units. + #[inline(always)] + pub const fn kib(size: T) -> Self + where + T: [const] KiloByteSize, + { + BSize(size * T::KIB) + } -impl_constructors!(GigaByteSize => { - gb = GB, - gib = GIB, -}); + /// Returns byte count as kilobytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_kb(&self) -> f64 + where + T: [const] KiloByteSize, + { + self.0.to_f64() / T::KB.to_f64() + } -impl_constructors!(TeraByteSize => { - tb = TB, - tib = TIB, -}); + /// Returns byte count as kibibytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_kib(&self) -> f64 + where + T: [const] KiloByteSize, + { + self.0.to_f64() / T::KIB.to_f64() + } +} -impl_constructors!(PetaByteSize => { - pb = PB, - pib = PIB, -}); +impl BSize { + /// Constructs a byte size wrapper from a quantity of `mb` units. + #[inline(always)] + pub const fn mb(size: T) -> Self + where + T: [const] MegaByteSize, + { + BSize(size * T::MB) + } -impl_constructors!(ExaByteSize => { - eb = EB, - eib = EIB, -}); + /// Constructs a byte size wrapper from a quantity of `mib` units. + #[inline(always)] + pub const fn mib(size: T) -> Self + where + T: [const] MegaByteSize, + { + BSize(size * T::MIB) + } -impl_unit_accessors!(); + /// Returns byte count as megabytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_mb(&self) -> f64 + where + T: [const] MegaByteSize, + { + self.0.to_f64() / T::MB.to_f64() + } -#[cfg(test)] -mod tests { - use super::BSize; + /// Returns byte count as mebibytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_mib(&self) -> f64 + where + T: [const] MegaByteSize, + { + self.0.to_f64() / T::MIB.to_f64() + } +} + +impl BSize { + /// Constructs a byte size wrapper from a quantity of `gb` units. + #[inline(always)] + pub const fn gb(size: T) -> Self + where + T: [const] GigaByteSize, + { + BSize(size * T::GB) + } + + /// Constructs a byte size wrapper from a quantity of `gib` units. + #[inline(always)] + pub const fn gib(size: T) -> Self + where + T: [const] GigaByteSize, + { + BSize(size * T::GIB) + } + + /// Returns byte count as gigabytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_gb(&self) -> f64 + where + T: [const] GigaByteSize, + { + self.0.to_f64() / T::GB.to_f64() + } + + /// Returns byte count as gibibytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_gib(&self) -> f64 + where + T: [const] GigaByteSize, + { + self.0.to_f64() / T::GIB.to_f64() + } +} + +impl BSize { + /// Constructs a byte size wrapper from a quantity of `tb` units. + #[inline(always)] + pub const fn tb(size: T) -> Self + where + T: [const] TeraByteSize, + { + BSize(size * T::TB) + } + + /// Constructs a byte size wrapper from a quantity of `tib` units. + #[inline(always)] + pub const fn tib(size: T) -> Self + where + T: [const] TeraByteSize, + { + BSize(size * T::TIB) + } - fn assert_close(actual: f64, expected: f64) { - let delta = (actual - expected).abs(); - let tolerance = f64::EPSILON; + /// Returns byte count as terabytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_tb(&self) -> f64 + where + T: [const] TeraByteSize, + { + self.0.to_f64() / T::TB.to_f64() + } + + /// Returns byte count as tebibytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_tib(&self) -> f64 + where + T: [const] TeraByteSize, + { + self.0.to_f64() / T::TIB.to_f64() + } +} + +impl BSize { + /// Constructs a byte size wrapper from a quantity of `pb` units. + #[inline(always)] + pub const fn pb(size: T) -> Self + where + T: [const] PetaByteSize, + { + BSize(size * T::PB) + } + + /// Constructs a byte size wrapper from a quantity of `pib` units. + #[inline(always)] + pub const fn pib(size: T) -> Self + where + T: [const] PetaByteSize, + { + BSize(size * T::PIB) + } + + /// Returns byte count as petabytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_pb(&self) -> f64 + where + T: [const] PetaByteSize, + { + self.0.to_f64() / T::PB.to_f64() + } - assert!( - delta <= tolerance, - "actual: {actual}, expected: {expected}, delta: {delta}, tolerance: {tolerance}", - ); + /// Returns byte count as pebibytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_pib(&self) -> f64 + where + T: [const] PetaByteSize, + { + self.0.to_f64() / T::PIB.to_f64() } +} + +impl BSize { + /// Constructs a byte size wrapper from a quantity of `eb` units. + #[inline(always)] + pub const fn eb(size: T) -> Self + where + T: [const] ExaByteSize, + { + BSize(size * T::EB) + } + + /// Constructs a byte size wrapper from a quantity of `eib` units. + #[inline(always)] + pub const fn eib(size: T) -> Self + where + T: [const] ExaByteSize, + { + BSize(size * T::EIB) + } + + /// Returns byte count as exabytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_eb(&self) -> f64 + where + T: [const] ExaByteSize, + { + self.0.to_f64() / T::EB.to_f64() + } + + /// Returns byte count as exbibytes. + /// + /// The result is approximate when the byte count cannot be represented + /// exactly as `f64`. + #[inline(always)] + pub const fn as_eib(&self) -> f64 + where + T: [const] ExaByteSize, + { + self.0.to_f64() / T::EIB.to_f64() + } +} + +#[cfg(test)] +mod tests { + use super::BSize; + use crate::assert_close; #[test] fn infers_constructor_type_from_argument() { @@ -100,42 +318,12 @@ mod tests { assert_eq!(SIZE, BSize::b(16 * 1_024)); } - #[cfg(target_pointer_width = "16")] #[test] - fn returns_usize_units() { - assert_eq!(BSize::kb(2_usize).0, 2_000); - assert_eq!(BSize::kib(2_usize).0, 2_048); - assert_close(BSize::kb(2_usize).as_kb(), 2.0); - assert_close(BSize::kib(2_usize).as_kib(), 2.0); - } + fn inferred_accessors_are_const() { + const BYTES: f64 = BSize::b(16_u64).as_b(); + const KIB: f64 = BSize::kib(16_u64).as_kib(); - #[cfg(target_pointer_width = "32")] - #[test] - fn returns_usize_units() { - assert_eq!(BSize::kb(2_usize).0, 2_000); - assert_eq!(BSize::kib(2_usize).0, 2_048); - assert_close(BSize::kb(2_usize).as_kb(), 2.0); - assert_close(BSize::kib(2_usize).as_kib(), 2.0); - assert_eq!(BSize::gb(2_usize).0, 2_000_000_000); - assert_eq!(BSize::gib(2_usize).0, 2_147_483_648); - assert_close(BSize::gb(2_usize).as_gb(), 2.0); - assert_close(BSize::gib(2_usize).as_gib(), 2.0); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn returns_usize_units() { - assert_eq!(BSize::kb(2_usize).0, 2_000); - assert_eq!(BSize::kib(2_usize).0, 2_048); - assert_close(BSize::kb(2_usize).as_kb(), 2.0); - assert_close(BSize::kib(2_usize).as_kib(), 2.0); - assert_eq!(BSize::gb(2_usize).0, 2_000_000_000); - assert_eq!(BSize::gib(2_usize).0, 2_147_483_648); - assert_close(BSize::gb(2_usize).as_gb(), 2.0); - assert_close(BSize::gib(2_usize).as_gib(), 2.0); - assert_eq!(BSize::eb(2_usize).0, 2_000_000_000_000_000_000); - assert_eq!(BSize::eib(2_usize).0, 2_305_843_009_213_693_952); - assert_close(BSize::eb(2_usize).as_eb(), 2.0); - assert_close(BSize::eib(2_usize).as_eib(), 2.0); + assert_close(BYTES, 16.0); + assert_close(KIB, 16.0); } } diff --git a/bsize/src/types/stable.rs b/bsize/src/types/stable.rs index 83ac75f..668cfe4 100644 --- a/bsize/src/types/stable.rs +++ b/bsize/src/types/stable.rs @@ -14,141 +14,156 @@ use super::BSize; -macro_rules! impl_constructors { - ($ty:ty => { $($name:ident = $trait:ident::$size:ident),* $(,)? }) => { - impl BSize<$ty> { - $( - #[doc = concat!( - "Constructs a byte size wrapper from a quantity of `", - stringify!($name), - "` units." - )] - #[inline(always)] - pub const fn $name(size: $ty) -> Self { - BSize(size * <$ty as $crate::traits::$trait>::$size) - } - )* +macroweave::repeat!((Ty, Name, Trait, Size) in [ + (u16, kb, KiloByteSize, KB), + (u16, kib, KiloByteSize, KIB), + (u32, kb, KiloByteSize, KB), + (u32, kib, KiloByteSize, KIB), + (u32, mb, MegaByteSize, MB), + (u32, mib, MegaByteSize, MIB), + (u32, gb, GigaByteSize, GB), + (u32, gib, GigaByteSize, GIB), + (u64, kb, KiloByteSize, KB), + (u64, kib, KiloByteSize, KIB), + (u64, mb, MegaByteSize, MB), + (u64, mib, MegaByteSize, MIB), + (u64, gb, GigaByteSize, GB), + (u64, gib, GigaByteSize, GIB), + (u64, tb, TeraByteSize, TB), + (u64, tib, TeraByteSize, TIB), + (u64, pb, PetaByteSize, PB), + (u64, pib, PetaByteSize, PIB), + (u64, eb, ExaByteSize, EB), + (u64, eib, ExaByteSize, EIB), +] { + impl BSize { + #[doc = concat!( + "Constructs a byte size wrapper from a quantity of `", + stringify!(Name), + "` units." + )] + #[inline(always)] + pub const fn Name(size: Ty) -> Self { + BSize(size * ::Size) } - }; -} - -macro_rules! impl_usize_constructors { - (through_kilo) => { - impl_constructors!(usize => { - kb = KiloByteSize::KB, - kib = KiloByteSize::KIB, - }); - }; - (through_giga) => { - impl_usize_constructors!(through_kilo); - impl_constructors!(usize => { - mb = MegaByteSize::MB, - mib = MegaByteSize::MIB, - gb = GigaByteSize::GB, - gib = GigaByteSize::GIB, - }); - }; - (through_exa) => { - impl_usize_constructors!(through_giga); - impl_constructors!(usize => { - tb = TeraByteSize::TB, - tib = TeraByteSize::TIB, - pb = PetaByteSize::PB, - pib = PetaByteSize::PIB, - eb = ExaByteSize::EB, - eib = ExaByteSize::EIB, - }); - }; -} - -impl_constructors!(u16 => { - kb = KiloByteSize::KB, - kib = KiloByteSize::KIB, -}); - -impl_constructors!(u32 => { - kb = KiloByteSize::KB, - kib = KiloByteSize::KIB, - mb = MegaByteSize::MB, - mib = MegaByteSize::MIB, - gb = GigaByteSize::GB, - gib = GigaByteSize::GIB, -}); - -impl_constructors!(u64 => { - kb = KiloByteSize::KB, - kib = KiloByteSize::KIB, - mb = MegaByteSize::MB, - mib = MegaByteSize::MIB, - gb = GigaByteSize::GB, - gib = GigaByteSize::GIB, - tb = TeraByteSize::TB, - tib = TeraByteSize::TIB, - pb = PetaByteSize::PB, - pib = PetaByteSize::PIB, - eb = ExaByteSize::EB, - eib = ExaByteSize::EIB, + } }); -#[cfg(target_pointer_width = "16")] -impl_usize_constructors!(through_kilo); -#[cfg(target_pointer_width = "32")] -impl_usize_constructors!(through_giga); -#[cfg(target_pointer_width = "64")] -impl_usize_constructors!(through_exa); - -impl_unit_accessors!(); - -#[cfg(test)] -mod tests { - use super::BSize; - - fn assert_close(actual: f64, expected: f64) { - let delta = (actual - expected).abs(); - let tolerance = f64::EPSILON; - - assert!( - delta <= tolerance, - "actual: {actual}, expected: {expected}, delta: {delta}, tolerance: {tolerance}", - ); +macroweave::repeat!((PointerWidth, Name, Trait, Size) in [ + ("16", kb, KiloByteSize, KB), + ("16", kib, KiloByteSize, KIB), + ("32", kb, KiloByteSize, KB), + ("32", kib, KiloByteSize, KIB), + ("32", mb, MegaByteSize, MB), + ("32", mib, MegaByteSize, MIB), + ("32", gb, GigaByteSize, GB), + ("32", gib, GigaByteSize, GIB), + ("64", kb, KiloByteSize, KB), + ("64", kib, KiloByteSize, KIB), + ("64", mb, MegaByteSize, MB), + ("64", mib, MegaByteSize, MIB), + ("64", gb, GigaByteSize, GB), + ("64", gib, GigaByteSize, GIB), + ("64", tb, TeraByteSize, TB), + ("64", tib, TeraByteSize, TIB), + ("64", pb, PetaByteSize, PB), + ("64", pib, PetaByteSize, PIB), + ("64", eb, ExaByteSize, EB), + ("64", eib, ExaByteSize, EIB), +] { + #[cfg(target_pointer_width = PointerWidth)] + impl BSize { + #[doc = concat!( + "Constructs a byte size wrapper from a quantity of `", + stringify!(Name), + "` units." + )] + #[inline(always)] + pub const fn Name(size: usize) -> Self { + BSize(size * ::Size) + } } +}); - #[cfg(target_pointer_width = "16")] - #[test] - fn returns_usize_units() { - assert_eq!(BSize::::kb(2).0, 2_000); - assert_eq!(BSize::::kib(2).0, 2_048); - assert_close(BSize::::kb(2).as_kb(), 2.0); - assert_close(BSize::::kib(2).as_kib(), 2.0); +macroweave::repeat!(Ty in [u8, u16, u32, u64, usize] { + impl BSize { + /// Returns byte count as bytes. + /// + /// The result is approximate when the byte count cannot be + /// represented exactly as `f64`. Use `.0` or [`BSize::with`] + /// for the exact underlying integer value. + #[inline(always)] + pub const fn as_b(&self) -> f64 { + self.0 as f64 + } } +}); - #[cfg(target_pointer_width = "32")] - #[test] - fn returns_usize_units() { - assert_eq!(BSize::::kb(2).0, 2_000); - assert_eq!(BSize::::kib(2).0, 2_048); - assert_close(BSize::::kb(2).as_kb(), 2.0); - assert_close(BSize::::kib(2).as_kib(), 2.0); - assert_eq!(BSize::::gb(2).0, 2_000_000_000); - assert_eq!(BSize::::gib(2).0, 2_147_483_648); - assert_close(BSize::::gb(2).as_gb(), 2.0); - assert_close(BSize::::gib(2).as_gib(), 2.0); +macroweave::repeat!((Ty, Name, Trait, Size, Unit) in [ + (u16, as_kb, KiloByteSize, KB, "kilobytes"), + (u16, as_kib, KiloByteSize, KIB, "kibibytes"), + (u32, as_kb, KiloByteSize, KB, "kilobytes"), + (u32, as_kib, KiloByteSize, KIB, "kibibytes"), + (u32, as_mb, MegaByteSize, MB, "megabytes"), + (u32, as_mib, MegaByteSize, MIB, "mebibytes"), + (u32, as_gb, GigaByteSize, GB, "gigabytes"), + (u32, as_gib, GigaByteSize, GIB, "gibibytes"), + (u64, as_kb, KiloByteSize, KB, "kilobytes"), + (u64, as_kib, KiloByteSize, KIB, "kibibytes"), + (u64, as_mb, MegaByteSize, MB, "megabytes"), + (u64, as_mib, MegaByteSize, MIB, "mebibytes"), + (u64, as_gb, GigaByteSize, GB, "gigabytes"), + (u64, as_gib, GigaByteSize, GIB, "gibibytes"), + (u64, as_tb, TeraByteSize, TB, "terabytes"), + (u64, as_tib, TeraByteSize, TIB, "tebibytes"), + (u64, as_pb, PetaByteSize, PB, "petabytes"), + (u64, as_pib, PetaByteSize, PIB, "pebibytes"), + (u64, as_eb, ExaByteSize, EB, "exabytes"), + (u64, as_eib, ExaByteSize, EIB, "exbibytes"), +] { + impl BSize { + #[doc = concat!("Returns byte count as ", Unit, ".")] + /// + /// The result is approximate when the byte count cannot be + /// represented exactly as `f64`. + #[inline(always)] + pub const fn Name(&self) -> f64 { + (self.0 as f64) / (::Size as f64) + } } +}); - #[cfg(target_pointer_width = "64")] - #[test] - fn returns_usize_units() { - assert_eq!(BSize::::kb(2).0, 2_000); - assert_eq!(BSize::::kib(2).0, 2_048); - assert_close(BSize::::kb(2).as_kb(), 2.0); - assert_close(BSize::::kib(2).as_kib(), 2.0); - assert_eq!(BSize::::gb(2).0, 2_000_000_000); - assert_eq!(BSize::::gib(2).0, 2_147_483_648); - assert_close(BSize::::gb(2).as_gb(), 2.0); - assert_close(BSize::::gib(2).as_gib(), 2.0); - assert_eq!(BSize::::eb(2).0, 2_000_000_000_000_000_000); - assert_eq!(BSize::::eib(2).0, 2_305_843_009_213_693_952); - assert_close(BSize::::eb(2).as_eb(), 2.0); - assert_close(BSize::::eib(2).as_eib(), 2.0); +macroweave::repeat!((PointerWidth, Name, Trait, Size, Unit) in [ + ("16", as_kb, KiloByteSize, KB, "kilobytes"), + ("16", as_kib, KiloByteSize, KIB, "kibibytes"), + ("32", as_kb, KiloByteSize, KB, "kilobytes"), + ("32", as_kib, KiloByteSize, KIB, "kibibytes"), + ("32", as_mb, MegaByteSize, MB, "megabytes"), + ("32", as_mib, MegaByteSize, MIB, "mebibytes"), + ("32", as_gb, GigaByteSize, GB, "gigabytes"), + ("32", as_gib, GigaByteSize, GIB, "gibibytes"), + ("64", as_kb, KiloByteSize, KB, "kilobytes"), + ("64", as_kib, KiloByteSize, KIB, "kibibytes"), + ("64", as_mb, MegaByteSize, MB, "megabytes"), + ("64", as_mib, MegaByteSize, MIB, "mebibytes"), + ("64", as_gb, GigaByteSize, GB, "gigabytes"), + ("64", as_gib, GigaByteSize, GIB, "gibibytes"), + ("64", as_tb, TeraByteSize, TB, "terabytes"), + ("64", as_tib, TeraByteSize, TIB, "tebibytes"), + ("64", as_pb, PetaByteSize, PB, "petabytes"), + ("64", as_pib, PetaByteSize, PIB, "pebibytes"), + ("64", as_eb, ExaByteSize, EB, "exabytes"), + ("64", as_eib, ExaByteSize, EIB, "exbibytes"), +] { + #[cfg(target_pointer_width = PointerWidth)] + impl BSize { + #[doc = concat!("Returns byte count as ", Unit, ".")] + /// + /// The result is approximate when the byte count cannot be + /// represented exactly as `f64`. + #[inline(always)] + pub const fn Name(&self) -> f64 { + (self.0 as f64) / (::Size as f64) + } } -} +});