diff --git a/CHANGELOG.md b/CHANGELOG.md index 045aabe..2fa1c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ # Changelog +## [0.6.0](https://github.com/Blobfolio/dactyl/releases/tag/v0.6.0) - 2023-10-15 + +### New + +* `IntDivFloat` trait + +### Changed + +* `SaturatingFrom` is now implemented to/from all primitive integer types, even in cases where saturation isn't ever necessary, like `T->T` +* `NiceU8::as_bytes2` is now `const` +* `NiceU8::as_str2` is now `const` +* Drop `num-traits` dependency + +### Deprecated + +* `div_mod` +* `int_div_float` (use `IntDivFloat::div_float` instead) + + ## [0.5.2](https://github.com/Blobfolio/dactyl/releases/tag/v0.5.2) - 2023-10-05 diff --git a/CREDITS.md b/CREDITS.md index 65c86f9..6da8471 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,8 +1,6 @@ # Project Dependencies Package: dactyl - Version: 0.5.2 - Generated: 2023-10-05 19:22:00 UTC + Version: 0.6.0 + Generated: 2023-10-16 01:12:30 UTC -| Package | Version | Author(s) | License | -| ---- | ---- | ---- | ---- | -| [num-traits](https://github.com/rust-num/num-traits) | 0.2.16 | The Rust Project Developers | Apache-2.0 or MIT | +This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index c04457a..b9358e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dactyl" -version = "0.5.2" +version = "0.6.0" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.70" @@ -27,9 +27,6 @@ bash-dir = "./" man-dir = "./" credits-dir = "./" -[dependencies] -num-traits = "0.2.*" - [dev-dependencies] brunch = "0.5.*" fastrand = "2" diff --git a/README.md b/README.md index b0c53b6..f1fedd5 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,19 @@ This crate provides a fast interface to "stringify" unsigned integers, formatted with commas at each thousand. It prioritizes speed and simplicity over configurability. -If your application just wants to quickly turn `1010` into `"1,010"`, `Dactyl` is a great choice. If your application requires locale awareness or other options, something like [`num-format`](https://crates.io/crates/num-format) would probably make more sense. +If your application just wants to quickly turn `1010` into `"1,010"`, Dactyl is a great choice. If your application requires locale awareness or other options, something like [`num-format`](https://crates.io/crates/num-format) would probably make more sense. Similar to [`itoa`](https://crates.io/crates/itoa), Dactyl writes ASCII conversions to a temporary buffer, but does so using fixed arrays sized for each type's maximum value, minimizing the allocation overhead for, say, tiny little `u8`s. Each type has its own struct, each of which works exactly the same way: -* [`NiceU8`] -* [`NiceU16`] -* [`NiceU32`] -* [`NiceU64`] - -(Note: support for `usize` values is folded into [`NiceU64`].) +* `NiceU8` +* `NiceU16` +* `NiceU32` +* `NiceU64` (also covers `usize`) +* `NiceFloat` +* `NiceElapsed` (for durations) +* `NicePercent` (for floats representing percentages) The intended use case is to simply call the appropriate `from()` for the type, then use either the `as_str()` or `as_bytes()` struct methods to retrieve the output in the desired format. Each struct also implements traits like `Deref`, `Display`, `AsRef`, `AsRef<[u8]>`, etc., if you prefer those. @@ -32,6 +33,14 @@ assert_eq!(NiceU16::from(11234_u16).as_str(), "11,234"); assert_eq!(NiceU16::from(11234_u16).as_bytes(), b"11,234"); ``` +But the niceness doesn't stop there. Dactyl provides several other structs, methods, and traits to performantly work with integers, such as: + +* `NoHash`: a passthrough hasher for integer `HashSet`/`HashMap` collections +* `traits::BytesToSigned`: signed integer parsing from byte slices +* `traits::BytesToUnsigned`: unsigned integer parsing from byte slices +* `traits::HexToSigned`: signed integer parsing from hex +* `traits::HexToUnsigned`: unsigned integer parsing from hex + ## Installation @@ -40,20 +49,11 @@ Add `dactyl` to your `dependencies` in `Cargo.toml`, like: ``` [dependencies] -dactyl = "0.5.*" +dactyl = "0.6.*" ``` -## Other - -This crate also contains a few more specialized "nice" structs: -* [`NiceFloat`] -* [`NiceElapsed`] -* [`NicePercent`] - - - ## License See also: [CREDITS.md](CREDITS.md) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..6ed2946 --- /dev/null +++ b/build.rs @@ -0,0 +1,334 @@ +/*! +# Dactyl: Build Script. + +This is used to pre-compile all of the integer-to-integer SaturatingFrom +implementations because they're an utter nightmare without some degree of +automation. + +But don't worry, it's still a nightmare. Haha. +*/ + +use std::{ + env, + fmt::{ + self, + Write, + }, + fs::File, + io::Write as ByteWrite, + path::PathBuf, +}; + + + +/// # Min/Max Trait. +/// +/// This trait lets us identify primitives and their lower/upper bounds, nice +/// and easy. +trait NumberExt: Copy { + const MIN_NUMBER: Self; + const MAX_NUMBER: Self; +} + +macro_rules! numext { + ($($ty:ty),+) => ($( + impl NumberExt for $ty { + const MIN_NUMBER: Self = Self::MIN; + const MAX_NUMBER: Self = Self::MAX; + } + )+); +} + +numext! { u8, u16, u32, u64, u128, i8, i16, i32, i64, i128 } + + + +#[derive(Clone, Copy)] +/// # A Number. +/// +/// This enum levels the playing field between integer values of different +/// types by upcasting everything to 128-bit. +/// +/// It also ensures that when printed, `_` separators are added to the right +/// place to keep the linter happy. +enum AnyNum { + Unsigned(u128), + Signed(i128), +} + +macro_rules! into_any { + ($($from:ty),+) => ($( + impl From<$from> for AnyNum { + #[allow(unused_comparisons)] + fn from(src: $from) -> Self { + if src < 0 { Self::Signed(src as i128) } + else { Self::Unsigned(src as u128) } + } + } + )+); +} + +into_any! { u8, u16, u32, u64, u128, i8, i16, i32, i64, i128 } + +impl fmt::Display for AnyNum { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Start with a straight string, writing immediately if the number is + // too small for separators. + let mut out = match self { + Self::Unsigned(n) => + if 1000.gt(n) { return write!(f, "{n}"); } + else { n.to_string() }, + Self::Signed(n) => + if (-999..1000).contains(n) { return write!(f, "{n}"); } + else { n.to_string() }, + }; + + // Note negativity, and strip the sign if it's there. + let neg = + if out.starts_with('-') { + out.remove(0); + true + } + else { false }; + + // Add _ delimiters every three places starting from the end. + let mut idx = out.len(); + while idx > 3 { + idx -= 3; + out.insert(idx, '_'); + } + + // Throw the negative sign back on, if any. + if neg { out.insert(0, '-'); } + + // Done! + f.write_str(&out) + } +} + +impl AnyNum { + /// # Inner as Signed. + /// + /// Return the inner value as an `i128` regardless of sign. + const fn signed_inner(self) -> i128 { + match self { + Self::Unsigned(n) =>n as i128, + Self::Signed(n) => n, + } + } + + /// # Inner as Unsigned. + /// + /// Return the inner value as a `u128` regardless of sign. + const fn unsigned_inner(self) -> u128 { + match self { + Self::Unsigned(n) => n, + Self::Signed(n) => n as u128, + } + } +} + + + +/// # Helper: Write Basic From/To Implementations. +macro_rules! wrt { + ($out:ident, $to:ty as $alias:ty, $($from:ty),+) => ($( + // The top. + writeln!( + &mut $out, + concat!( + "impl SaturatingFrom<", stringify!($from), "> for ", stringify!($to), "{{\n", + "\t#[doc = \"", "# Saturating From `", stringify!($from), "`\"]\n", + "\t#[doc = \"\"]\n", + "\t#[doc = \"", "This method will safely recast any `", stringify!($from), "` into a `", stringify!($to), "`, clamping the values to `", stringify!($to), "::MIN..=", stringify!($to), "::MAX` to prevent overflow or wrapping.", "\"]\n", + "\tfn saturating_from(src: ", stringify!($from), ") -> Self {{", + ), + ).unwrap(); + // The body. + write_condition::<$alias, $from>(&mut $out); + // The bottom. + writeln!( + &mut $out, + "\t}}\n}}", + ).unwrap(); + )+); + ($out:ident, $to:ty, $($from:ty),+) => ( + wrt!($out, $to as $to, $($from),+); + ); +} + +/// # Helper: Write Noop Implementations. +macro_rules! wrt_self { + ($out:ident, $($to:ty),+) => ($( + writeln!( + &mut $out, + concat!( + "impl SaturatingFrom for ", stringify!($to), "{{\n", + "\t#[doc = \"# Saturating From `Self`\"]\n", + "\t#[doc = \"\"]\n", + "\t#[doc = \"`Self`-to-`Self` (obviously) requires no saturation; this implementation is a noop.\"]\n", + "\t#[inline]", + "\tfn saturating_from(src: Self) -> Self {{ src }}\n", + "}}", + ), + ).unwrap(); + )+); +} + + + +fn main() { + // Make sure our formatting looks right. + assert_eq!(AnyNum::from(12345_u32).to_string(), "12_345", "Bug: Number formatting is wrong!"); + assert_eq!(AnyNum::from(-12345_i32).to_string(), "-12_345", "Bug: Number formatting is wrong!"); + + // Compile and write the impls! + let data = build_impls(); + File::create(out_path("dactyl-saturation.rs")) + .and_then(|mut f| f.write_all(data.as_bytes()).and_then(|_| f.flush())) + .expect("Unable to save drive data."); +} + +/// # Build Impls. +/// +/// Generate "code" corresponding to all of the integer-to-integer +/// SaturatingFrom implementations, and return it as a string. +/// +/// This would be fairly compact were it not for Rust's sized types, which +/// require cfg-gated module wrappers. +/// +/// TODO: if it ever becomes possible for a bulid script to share pointer +/// widths with the target (rather than always using the host), clean up the +/// sized crap. Haha. +fn build_impls() -> String { + let mut out = String::new(); + + // Into Unsigned. + wrt!(out, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + wrt!(out, u16, u8, u32, u64, u128, i8, i16, i32, i64, i128); + wrt!(out, u32, u8, u16, u64, u128, i8, i16, i32, i64, i128); + wrt!(out, u64, u8, u16, u32, u128, i8, i16, i32, i64, i128); + wrt!(out, u128, u8, u16, u32, u64, i8, i16, i32, i64, i128); + + // Into Signed. + wrt!(out, i8, u8, u16, u32, u64, u128, i16, i32, i64, i128); + wrt!(out, i16, u8, u16, u32, u64, u128, i8, i32, i64, i128); + wrt!(out, i32, u8, u16, u32, u64, u128, i8, i16, i64, i128); + wrt!(out, i64, u8, u16, u32, u64, u128, i8, i16, i32, i128); + wrt!(out, i128, u8, u16, u32, u64, u128, i8, i16, i32, i64 ); + + // Noop casts. + wrt_self!(out, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + + // Write cfg-gated modules containing all of the sized implementations for + // a given pointer width. Thankfully we only have to enumerate the into + // impls; generics can be used for the equivalent froms. + macro_rules! sized { + ($unsigned:ty, $signed:ty) => ( + writeln!( + &mut out, + " +#[cfg(target_pointer_width = \"{}\")] +mod sized {{ + use super::SaturatingFrom; + + impl> SaturatingFrom for T {{ + /// # Saturating From `usize` + /// + /// This blanket implementation uses `{unsigned}` as a go-between, since it is equivalent to `usize`. + fn saturating_from(src: usize) -> T {{ + T::saturating_from(src as {unsigned}) + }} + }} + impl> SaturatingFrom for T {{ + /// # Saturating From `isize` + /// + /// This blanket implementation uses `{signed}` as a go-between, since it is equivalent to `isize`. + fn saturating_from(src: isize) -> T {{ + T::saturating_from(src as {signed}) + }} + }}", + <$unsigned>::BITS, + unsigned=stringify!($unsigned), + signed=stringify!($signed), + ).unwrap(); + + // Write all of the into implementations for our sized types into + // a separate buffer, then iterate over that so we can tweak the + // indentation. + let mut tmp = String::new(); + wrt!(tmp, usize as $unsigned, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + wrt!(tmp, isize as $signed, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + for line in tmp.lines() { + out.push('\t'); + out.push_str(line); + out.push('\n'); + } + + // Close off the module. + out.push_str("}\n"); + ); + } + + // Actually write the sized modules. + sized!(u16, i16); + sized!(u32, i32); + sized!(u64, i64); + sized!(u128, i128); + + // Done! + out +} + +/// # Out path. +/// +/// This generates a (file/dir) path relative to `OUT_DIR`. +fn out_path(name: &str) -> PathBuf { + let dir = env::var("OUT_DIR").expect("Missing OUT_DIR."); + let mut out = std::fs::canonicalize(dir).expect("Missing OUT_DIR."); + out.push(name); + out +} + +/// # Write Cast Conditional. +/// +/// This writes the body of a `saturating_from()` block, clamping as needed. +/// It feels wrong using a method for this, but because of the conditional +/// logic it's cleaner than shoving it into a macro. +fn write_condition(out: &mut String) +where TO: NumberExt + Into, FROM: NumberExt + Into { + // Minimum clamp. + let to: AnyNum = TO::MIN_NUMBER.into(); + let from: AnyNum = FROM::MIN_NUMBER.into(); + let min = + if from.signed_inner() < to.signed_inner() { Some(to) } + else { None }; + + // Maximum clamp. + let to: AnyNum = TO::MAX_NUMBER.into(); + let from: AnyNum = FROM::MAX_NUMBER.into(); + let max = + if to.unsigned_inner() < from.unsigned_inner() { Some(to) } + else { None }; + + // Write the conditions! + match (min, max) { + (Some(min), Some(max)) => writeln!( + out, + "\t\tif src <= {min} {{ {min} }} + else if src >= {max} {{ {max} }} + else {{ src as Self }}" + ), + (Some(min), None) => writeln!( + out, + "\t\tif src <= {min} {{ {min} }} + else {{ src as Self }}" + ), + (None, Some(max)) => writeln!( + out, + "\t\tif src >= {max} {{ {max} }} + else {{ src as Self }}" + ), + (None, None) => writeln!(out, "\t\tsrc as Self"), + }.unwrap(); +} diff --git a/justfile b/justfile index dd5ceb4..cdd36db 100644 --- a/justfile +++ b/justfile @@ -76,9 +76,9 @@ bench BENCH="": env RUSTUP_PERMIT_COPY_RENAME=true rustup install nightly # Make the docs. - cargo +nightly doc \ + cargo +nightly rustdoc \ --release \ - --no-deps \ + --all-features \ --target x86_64-unknown-linux-gnu \ --target-dir "{{ cargo_dir }}" @@ -93,10 +93,13 @@ bench BENCH="": # Pre-clean. [ ! -d "{{ justfile_directory() }}/target" ] || rm -rf "{{ justfile_directory() }}/target" - fyi task "Testing native/default target." - MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test + fyi task "Testing x86_64-unknown-linux-gnu target." + MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test --target x86_64-unknown-linux-gnu - fyi task "Testing mps64 (big endian) target." + fyi task "Testing i686-unknown-linux-gnu (32-bit) target." + MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test --target i686-unknown-linux-gnu + + fyi task "Testing mips64-unknown-linux-gnuabi64 (big endian) target." MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test --target mips64-unknown-linux-gnuabi64 # Post-clean. diff --git a/src/lib.rs b/src/lib.rs index 1c23adb..26e4ed3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ This crate provides a fast interface to "stringify" unsigned integers, formatted with commas at each thousand. It prioritizes speed and simplicity over configurability. -If your application just wants to turn `1010` into `"1,010"`, `Dactyl` is a great choice. If your application requires locale awareness or other options, something like [`num-format`](https://crates.io/crates/num-format) would probably make more sense. +If your application just wants to quickly turn `1010` into `"1,010"`, Dactyl is a great choice. If your application requires locale awareness or other options, something like [`num-format`](https://crates.io/crates/num-format) would probably make more sense. Similar to [`itoa`](https://crates.io/crates/itoa), Dactyl writes ASCII conversions to a temporary buffer, but does so using fixed arrays sized for each type's maximum value, minimizing the allocation overhead for, say, tiny little `u8`s. @@ -20,9 +20,10 @@ Each type has its own struct, each of which works exactly the same way: * [`NiceU8`] * [`NiceU16`] * [`NiceU32`] -* [`NiceU64`] - -(Note: support for `usize` values is folded into [`NiceU64`].) +* [`NiceU64`] (also covers `usize`) +* [`NiceFloat`] +* [`NiceElapsed`] (for durations) +* [`NicePercent`] (for floats representing percentages) The intended use case is to simply call the appropriate `from()` for the type, then use either the `as_str()` or `as_bytes()` struct methods to retrieve the output in the desired format. Each struct also implements traits like `Deref`, `Display`, `AsRef`, `AsRef<[u8]>`, etc., if you prefer those. @@ -33,10 +34,13 @@ assert_eq!(NiceU16::from(11234_u16).as_str(), "11,234"); assert_eq!(NiceU16::from(11234_u16).as_bytes(), b"11,234"); ``` -This crate also contains a few more specialized "nice" structs: -* [`NiceFloat`] -* [`NiceElapsed`] -* [`NicePercent`] +But the niceness doesn't stop there. Dactyl provides several other structs, methods, and traits to performantly work with integers, such as: + +* [`NoHash`]: a passthrough hasher for integer `HashSet`/`HashMap` collections +* [`traits::BytesToSigned`]: signed integer parsing from byte slices +* [`traits::BytesToUnsigned`]: unsigned integer parsing from byte slices +* [`traits::HexToSigned`]: signed integer parsing from hex +* [`traits::HexToUnsigned`]: unsigned integer parsing from hex */ @@ -94,12 +98,12 @@ pub use nice_int::{ #[doc(hidden)] pub use nice_int::NiceWrapper; -use num_traits::cast::AsPrimitive; +use traits::IntDivFloat; /// # Decimals, 00-99. -static DOUBLE: [[u8; 2]; 100] = [ +const DOUBLE: [[u8; 2]; 100] = [ [48, 48], [48, 49], [48, 50], [48, 51], [48, 52], [48, 53], [48, 54], [48, 55], [48, 56], [48, 57], [49, 48], [49, 49], [49, 50], [49, 51], [49, 52], [49, 53], [49, 54], [49, 55], [49, 56], [49, 57], [50, 48], [50, 49], [50, 50], [50, 51], [50, 52], [50, 53], [50, 54], [50, 55], [50, 56], [50, 57], @@ -120,10 +124,10 @@ static DOUBLE: [[u8; 2]; 100] = [ /// ## Panics /// /// This will panic if the number is greater than 99. -pub(crate) fn double(idx: usize) -> [u8; 2] { DOUBLE[idx] } +pub(crate) const fn double(idx: usize) -> [u8; 2] { DOUBLE[idx] } #[inline] -#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_possible_truncation, clippy::integer_division)] /// # Triple Digits. /// /// Return both digits, ASCII-fied. @@ -131,9 +135,9 @@ pub(crate) fn double(idx: usize) -> [u8; 2] { DOUBLE[idx] } /// ## Panics /// /// This will panic if the number is greater than 99. -pub(crate) fn triple(idx: usize) -> [u8; 3] { +pub(crate) const fn triple(idx: usize) -> [u8; 3] { assert!(idx < 1000, "Bug: Triple must be less than 1000."); - let (div, rem) = div_mod(idx, 100); + let (div, rem) = (idx / 100, idx % 100); let a = div as u8 + b'0'; let [b, c] = DOUBLE[rem]; [a, b, c] @@ -141,7 +145,9 @@ pub(crate) fn triple(idx: usize) -> [u8; 3] { +#[deprecated(since = "0.5.3", note = "use (a / b, a % b) instead")] #[must_use] +#[inline] /// # Combined Division/Remainder. /// /// Perform division and remainder operations in one go, returning both results @@ -173,24 +179,30 @@ pub(crate) fn triple(idx: usize) -> [u8; 3] { pub fn div_mod(e: T, d: T) -> (T, T) where T: Copy + std::ops::Div + std::ops::Rem { (e / d, e % d) } +#[deprecated(since = "0.6.0", note = "use traits::IntDivFloat instead")] #[must_use] +#[inline] /// # Integer to Float Division. /// -/// This uses [`num_traits::cast`](https://docs.rs/num-traits/latest/num_traits/cast/index.html) to convert primitives to `f64` as accurately -/// as possible, then performs the division. For very large numbers, some -/// rounding may occur. +/// Recast two integers to floats, then divide them and return the result, or +/// `None` if the operation is invalid or yields `NaN` or infinity. /// -/// If the result is invalid, NaN, or infinite, `None` is returned. -pub fn int_div_float(e: T, d: T) -> Option -where T: AsPrimitive { - let d: f64 = d.as_(); - - // The denominator can't be zero. - if d == 0.0 { None } - else { - Some(e.as_() / d).filter(|x| x.is_finite()) - } -} +/// This method accepts `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, +/// `i32`, `i64`, `i128`, and `isize`. +/// +/// ## Examples +/// +/// ``` +/// // Equivalent to 20_f64 / 16_f64. +/// assert_eq!( +/// dactyl::int_div_float(20_u8, 16_u8), +/// Some(1.25_f64) +/// ); +/// +/// // Division by zero is still a no-no. +/// assert!(dactyl::int_div_float(100_i32, 0_i32).is_none()); +/// ``` +pub fn int_div_float(e: T, d: T) -> Option { e.div_float(d) } @@ -199,11 +211,38 @@ mod tests { use super::*; use brunch as _; + #[allow(deprecated)] #[test] fn t_int_div_float() { - assert_eq!(int_div_float(4_000_000_000_u64, 8_000_000_000_u64), Some(0.5)); - assert_eq!(int_div_float(400_000_000_000_u64, 800_000_000_000_u64), Some(0.5)); - assert_eq!(int_div_float(400_000_000_000_u64, 0_u64), None); - assert_eq!(int_div_float(4_u8, 8_u8), Some(0.5)); + let mut rng = fastrand::Rng::new(); + + // Just make sure this produces the same result as the trait. + macro_rules! t_div { + ($($rnd:ident $ty:ty),+ $(,)?) => ($( + for _ in 0..10 { + let a = rng.$rnd(<$ty>::MAX..=<$ty>::MAX); + let b = rng.$rnd(<$ty>::MAX..=<$ty>::MAX); + if b != 0 { + assert_eq!(int_div_float(a, b), a.div_float(b)); + } + } + )+); + } + + // Make sure we actually implemented all of these. Haha. + t_div! { + u8 u8, + u16 u16, + u32 u32, + u64 u64, + u128 u128, + usize usize, + i8 i8, + i16 i16, + i32 i32, + i64 i64, + i128 i128, + isize isize, + } } } diff --git a/src/nice_int/mod.rs b/src/nice_int/mod.rs index 6d1ff46..8e7df3f 100644 --- a/src/nice_int/mod.rs +++ b/src/nice_int/mod.rs @@ -180,10 +180,10 @@ macro_rules! nice_parse { fn parse(&mut self, mut num: $uint) { for chunk in self.inner.rchunks_exact_mut(4) { if 999 < num { - let (div, rem) = crate::div_mod(num, 1000); + let rem = num % 1000; + num /= 1000; chunk[1..].copy_from_slice(crate::triple(rem as usize).as_slice()); self.from -= 4; - num = div; } else { break; } } diff --git a/src/nice_int/nice_float.rs b/src/nice_int/nice_float.rs index 9af2c66..089b7ce 100644 --- a/src/nice_int/nice_float.rs +++ b/src/nice_int/nice_float.rs @@ -454,10 +454,10 @@ impl NiceFloat { for chunk in self.inner[..IDX_DOT].rchunks_exact_mut(4) { if 999 < top { - let (div, rem) = crate::div_mod(top, 1000); + let rem = top % 1000; + top /= 1000; chunk[1..].copy_from_slice(crate::triple(rem as usize).as_slice()); self.from -= 4; - top = div; } else { break; } } @@ -487,6 +487,7 @@ impl NiceFloat { } } + #[allow(clippy::integer_division)] /// # Parse Bottom. /// /// This writes the fractional part of the float, if any. @@ -499,7 +500,7 @@ impl NiceFloat { let mut divisor = 1_000_000_u32; for chunk in self.inner[IDX_DOT + 1..].chunks_exact_mut(2) { - let (a, b) = crate::div_mod(bottom, divisor); + let (a, b) = (bottom / divisor, bottom % divisor); // Write the leftmost two digits. if 0 != a { diff --git a/src/nice_int/nice_percent.rs b/src/nice_int/nice_percent.rs index 775a25e..83d8bfe 100644 --- a/src/nice_int/nice_percent.rs +++ b/src/nice_int/nice_percent.rs @@ -2,7 +2,10 @@ # Dactyl: Nice Percent. */ -use crate::NiceWrapper; +use crate::{ + NiceWrapper, + traits::IntDivFloat, +}; @@ -62,6 +65,7 @@ impl Default for NicePercent { /// This code is identical for `f32` and `f64` types. macro_rules! nice_from { ($($float:ty),+ $(,)?) => ($( + #[allow(clippy::integer_division)] impl From<$float> for NicePercent { fn from(num: $float) -> Self { // Shortcut for overflowing values. @@ -78,7 +82,7 @@ macro_rules! nice_from { else if 9999 < whole { return Self::max(); } // Split the top and bottom. - let (top, bottom) = crate::div_mod(whole, 100); + let (top, bottom) = (whole / 100, whole % 100); let [a, b] = crate::double(top as usize); let from = if a == b'0' { SIZE - 5 } else { SIZE - 6 }; @@ -95,8 +99,7 @@ macro_rules! nice_from { nice_from!(f32, f64); -impl TryFrom<(T, T)> for NicePercent -where T: num_traits::cast::AsPrimitive { +impl TryFrom<(T, T)> for NicePercent { type Error = (); /// # Percent From T/T. @@ -119,7 +122,7 @@ where T: num_traits::cast::AsPrimitive { /// Conversion will fail if the enumerator is larger than the denominator, /// or if the denominator is zero. fn try_from(src: (T, T)) -> Result { - crate::int_div_float(src.0, src.1) + src.0.div_float(src.1) .map(Self::from) .ok_or(()) } diff --git a/src/nice_int/nice_u16.rs b/src/nice_int/nice_u16.rs index 5d87fd9..159499f 100644 --- a/src/nice_int/nice_u16.rs +++ b/src/nice_int/nice_u16.rs @@ -66,9 +66,10 @@ super::nice_from_nz!(NiceU16, NonZeroU16); impl From for NiceU16 { #[allow(clippy::cast_possible_truncation)] // One digit always fits u8. #[allow(clippy::many_single_char_names)] // ABCDE keeps the ordering straight. + #[allow(clippy::integer_division)] fn from(num: u16) -> Self { if 999 < num { - let (num, rem) = crate::div_mod(num, 1000); + let (num, rem) = (num / 1000, num % 1000); let [c, d, e] = crate::triple(rem as usize); if 9 < num { diff --git a/src/nice_int/nice_u8.rs b/src/nice_int/nice_u8.rs index 7fa24d8..a9bbf32 100644 --- a/src/nice_int/nice_u8.rs +++ b/src/nice_int/nice_u8.rs @@ -98,7 +98,13 @@ impl NiceU8 { /// assert_eq!(dactyl::NiceU8::from(50).as_bytes2(), b"50"); /// assert_eq!(dactyl::NiceU8::from(113).as_bytes2(), b"113"); /// ``` - pub fn as_bytes2(&self) -> &[u8] { &self.inner[1.min(self.from)..] } + pub const fn as_bytes2(&self) -> &[u8] { + if self.from == 0 { &self.inner } + else { + let [ _, rest @ .. ] = &self.inner; + rest + } + } #[must_use] #[inline] @@ -133,9 +139,14 @@ impl NiceU8 { /// assert_eq!(dactyl::NiceU8::from(50).as_str2(), "50"); /// assert_eq!(dactyl::NiceU8::from(113).as_str2(), "113"); /// ``` - pub fn as_str2(&self) -> &str { + pub const fn as_str2(&self) -> &str { // Safety: numbers are valid ASCII. - debug_assert!(self.as_bytes2().is_ascii(), "Bug: NiceU8 is not ASCII."); + debug_assert!( + (self.from != 0 || self.inner[0].is_ascii_digit()) && + self.inner[1].is_ascii_digit() && + self.inner[2].is_ascii_digit(), + "Bug: NiceU8 is not ASCII." + ); unsafe { std::str::from_utf8_unchecked(self.as_bytes2()) } } diff --git a/src/traits/intdiv.rs b/src/traits/intdiv.rs new file mode 100644 index 0000000..e784989 --- /dev/null +++ b/src/traits/intdiv.rs @@ -0,0 +1,107 @@ +/*! +# Dactyl: Integer Division +*/ + + + +/// # Integer Float Division. +/// +/// This trait adds a `div_float` method to primitive integers, enabling +/// division as floats. +/// +/// ## Examples +/// +/// ``` +/// use dactyl::traits::IntDivFloat; +/// +/// // Equivalent to 25_f64 / 20_f64. +/// assert_eq!( +/// 25_u32.div_float(20_u32), +/// Some(1.25_f64), +/// ); +/// ``` +pub trait IntDivFloat: Copy { + /// # Integer to Float Division. + /// + /// Recast two integers to floats, then divide them and return the result, + /// or `None` if the operation is invalid or yields `NaN` or infinity. + fn div_float(self, d: Self) -> Option; +} + +/// # Helper: Implement Trait. +macro_rules! intdiv { + ($($ty:ty),+) => ($( + impl IntDivFloat for $ty { + /// # Integer to Float Division. + /// + /// Recast two integers to floats, then divide them and return the + /// result, or `None` if the operation is invalid or yields `NaN` + /// or infinity. + /// + /// ## Examples + /// + /// ``` + /// use dactyl::traits::IntDivFloat; + /// + /// // Equivalent to 20_f64 / 16_f64. + /// assert_eq!( + #[doc = concat!(" 20_", stringify!($ty), ".div_float(16),")] + /// Some(1.25_f64), + /// ); + /// + /// // Division by zero is still a no-no. + #[doc = concat!("assert!(20_", stringify!($ty), ".div_float(0).is_none());")] + /// ``` + fn div_float(self, d: Self) -> Option { + let res = self as f64 / d as f64; + if res.is_finite() { Some(res) } + else { None } + } + } + )+); +} + +intdiv! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize } + + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn t_div_float() { + macro_rules! t_div { + ($($ty:ty),+) => ($( + let e: $ty = 3; + let d: $ty = 4; + + // Easy stuff. + assert_eq!(e.div_float(d), Some(0.75)); + assert_eq!(e.div_float(e), Some(1.0)); + + // 1.3333333333… + let Some(long) = d.div_float(e) else { + panic!( + concat!("{}_", stringify!($ty), " / {}_", stringify!($ty), " failed."), + d, + e, + ); + }; + assert!( + long > 1.0 && long < 2.0, + concat!("{}_", stringify!($ty), " / {}_", stringify!($ty), " came out weird: {}"), + d, + e, + long, + ); + + // Can't divide by zero! + assert_eq!(e.div_float(0), None); + )+); + } + + // Make sure we actually implemented all of these. Haha. + t_div! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize } + } +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 19cfc09..e4a8915 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -6,6 +6,7 @@ mod btoi; mod btou; mod hex; mod inflect; +mod intdiv; mod saturating_from; pub use btoi::BytesToSigned; @@ -18,4 +19,5 @@ pub use inflect::{ Inflection, NiceInflection, }; +pub use intdiv::IntDivFloat; pub use saturating_from::SaturatingFrom; diff --git a/src/traits/saturating_from.rs b/src/traits/saturating_from.rs index 96ff8a1..1884899 100644 --- a/src/traits/saturating_from.rs +++ b/src/traits/saturating_from.rs @@ -1,468 +1,601 @@ /*! # Dactyl: Saturated Unsigned Integer Conversion -The `SaturatingFrom` trait allows large primitives to be downcast into smaller -types with values capped at the smaller type's `::MAX` value, avoiding any -possible overflow or wrapping issues. It's a clamp, basically, except all uints -share the same bottom. +The `SaturatingFrom` trait allows integer primitives to be freely converted +between one another in a saturating fashion. -It is implemented for `u8`, `u16`, `u32`, and `u64` for all types larger than -said type, up to `u128`. - -The `usize` type, being variable, works a little differently. It implements -`SaturatingFrom` on `u32`, `u64`, and `u128` regardless of the machine's bit -size, but its ceiling will vary based on the machine's bit size (it could be as -low as `u16::MAX` or as high as `u64::MAX`). +To make life easy, `int::saturating_from(float)` is implemented as well, but +this is functionally identical to writing `float as int`, since such casts are +already saturating in Rust. ## Examples ``` use dactyl::traits::SaturatingFrom; +// Too big. assert_eq!(u8::saturating_from(1026_u16), 255_u8); -assert_eq!(u8::saturating_from(99_u16), 99_u8); -``` -*/ - -/// # Helper: Title/Description. -/// -/// This generates a formatted title and description for the documentation. -macro_rules! impl_meta { - // Title/Desc. - ($to:ty, $from:ty) => ( - concat!( - "# Saturating From `", - stringify!($from), - "`\n", - "This method will safely recast any `", - stringify!($from), - "` into a `", - stringify!($to), - "`, capping the values at `0` or `", - stringify!($to), - "::MAX` to prevent overflow or wrapping." - ) - ); -} - -/// # Helper: Generate Trait Implementations. -/// -/// This generates implementations for unsigned sources, with or without an -/// upper cap. -macro_rules! unsigned_to_unsigned { - // Cap to max. - ($meta:expr, $from:ty, $to:ty, $MAX:literal) => ( - impl SaturatingFrom<$from> for $to { - #[doc = $meta] - fn saturating_from(src: $from) -> Self { - if src >= $MAX { $MAX } - else { src as Self } - } - } - ); - - // Direct cast. - ($meta:expr, $from:ty, $to:ty) => ( - impl SaturatingFrom<$from> for $to { - #[doc = $meta] - fn saturating_from(src: $from) -> Self { src as Self } - } - ); - - // Cap to max. - ($to:ty, $MAX:literal, ($($from:ty),+)) => ( - $( unsigned_to_unsigned!(impl_meta!($to, $from), $from, $to, $MAX); )+ - ); - - // Direct cast. - ($to:ty, ($($from:ty),+)) => ( - $( unsigned_to_unsigned!(impl_meta!($to, $from), $from, $to); )+ - ); -} - -/// # Helper: Generate Trait Implementations (Signed). -/// -/// This generates implementations for signed sources, with or without an -/// upper cap. All signed types have a lower cap of zero. -macro_rules! signed_to_unsigned { - // Cap to min/max. - ($meta:expr, $from:ty, $to:ty, $MAX:literal) => ( - impl SaturatingFrom<$from> for $to { - #[doc = $meta] - fn saturating_from(src: $from) -> Self { - if src >= $MAX { Self::MAX } - else if src > 0 { src as Self } - else { 0 } - } - } - ); - // Cap to min. - ($meta:expr, $from:ty, $to:ty) => ( - impl SaturatingFrom<$from> for $to { - #[doc = $meta] - fn saturating_from(src: $from) -> Self { - if src > 0 { src as Self } - else { 0 } - } - } - ); - - // Cap to min/max. - ($to:ty, $MAX:literal, ($($from:ty),+)) => ( - $( signed_to_unsigned!(impl_meta!($to, $from), $from, $to, $MAX); )+ - ); - - // Cap to min. - ($to:ty, ($($from:ty),+)) => ( - $( signed_to_unsigned!(impl_meta!($to, $from), $from, $to); )+ - ); -} +// Too small. +assert_eq!(u8::saturating_from(-1026_i32), 0_u8); -/// # Helper: Generate Trait Implementations (Signed). -/// -/// This generates implementations for float sources, with or without an -/// upper cap. Negative and NaN values are cast to zero; infinite is cast to -/// MAX. -macro_rules! float_to_unsigned { - // Cap to min/max. - ($meta:expr, $from:ty, $to:ty, $MAX:literal) => ( - impl SaturatingFrom<$from> for $to { - #[doc = $meta] - fn saturating_from(src: $from) -> Self { - if src <= 0.0 { 0 } - else if src >= $MAX { Self::MAX } - else { src as Self } - } - } - ); +// Just right. +assert_eq!(u8::saturating_from(99_u64), 99_u8); +``` +*/ - // Cap to min. - ($meta:expr, $from:ty, $to:ty) => ( - impl SaturatingFrom<$from> for $to { - #[doc = $meta] - fn saturating_from(src: $from) -> Self { - if src > 0.0 { src as Self } - else { 0 } - } - } - ); - - // Cap to min/max. - ($to:ty, $MAX:literal, ($($from:ty),+)) => ( - $( float_to_unsigned!(impl_meta!($to, $from), $from, $to, $MAX); )+ - ); - - // Cap to min. - ($to:ty, ($($from:ty),+)) => ( - $( float_to_unsigned!(impl_meta!($to, $from), $from, $to); )+ - ); -} +#![allow( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_sign_loss, +)] /// # Saturating From. /// -/// Convert an unsigned integer of a larger type into `Self`, capping the -/// maximum value to `Self::MAX` to prevent overflow or wrapping. +/// Convert between numeric types, clamping to `Self::MIN..=Self::MAX` to +/// prevent overflow or wrapping issues. pub trait SaturatingFrom { /// # Saturating From. + /// + /// Convert `T` to `Self`, clamping to `Self::MIN..=Self::MAX` as required + /// to prevent overflow or wrapping. fn saturating_from(src: T) -> Self; } +// All the integer conversions are built at compile-time. +include!(concat!(env!("OUT_DIR"), "/dactyl-saturation.rs")); +// Floats are mercifully saturating on their own. +macro_rules! float { + ($from:ty, $($to:ty),+) => ($( + impl SaturatingFrom<$from> for $to { + #[doc = concat!("# Saturating From `", stringify!($from), "`")] + #[doc = ""] + #[doc = concat!("This method will safely recast any `", stringify!($from), "` into a `", stringify!($to), "`, clamping the values to `", stringify!($to), "::MIN..=", stringify!($to), "::MAX` to prevent overflow or wrapping.")] + fn saturating_from(src: $from) -> Self { src as Self } + } + )+); +} -// These three are always the same. -unsigned_to_unsigned!(u8, 255, (u16, u32, u64, u128, usize)); -unsigned_to_unsigned!(u16, 65_535, (u32, u64, u128, usize)); -unsigned_to_unsigned!(u32, 4_294_967_295, (u64, u128)); // Usize conditional, below. -unsigned_to_unsigned!(u64, 18_446_744_073_709_551_615, (u128)); // Usize conditional, below. -unsigned_to_unsigned!(u128, (usize)); - -// usize-to-u32 varies by pointer. -#[cfg(any(target_pointer_width = "16", target_pointer_width="32"))] // 16/32 fit. -unsigned_to_unsigned!(u32, (usize)); -#[cfg(any(target_pointer_width = "64", target_pointer_width="128"))] // 64/128 don't. -unsigned_to_unsigned!(u32, 4_294_967_295, (usize)); - -// usize-to-u64 varies by pointer. -#[cfg(not(target_pointer_width = "128"))] // 16/32/64 fit. -unsigned_to_unsigned!(u64, (usize)); -#[cfg(target_pointer_width = "128")] // 128 doesn't. -unsigned_to_unsigned!(u64, 18_446_744_073_709_551_615, (usize)); - -// Usize varies by pointer. -#[cfg(target_pointer_width = "16")] // 32/64/128 don't fit. -unsigned_to_unsigned!(usize, 65_535, (u32, u64, u128)); - -#[cfg(target_pointer_width = "32")] // 32 fits. -unsigned_to_unsigned!(usize, (u32)); -#[cfg(target_pointer_width = "32")] // 64, 128 don't. -unsigned_to_unsigned!(usize, 4_294_967_295, (u64, u128)); - -#[cfg(target_pointer_width = "64")] // 32/64 fits. -unsigned_to_unsigned!(usize, (u32, u64)); -#[cfg(target_pointer_width = "64")] // 128 doesn't. -unsigned_to_unsigned!(usize, 18_446_744_073_709_551_615, (u128)); - -#[cfg(target_pointer_width = "128")] -unsigned_to_unsigned!(usize, (u32, u64, u128)); - - - -// Converting from signed types. These are straight conversions. -signed_to_unsigned!(u8, (i8)); -signed_to_unsigned!(u16, (i8, i16)); -signed_to_unsigned!(u32, (i8, i16, i32)); -signed_to_unsigned!(u64, (i8, i16, i32, i64)); -signed_to_unsigned!(u128, (i8, i16, i32, i64, i128, isize)); -signed_to_unsigned!(usize, (i8, i16, isize)); - -// These require max capping. -signed_to_unsigned!(u8, 255, (i16, i32, i64, i128, isize)); -signed_to_unsigned!(u16, 65_535, (i32, i64, i128, isize)); -signed_to_unsigned!(u32, 4_294_967_295, (i64, i128)); -signed_to_unsigned!(u64, 18_446_744_073_709_551_615, (i128)); - -// U32/isize varies by pointer. -#[cfg(any(target_pointer_width = "16", target_pointer_width="32"))] -signed_to_unsigned!(u32, (isize)); -#[cfg(any(target_pointer_width = "64", target_pointer_width="128"))] -signed_to_unsigned!(u32, 4_294_967_295, (isize)); - -// U64/isize varies by pointer. -#[cfg(not(target_pointer_width = "128"))] -signed_to_unsigned!(u64, (isize)); -#[cfg(target_pointer_width = "128")] -signed_to_unsigned!(u64, 18_446_744_073_709_551_615, (isize)); - -// All other usize conversions vary by pointer. -#[cfg(target_pointer_width = "16")] -signed_to_unsigned!(usize, 65_535, (i32, i64, i128)); - -#[cfg(target_pointer_width = "32")] -signed_to_unsigned!(usize, (i32)); -#[cfg(target_pointer_width = "32")] -signed_to_unsigned!(usize, 4_294_967_295, (i64, i128)); - -#[cfg(target_pointer_width = "64")] -signed_to_unsigned!(usize, (i32, i64)); -#[cfg(target_pointer_width = "64")] -signed_to_unsigned!(usize, 18_446_744_073_709_551_615, (i128)); +float!(f32, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); +float!(f64, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); -#[cfg(target_pointer_width = "128")] -signed_to_unsigned!(usize, (i32, i64, i128)); +#[cfg(test)] +/// # Saturation Tests. +/// +/// There isn't a particularly good way to do this other than to walk through +/// fixed ranges and assert smaller types get clamped, and greater-equal ones +/// don't. +/// +/// Usize/isize tests vanish beyond the 16-bit ranges to avoid clutter, but +/// have their own separate cfg-gated test that should verify they work/fail +/// beyond that given the target's pointer width. +/// +/// The 16-bit and 128-bit sized tests may be buggy as they haven't actually +/// been run since Rust doesn't support any architectures at either width +/// yet. TBD. +mod tests { + // Testing everything the same way is easier (and safer) than testing + // certain conversions one way and others another. + #![allow(trivial_numeric_casts)] -// Converting from floats. -float_to_unsigned!(u8, 255.0, (f32, f64)); -float_to_unsigned!(u16, 65_535.0, (f32, f64)); -float_to_unsigned!(u32, 4_294_967_295.0, (f32, f64)); -float_to_unsigned!(u64, (f32)); -float_to_unsigned!(u64, 18_446_744_073_709_551_615.0, (f64)); -float_to_unsigned!(u128, (f32, f64)); + use super::*; -// And again, usize is a pain. -#[cfg(target_pointer_width = "16")] -float_to_unsigned!(usize, 65_535.0, (f32, f64)); + #[cfg(not(miri))] + const SAMPLE_SIZE: usize = 500_000; -#[cfg(target_pointer_width = "32")] -float_to_unsigned!(usize, 4_294_967_295.0, (f32, f64)); + #[cfg(miri)] + const SAMPLE_SIZE: usize = 500; // Miri runs way too slow for half a million tests. + + /// # Helper: Assert SaturatingFrom is Lossless. + macro_rules! cast_assert_same { + ($to:ty, $raw:ident, $($from:ty),+) => ($( + assert_eq!( + <$to>::saturating_from($raw as $from), + $raw as $to, + concat!("Expected {}_", stringify!($to), " from {}_", stringify!($from), "."), + $raw, + $raw, + ); + )+); + } -#[cfg(target_pointer_width = "64")] -float_to_unsigned!(usize, (f32)); -#[cfg(target_pointer_width = "64")] -float_to_unsigned!(usize, 18_446_744_073_709_551_615.0, (f64)); + /// # Helper: Assert SaturatingFrom Clamps to Self::MAX. + macro_rules! cast_assert_max { + ($to:ty, $raw:ident, $($from:ty),+) => ($( + assert_eq!( + <$to>::saturating_from($raw as $from), + <$to>::MAX, + concat!("Expected {}_", stringify!($to), " from {}_", stringify!($from), "."), + <$to>::MAX, + $raw, + ); + )+); + } -#[cfg(target_pointer_width = "128")] -float_to_unsigned!(usize, (f32, f64)); + /// # Helper: Assert SaturatingFrom Clamps to Self::MIN. + macro_rules! cast_assert_min { + ($to:ty, $raw:ident, $($from:ty),+) => ($( + assert_eq!( + <$to>::saturating_from($raw as $from), + <$to>::MIN, + concat!("Expected {}_", stringify!($to), " from {}_", stringify!($from), "."), + <$to>::MIN, + $raw, + ); + )+); + } + #[test] + fn t_saturating_rng_i28min_i64min() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i128(i128::MIN..i64::MIN as i128)).take(SAMPLE_SIZE) { + // Floor reached. + cast_assert_min!( u8, i, i128); + cast_assert_min!( u16, i, i128); + cast_assert_min!( u32, i, i128); + cast_assert_min!( u64, i, i128); + cast_assert_min!( u128, i, i128); + cast_assert_min!( usize, i, i128); + cast_assert_min!( i8, i, i128); + cast_assert_min!( i16, i, i128); + cast_assert_min!( i32, i, i128); + cast_assert_min!( i64, i, i128); + + // Still in range. + cast_assert_same!(i128, i, i128); + } + } + #[test] + fn t_saturating_rng_i64min_i32min() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i64(i64::MIN..i32::MIN as i64)).take(SAMPLE_SIZE) { + // Floor reached. + cast_assert_min!( u8, i, i64, i128); + cast_assert_min!( u16, i, i64, i128); + cast_assert_min!( u32, i, i64, i128); + cast_assert_min!( u64, i, i64, i128); + cast_assert_min!( u128, i, i64, i128); + cast_assert_min!( usize, i, i64, i128); + cast_assert_min!( i8, i, i64, i128); + cast_assert_min!( i16, i, i64, i128); + cast_assert_min!( i32, i, i64, i128); + + // Still in range. + cast_assert_same!(i64, i, i64, i128); + cast_assert_same!(i128, i, i64, i128); + } + } -#[cfg(test)] -mod tests { - use super::*; - use num_traits::cast::AsPrimitive; + #[test] + fn t_saturating_rng_i32min_i16min() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i32(i32::MIN..i16::MIN as i32)).take(SAMPLE_SIZE) { + // Floor reached. + cast_assert_min!( u8, i, i32, i64, i128); + cast_assert_min!( u16, i, i32, i64, i128); + cast_assert_min!( u32, i, i32, i64, i128); + cast_assert_min!( u64, i, i32, i64, i128); + cast_assert_min!( u128, i, i32, i64, i128); + cast_assert_min!( usize, i, i32, i64, i128); + cast_assert_min!( i8, i, i32, i64, i128); + cast_assert_min!( i16, i, i32, i64, i128); + + // Still in range. + cast_assert_same!(i32, i, i32, i64, i128); + cast_assert_same!(i64, i, i32, i64, i128); + cast_assert_same!(i128, i, i32, i64, i128); + } + } #[cfg(not(miri))] - const SAMPLE_SIZE: usize = 1_000_000; + #[test] + fn t_saturating_rng_i16min_i8min() { + for i in i16::MIN..i8::MIN as i16 { + // Floor reached. + cast_assert_min!( u8, i, i16, i32, i64, i128, isize); + cast_assert_min!( u16, i, i16, i32, i64, i128, isize); + cast_assert_min!( u32, i, i16, i32, i64, i128, isize); + cast_assert_min!( u64, i, i16, i32, i64, i128, isize); + cast_assert_min!( u128, i, i16, i32, i64, i128, isize); + cast_assert_min!( usize, i, i16, i32, i64, i128, isize); + cast_assert_min!( i8, i, i16, i32, i64, i128, isize); + + // Still in range. + cast_assert_same!(i16, i, i16, i32, i64, i128, isize); + cast_assert_same!(i32, i, i16, i32, i64, i128, isize); + cast_assert_same!(i64, i, i16, i32, i64, i128, isize); + cast_assert_same!(i128, i, i16, i32, i64, i128, isize); + cast_assert_same!(isize, i, i16, i32, i64, i128, isize); + } + } #[cfg(miri)] - const SAMPLE_SIZE: usize = 500; // Miri runs way too slow for a million tests. - - /// # Test Flooring. - macro_rules! test_impl { - ($type:ty, ($($val:expr),+)) => ( - $( assert_eq!(<$type>::saturating_from($val), <$type>::MIN); )+ - ); - - ($type:ty) => { - // SaturatingFrom is implemented for all signed types. - test_impl!($type, (-1_i8, -1_i16, -1_i32, -1_i64, -1_i128, -1_isize)); - test_impl!($type, (0_i8, 0_i16, 0_i32, 0_i64, 0_i128, 0_isize)); - - // Negative infinity and NAN should zero out. - test_impl!($type, (0_f32, f32::NEG_INFINITY, f32::NAN)); - test_impl!($type, (0_f64, f64::NEG_INFINITY, f64::NAN)); - }; + #[test] + fn t_saturating_rng_i16min_i8min() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i16(i16::MIN..i8::MIN as i16)).take(SAMPLE_SIZE) { + // Floor reached. + cast_assert_min!( u8, i, i16, i32, i64, i128, isize); + cast_assert_min!( u16, i, i16, i32, i64, i128, isize); + cast_assert_min!( u32, i, i16, i32, i64, i128, isize); + cast_assert_min!( u64, i, i16, i32, i64, i128, isize); + cast_assert_min!( u128, i, i16, i32, i64, i128, isize); + cast_assert_min!( usize, i, i16, i32, i64, i128, isize); + cast_assert_min!( i8, i, i16, i32, i64, i128, isize); + + // Still in range. + cast_assert_same!(i16, i, i16, i32, i64, i128, isize); + cast_assert_same!(i32, i, i16, i32, i64, i128, isize); + cast_assert_same!(i64, i, i16, i32, i64, i128, isize); + cast_assert_same!(i128, i, i16, i32, i64, i128, isize); + cast_assert_same!(isize, i, i16, i32, i64, i128, isize); + } } - /// # Test Ceiling. - macro_rules! test_impl_max { - ($type:ty, ($($from:ty),+)) => ( - $( assert_eq!(<$type>::saturating_from(<$from>::MAX), <$type>::MAX); )+ - ); - - ($type:ty) => ( - // Float infinity should max out. - assert_eq!(<$type>::saturating_from(f32::INFINITY), <$type>::MAX); - assert_eq!(<$type>::saturating_from(f64::INFINITY), <$type>::MAX); - ); + #[test] + fn t_saturating_rng_i8min_0() { + // All unsigned should be floored, but all signed should be fine. + for i in i8::MIN..0 { + // Floor reached. + cast_assert_min!( u8, i, i8, i16, i32, i64, i128, isize); + cast_assert_min!( u16, i, i8, i16, i32, i64, i128, isize); + cast_assert_min!( u32, i, i8, i16, i32, i64, i128, isize); + cast_assert_min!( u64, i, i8, i16, i32, i64, i128, isize); + cast_assert_min!( u128, i, i8, i16, i32, i64, i128, isize); + cast_assert_min!( usize, i, i8, i16, i32, i64, i128, isize); + + // Still in range. + cast_assert_same!(i8, i, i8, i16, i32, i64, i128, isize); + cast_assert_same!(i16, i, i8, i16, i32, i64, i128, isize); + cast_assert_same!(i32, i, i8, i16, i32, i64, i128, isize); + cast_assert_same!(i64, i, i8, i16, i32, i64, i128, isize); + cast_assert_same!(i128, i, i8, i16, i32, i64, i128, isize); + cast_assert_same!(isize, i, i8, i16, i32, i64, i128, isize); + } } - /// # Range Testing. - macro_rules! test_impl_range { - ($type:ty, ($($from:ty),+)) => { - for i in 0..=<$type>::MAX { - $( assert_eq!(<$type>::saturating_from(i as $from), i); )+ - } - }; + #[test] + fn t_saturating_rng_0_i8max() { + // All saturations should be lossless for upper i8 range. + for i in 0..=i8::MAX { + cast_assert_same!(i8, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i16, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i32, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i64, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i128, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(isize, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u8, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u16, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u32, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u64, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u128, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(usize, i, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + } } - /// # Range Testing (subset). - /// - /// This computes casting for a subset of the total type range; this allows - /// testing large types to finish in a reasonable amount of time. - macro_rules! test_impl_subrange { - ($type:ty:$fn:ident, ($($from:ty),+)) => { - let mut rng = fastrand::Rng::new(); - for i in std::iter::repeat_with(|| rng.$fn(..)).take(SAMPLE_SIZE) { - $( - let test: $from = i.as_(); - let test2: $type = test.as_(); - if test2 == i { - assert_eq!(<$type>::saturating_from(test), i); - } - )+ - } - }; + #[test] + fn t_saturating_rng_i8max_u8max() { + for i in (i8::MAX as u8 + 1)..=u8::MAX { + // Ceiling reached. + cast_assert_max!( i8, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + + // Still in range. + cast_assert_same!(i16, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i32, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i64, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(i128, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(isize, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u8, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u16, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u32, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u64, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(u128, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + cast_assert_same!(usize, i, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + } } - - + #[cfg(not(miri))] #[test] - /// # Test Implementations - /// - /// This makes sure we've actually implemented all the expected type-to- - /// type conversions. - /// - /// The macro without any specific values tests all signed ints at 0 and -1 - /// and all floats at NAN, NEG_INFINITY, and zero. - fn t_impls() { - test_impl!(u8, (0_u16, 0_u32, 0_u64, 0_u128, 0_usize)); - test_impl!(u8); - - test_impl!(u16, (0_u32, 0_u64, 0_u128, 0_usize)); - test_impl!(u16); + fn t_saturating_rng_u8max_i16max() { + for i in (u8::MAX as i16 + 1)..=i16::MAX { + // Ceiling reached. + cast_assert_max!( i8, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_max!( u8, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + + // Still in range. + cast_assert_same!(i16, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(i32, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(i64, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(i128, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(isize, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u16, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u32, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u64, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u128, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(usize, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + } + } - test_impl!(u32, (0_u64, 0_u128, 0_usize)); - test_impl!(u32); + #[cfg(miri)] + #[test] + fn t_saturating_rng_u8max_i16max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i16((u8::MAX as i16 + 1)..=i16::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_max!( u8, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + + // Still in range. + cast_assert_same!(i16, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(i32, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(i64, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(i128, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(isize, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u16, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u32, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u64, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(u128, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + cast_assert_same!(usize, i, i16, i32, i64, i128, isize, u16, u32, u64, u128, usize); + } + } - test_impl!(u64, (0_u128, 0_usize)); - test_impl!(u64); + #[cfg(not(miri))] + #[test] + fn t_saturating_rng_i16max_u16max() { + for i in (i16::MAX as u16 + 1)..=u16::MAX { + // Ceiling reached. + cast_assert_max!( i8, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_max!( u8, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_max!( i16, i, i32, i64, i128, u16, u32, u64, u128, usize); + + // Still in range. + cast_assert_same!(i32, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(i64, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(i128, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u16, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u32, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u64, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u128, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(usize, i, i32, i64, i128, u16, u32, u64, u128, usize); + } + } - test_impl!(u128, (0_usize)); - test_impl!(u128); + #[cfg(miri)] + #[test] + fn t_saturating_rng_i16max_u16max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.u16((i16::MAX as u16 + 1)..=u16::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_max!( u8, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_max!( i16, i, i32, i64, i128, u16, u32, u64, u128, usize); + + // Still in range. + cast_assert_same!(i32, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(i64, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(i128, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u16, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u32, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u64, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(u128, i, i32, i64, i128, u16, u32, u64, u128, usize); + cast_assert_same!(usize, i, i32, i64, i128, u16, u32, u64, u128, usize); + } + } - test_impl!(usize, (0_u32, 0_u64, 0_u128)); - test_impl!(usize); + #[test] + fn t_saturating_rng_u16max_i32max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i32((u16::MAX as i32 + 1)..=i32::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i32, i64, i128, u32, u64, u128); + cast_assert_max!( u8, i, i32, i64, i128, u32, u64, u128); + cast_assert_max!( i16, i, i32, i64, i128, u32, u64, u128); + cast_assert_max!( u16, i, i32, i64, i128, u32, u64, u128); + + // Still in range. + cast_assert_same!(i32, i, i32, i64, i128, u32, u64, u128); + cast_assert_same!(i64, i, i32, i64, i128, u32, u64, u128); + cast_assert_same!(i128, i, i32, i64, i128, u32, u64, u128); + cast_assert_same!(u32, i, i32, i64, i128, u32, u64, u128); + cast_assert_same!(u64, i, i32, i64, i128, u32, u64, u128); + cast_assert_same!(u128, i, i32, i64, i128, u32, u64, u128); + } } #[test] - /// # Test u8 - /// - /// Make sure larger ints correctly saturate to u8. - fn t_u8_from() { - test_impl_range!(u8, (u16, u32, u64, u128, usize, f32, f64)); - test_impl_max!(u8, (u16, u32, u64, u128, usize, f32, f64)); - test_impl_max!(u8); // This tests all float::INFINITY. + fn t_saturating_rng_i32max_u32max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.u32((i32::MAX as u32 + 1)..=u32::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i64, i128, u32, u64, u128); + cast_assert_max!( u8, i, i64, i128, u32, u64, u128); + cast_assert_max!( i16, i, i64, i128, u32, u64, u128); + cast_assert_max!( u16, i, i64, i128, u32, u64, u128); + cast_assert_max!( i32, i, i64, i128, u32, u64, u128); + + // Still in range. + cast_assert_same!(i64, i, i64, i128, u32, u64, u128); + cast_assert_same!(i128, i, i64, i128, u32, u64, u128); + cast_assert_same!(u32, i, i64, i128, u32, u64, u128); + cast_assert_same!(u64, i, i64, i128, u32, u64, u128); + cast_assert_same!(u128, i, i64, i128, u32, u64, u128); + } } #[test] - fn t_u16_from() { - test_impl_range!(u16, (u32, u64, u128, usize, f32, f64)); - test_impl_max!(u16, (u32, u64, u128, usize, f32, f64)); - test_impl_max!(u16); // This tests all float::INFINITY. + fn t_saturating_rng_u32max_i64max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i64((u32::MAX as i64 + 1)..=i64::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i64, i128, u64, u128); + cast_assert_max!( u8, i, i64, i128, u64, u128); + cast_assert_max!( i16, i, i64, i128, u64, u128); + cast_assert_max!( u16, i, i64, i128, u64, u128); + cast_assert_max!( i32, i, i64, i128, u64, u128); + cast_assert_max!( u32, i, i64, i128, u64, u128); + + // Still in range. + cast_assert_same!(i64, i, i64, i128, u64, u128); + cast_assert_same!(i128, i, i64, i128, u64, u128); + cast_assert_same!(u64, i, i64, i128, u64, u128); + cast_assert_same!(u128, i, i64, i128, u64, u128); + } } #[test] - fn t_u32_from() { - test_impl_subrange!(u32:u32, (u64, u128, f32, f64)); - test_impl_max!(u32, (u64, u128)); - test_impl_max!(u32); // This tests all float::INFINITY. + fn t_saturating_rng_i64max_u64max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.u64((i64::MAX as u64 + 1)..=u64::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i128, u64, u128); + cast_assert_max!( u8, i, i128, u64, u128); + cast_assert_max!( i16, i, i128, u64, u128); + cast_assert_max!( u16, i, i128, u64, u128); + cast_assert_max!( i32, i, i128, u64, u128); + cast_assert_max!( u32, i, i128, u64, u128); + cast_assert_max!( i64, i, i128, u64, u128); + + // Still in range. + cast_assert_same!(i128, i, i128, u64, u128); + cast_assert_same!(u64, i, i128, u64, u128); + cast_assert_same!(u128, i, i128, u64, u128); + } } #[test] - fn t_u64_from() { - test_impl_subrange!(u64:u64, (u128)); - test_impl_max!(u64, (u128)); - test_impl_max!(u64); // This tests all float::INFINITY. + fn t_saturating_rng_u64max_i128max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.i128((u64::MAX as i128 + 1)..=i128::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, i128, u128); + cast_assert_max!( u8, i, i128, u128); + cast_assert_max!( i16, i, i128, u128); + cast_assert_max!( u16, i, i128, u128); + cast_assert_max!( i32, i, i128, u128); + cast_assert_max!( u32, i, i128, u128); + cast_assert_max!( i64, i, i128, u128); + cast_assert_max!( u64, i, i128, u128); + + // Still in range. + cast_assert_same!(i128, i, i128, u128); + cast_assert_same!(u128, i, i128, u128); + } } #[test] - #[ignore] - // Float testing is comparatively slow at 64 bits. - fn t_u64_from_float() { - test_impl_subrange!(u64:u64, (f32, f64)); + fn t_saturating_rng_i128max_u128max() { + let mut rng = fastrand::Rng::new(); + for i in std::iter::repeat_with(|| rng.u128((i128::MAX as u128 + 1)..=u128::MAX)).take(SAMPLE_SIZE) { + // Ceiling reached. + cast_assert_max!( i8, i, u128); + cast_assert_max!( u8, i, u128); + cast_assert_max!( i16, i, u128); + cast_assert_max!( u16, i, u128); + cast_assert_max!( i32, i, u128); + cast_assert_max!( u32, i, u128); + cast_assert_max!( i64, i, u128); + cast_assert_max!( u64, i, u128); + cast_assert_max!( i128, i, u128); + + // Still in range. + cast_assert_same!(u128, i, u128); + } } #[cfg(target_pointer_width = "16")] #[test] - fn t_usize_from() { - assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - test_impl_range!(usize, (u32, u64, u128, f32, f64)); - test_impl_max!(usize, (u32, u64, u128)); - test_impl_max!(usize); // This tests all float::INFINITY. + fn t_saturating_sized16() { + let mut rng = fastrand::Rng::new(); + + // Both should be floored below i16. + for i in std::iter::repeat_with(|| rng.i32(i32::MIN..i16::MIN as i32)).take(SAMPLE_SIZE) { + cast_assert_min!(isize, i, i32, i64, i128); + cast_assert_min!(usize, i, i32, i64, i128); + } + + // isize should max out after i16::MAX. + for i in (i16::MAX as u16 + 1)..=u16::MAX { + cast_assert_max!(isize, i, i32, i64, i128, u16, u32, u64, u128, usize); + } + + // usize should max out after u16::MAX. + for i in std::iter::repeat_with(|| rng.i32((u16::MAX as i32 + 1)..=i32::MAX)).take(SAMPLE_SIZE) { + cast_assert_max!(usize, i, i32, i64, i128, u32, u64, u128); + } } #[cfg(target_pointer_width = "32")] #[test] - fn t_usize_from() { - assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - test_impl_subrange!(usize:usize, (u32, u64, u128, f32, f64)); - test_impl_max!(usize, (u32, u64, u128)); - test_impl_max!(u32, (usize)); - test_impl_max!(usize); // This tests all float::INFINITY. + fn t_saturating_sized32() { + let mut rng = fastrand::Rng::new(); + + // isize should be fine down to i32::MIN, but usize will get floored. + for i in std::iter::repeat_with(|| rng.i32(i32::MIN..i16::MIN as i32)).take(SAMPLE_SIZE) { + cast_assert_same!(isize, i, i32, i64, i128, isize); + cast_assert_min!( usize, i, i32, i64, i128, isize); + } + + // Below i32, isize should be floored too. + for i in std::iter::repeat_with(|| rng.i64(i64::MIN..i32::MIN as i64)).take(SAMPLE_SIZE) { + cast_assert_min!(isize, i, i64, i128); + } + + // Both should be fine up to i32::MAX. + for i in std::iter::repeat_with(|| rng.i32((u16::MAX as i32 + 1)..=i32::MAX)).take(SAMPLE_SIZE) { + cast_assert_same!(isize, i, i32, i64, i128, isize, u32, u64, u128, usize); + cast_assert_same!(usize, i, i32, i64, i128, isize, u32, u64, u128, usize); + } + + // isize should max out after i32::MAX, but usize should be fine. + for i in std::iter::repeat_with(|| rng.u32((i32::MAX as u32 + 1)..=u32::MAX)).take(SAMPLE_SIZE) { + cast_assert_max!( isize, i, i64, i128, u32, u64, u128, usize); + cast_assert_same!(usize, i, i64, i128, u32, u64, u128, usize); + } + + // usize should max out after u32::MAX. + for i in std::iter::repeat_with(|| rng.i64((u32::MAX as i64 + 1)..=i64::MAX)).take(SAMPLE_SIZE) { + cast_assert_max!(usize, i, i64, i128, u64, u128); + } } #[cfg(target_pointer_width = "64")] #[test] - fn t_usize_from() { - assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - test_impl_subrange!(usize:usize, (u64, u128)); - assert_eq!(u32::saturating_from(usize::MAX), u32::MAX); - test_impl_max!(u64, (usize)); - test_impl_max!(usize); // This tests all float::INFINITY. - } + fn t_saturating_sized64() { + let mut rng = fastrand::Rng::new(); - #[cfg(target_pointer_width = "128")] - #[test] - fn t_usize_from() { - assert_eq!(std::mem::size_of::(), std::mem::size_of::()); - test_impl_subrange!(usize:usize, (u128)); - assert_eq!(u32::saturating_from(usize::MAX), u32::MAX); - assert_eq!(u64::saturating_from(usize::MAX), u64::MAX); - test_impl_max!(u128, (usize)); - test_impl_max!(u128); // This tests all float::INFINITY. - test_impl_max!(usize); // This tests all float::INFINITY. - } + // isize should be fine down to i64::MIN, but usize will get floored. + for i in std::iter::repeat_with(|| rng.i64(i64::MIN..i32::MIN as i64)).take(SAMPLE_SIZE) { + cast_assert_same!(isize, i, i64, i128, isize); + cast_assert_min!( usize, i, i64, i128, isize); + } - #[cfg(any(target_pointer_width = "64", target_pointer_width = "128"))] - #[test] - #[ignore] - // Float testing is comparatively slow at 64+ bits. - fn t_usize_from_float() { - test_impl_subrange!(usize:usize, (f32, f64)); + // Below i64, isize should be floored too. + for i in std::iter::repeat_with(|| rng.i128(i128::MIN..i64::MIN as i128)).take(SAMPLE_SIZE) { + cast_assert_min!(isize, i, i128); + } + + // Both should be fine up to i64::MAX. + for i in std::iter::repeat_with(|| rng.i64((u32::MAX as i64 + 1)..=i64::MAX)).take(SAMPLE_SIZE) { + cast_assert_same!(isize, i, i64, i128, isize, u64, u128, usize); + cast_assert_same!(usize, i, i64, i128, isize, u64, u128, usize); + } + + // isize should max out after i64::MAX, but usize should be fine. + for i in std::iter::repeat_with(|| rng.u64((i64::MAX as u64 + 1)..=u64::MAX)).take(SAMPLE_SIZE) { + cast_assert_max!( isize, i, i128, u64, u128, usize); + cast_assert_same!(usize, i, i128, u64, u128, usize); + } + + // usize should max out after u64::MAX. + for i in std::iter::repeat_with(|| rng.i128((u64::MAX as i128 + 1)..=i128::MAX)).take(SAMPLE_SIZE) { + cast_assert_max!(usize, i, i128, u128); + } } }