diff --git a/CHANGELOG.md b/CHANGELOG.md index 3085994c4d..0770e5ef09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,10 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ### Fixes - Fix `WeightedIndex` panic when the sum of float weights is infinite; return `Error::Overflow` instead ([#1808]) +- Fix spurious `Error::NonFinite` from `Uniform::new_inclusive` on large finite float ranges such as `0.0..=f64::MAX` ([#1809]) [#1808]: https://github.com/rust-random/rand/pull/1808 +[#1809]: https://github.com/rust-random/rand/pull/1809 ## [0.10.2] — 2026-07-02 diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 3e91c0f3a0..b4351db511 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -66,7 +66,10 @@ macro_rules! uniform_float_impl { let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); loop { - let mask = (scale * max_rand + low).gt_mask(high); + // `not_le_mask` rather than `gt_mask` so that a non-finite + // product (which compares `false` under `>`) is also + // reduced instead of being silently accepted. + let mask = (scale * max_rand + low).not_le_mask(high); if !mask.any() { break; } @@ -126,12 +129,18 @@ macro_rules! uniform_float_impl { return Err(Error::EmptyRange); } - let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); - let scale = (high - low) / max_rand; - if !scale.all_finite() { + let range = high - low; + if !range.all_finite() { return Err(Error::NonFinite); } + let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); + // This division may overflow to infinity even where `range` is + // finite (e.g. `low = 0.0`, `high = f64::MAX`). `new_bounded` + // reduces `scale` until samples cannot exceed `high`, which + // handles the infinite case in a single step. + let scale = range / max_rand; + Ok(Self::new_bounded(low, high, scale)) } @@ -238,6 +247,8 @@ mod tests { (-<$f_scalar>::from_bits(7), -0.0), (0.1 * $f_scalar::MAX, $f_scalar::MAX), (-$f_scalar::MAX * 0.2, $f_scalar::MAX * 0.7), + (0.0, $f_scalar::MAX), + (-$f_scalar::MAX, 0.0), ]; for &(low_scalar, high_scalar) in v.iter() { for lane in 0..<$ty>::LEN { @@ -360,6 +371,10 @@ mod tests { #[test] fn test_float_overflow() { assert_eq!(Uniform::try_from(f64::MIN..f64::MAX), Err(Error::NonFinite)); + assert_eq!( + Uniform::try_from(f64::MIN..=f64::MAX), + Err(Error::NonFinite) + ); } #[test] diff --git a/src/distr/utils.rs b/src/distr/utils.rs index d3cfbf6a0f..1a74bdca1d 100644 --- a/src/distr/utils.rs +++ b/src/distr/utils.rs @@ -219,7 +219,11 @@ pub(crate) trait FloatSIMDUtils { fn all_finite(self) -> bool; type Mask; - fn gt_mask(self, other: Self) -> Self::Mask; + + // The negation of `<=`, which (unlike `>`) is also `true` for lanes which + // are not-a-number. This lets callers treat NaN like an out-of-bounds + // value rather than silently accepting it. + fn not_le_mask(self, other: Self) -> Self::Mask; // Decrease all lanes where the mask is `true` to the next lower value // representable by the floating-point type. At least one of the lanes @@ -294,8 +298,8 @@ macro_rules! scalar_float_impl { } #[inline(always)] - fn gt_mask(self, other: Self) -> Self::Mask { - self > other + fn not_le_mask(self, other: Self) -> Self::Mask { + !(self <= other) } #[inline(always)] @@ -357,8 +361,8 @@ macro_rules! simd_impl { } #[inline(always)] - fn gt_mask(self, other: Self) -> Self::Mask { - self.simd_gt(other) + fn not_le_mask(self, other: Self) -> Self::Mask { + !self.simd_le(other) } #[inline(always)]