From ef5b8ab45064585604b577184740adee39377a25 Mon Sep 17 00:00:00 2001 From: Teddy Tennant Date: Sat, 11 Jul 2026 13:25:25 -0400 Subject: [PATCH 1/5] Fix Uniform::new_inclusive overflow on large finite float ranges Dividing high - low by 1 - EPSILON can round to infinity even when the range itself is finite (e.g. 0.0..=f64::MAX), yielding a spurious NonFinite error while Uniform::new and sample_single_inclusive both accept the same range. Clamp infinite lanes to the largest finite value and let new_bounded reduce scale as usual, so that samples still cannot exceed high. Noted by dhardy in #1603. --- CHANGELOG.md | 7 +++++++ src/distr/uniform_float.rs | 20 ++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b39cb06b..e964ac82b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ A [separate changelog is kept for rand_core](https://github.com/rust-random/core You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [Unreleased] + +### Fixes +- Fix spurious `Error::NonFinite` from `Uniform::new_inclusive` on large finite float ranges such as `0.0..=f64::MAX` ([#1809]) + +[#1809]: https://github.com/rust-random/rand/pull/1809 + ## [0.10.2] — 2026-07-02 ### Fixes diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 3e91c0f3a0..ec3520defc 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -126,10 +126,20 @@ macro_rules! uniform_float_impl { return Err(Error::EmptyRange); } + 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); - let scale = (high - low) / max_rand; + let mut scale = range / max_rand; if !scale.all_finite() { - return Err(Error::NonFinite); + // The division above may overflow to infinity even though + // `range` is finite (e.g. `low = 0.0`, `high = f64::MAX`). + // Replace infinite lanes with the largest finite value; + // `new_bounded` reduces `scale` as required to ensure that + // samples can never exceed `high`. + scale = scale.decrease_masked(scale.gt_mask(<$ty>::splat($f_scalar::MAX))); } Ok(Self::new_bounded(low, high, scale)) @@ -238,6 +248,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 +372,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] From cf45c34fe16ad7b46037269d69e58aa3505ec821 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 5 Aug 2026 07:37:46 +0000 Subject: [PATCH 2/5] Add tests for decrease_masked --- src/distr/utils.rs | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/distr/utils.rs b/src/distr/utils.rs index d3cfbf6a0f..b806203a8a 100644 --- a/src/distr/utils.rs +++ b/src/distr/utils.rs @@ -223,7 +223,7 @@ pub(crate) trait FloatSIMDUtils { // 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 - // must be set. + // must be set. Inputs may be non-finite but must not be NaN. fn decrease_masked(self, mask: Self::Mask) -> Self; // Convert from int value. Conversion is done while retaining the numerical @@ -401,3 +401,41 @@ macro_rules! simd_impl { simd_impl!(f32, u32); #[cfg(feature = "simd_support")] simd_impl!(f64, u64); + +#[cfg(test)] +mod test { + use crate::distr::utils::FloatSIMDUtils; + #[cfg(feature = "simd_support")] + use std::simd::{Mask, Simd}; + + #[test] + fn decrease_masked() { + assert_eq!((-1.0 - f32::EPSILON).decrease_masked(true), -1.0); + assert_eq!((1.0 + f64::EPSILON).decrease_masked(true), 1.0); + + #[cfg(feature = "simd_support")] + assert_eq!( + Simd::::splat(1.0 + f32::EPSILON).decrease_masked(Mask::splat(true)), + Simd::splat(1.0) + ); + + #[cfg(feature = "simd_support")] + assert_eq!( + Simd::::from_array([-1.0, -1.0 - f64::EPSILON]) + .decrease_masked(Mask::from_array([false, true])), + Simd::splat(-1.0) + ); + } + + #[test] + fn decrease_masked_infinity() { + assert_eq!(f32::INFINITY.decrease_masked(true), f32::MAX); + assert_eq!((-f64::INFINITY).decrease_masked(true), -f64::MAX); + + #[cfg(feature = "simd_support")] + assert_eq!( + Simd::::splat(-f32::INFINITY).decrease_masked(Mask::splat(true)), + Simd::splat(-f32::MAX) + ); + } +} From 2e07b4702f0568c60d583ca70710a072634b1906 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 5 Aug 2026 07:13:19 +0000 Subject: [PATCH 3/5] Allow fn UniformFloat::new_bounded to handle infinite scale --- src/distr/uniform_float.rs | 9 ++++++--- src/distr/utils.rs | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index ec3520defc..a82e3d574c 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -57,6 +57,9 @@ macro_rules! uniform_float_impl { /// Construct, reducing `scale` as required to ensure that rounding /// can never yield values greater than `high`. /// + /// Requirements: `low` and `high` must be finite. `scale` may be + /// infinite but must not be NaN. + /// /// Note: though it may be tempting to use a variant of this method /// to ensure that samples from `[low, high)` are always strictly /// less than `high`, this approach may be very slow where @@ -66,11 +69,11 @@ 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); - if !mask.any() { + let mask = (scale * max_rand + low).le_mask(high); + if mask.all() { break; } - scale = scale.decrease_masked(mask); + scale = scale.decrease_masked(!mask); } debug_assert!(<$ty>::splat(0.0).all_le(scale)); diff --git a/src/distr/utils.rs b/src/distr/utils.rs index b806203a8a..cfcf831d49 100644 --- a/src/distr/utils.rs +++ b/src/distr/utils.rs @@ -220,6 +220,7 @@ pub(crate) trait FloatSIMDUtils { type Mask; fn gt_mask(self, other: Self) -> Self::Mask; + fn 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 @@ -262,12 +263,12 @@ impl IntAsSIMD for u32 {} impl IntAsSIMD for u64 {} pub(crate) trait BoolAsSIMD: Sized { - fn any(self) -> bool; + fn all(self) -> bool; } impl BoolAsSIMD for bool { #[inline(always)] - fn any(self) -> bool { + fn all(self) -> bool { self } } @@ -298,6 +299,11 @@ macro_rules! scalar_float_impl { self > other } + #[inline(always)] + fn le_mask(self, other: Self) -> Self::Mask { + self <= other + } + #[inline(always)] fn decrease_masked(self, mask: Self::Mask) -> Self { debug_assert!(mask, "At least one lane must be set"); @@ -361,6 +367,11 @@ macro_rules! simd_impl { self.simd_gt(other) } + #[inline(always)] + fn le_mask(self, other: Self) -> Self::Mask { + self.simd_le(other) + } + #[inline(always)] fn decrease_masked(self, mask: Self::Mask) -> Self { // Casting a mask into ints will produce all bits set for From d0e5cc6ae24a60c092cd3f842c167d130eb699f2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 5 Aug 2026 07:47:51 +0000 Subject: [PATCH 4/5] Remove call to decrease_masked outside of new_bounded --- src/distr/uniform_float.rs | 10 +--------- src/distr/utils.rs | 11 ----------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index a82e3d574c..8ac518f269 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -135,15 +135,7 @@ macro_rules! uniform_float_impl { } let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); - let mut scale = range / max_rand; - if !scale.all_finite() { - // The division above may overflow to infinity even though - // `range` is finite (e.g. `low = 0.0`, `high = f64::MAX`). - // Replace infinite lanes with the largest finite value; - // `new_bounded` reduces `scale` as required to ensure that - // samples can never exceed `high`. - scale = scale.decrease_masked(scale.gt_mask(<$ty>::splat($f_scalar::MAX))); - } + let scale = range / max_rand; Ok(Self::new_bounded(low, high, scale)) } diff --git a/src/distr/utils.rs b/src/distr/utils.rs index cfcf831d49..4d0dca11ec 100644 --- a/src/distr/utils.rs +++ b/src/distr/utils.rs @@ -219,7 +219,6 @@ pub(crate) trait FloatSIMDUtils { fn all_finite(self) -> bool; type Mask; - fn gt_mask(self, other: Self) -> Self::Mask; fn le_mask(self, other: Self) -> Self::Mask; // Decrease all lanes where the mask is `true` to the next lower value @@ -294,11 +293,6 @@ macro_rules! scalar_float_impl { self.is_finite() } - #[inline(always)] - fn gt_mask(self, other: Self) -> Self::Mask { - self > other - } - #[inline(always)] fn le_mask(self, other: Self) -> Self::Mask { self <= other @@ -362,11 +356,6 @@ macro_rules! simd_impl { self.is_finite().all() } - #[inline(always)] - fn gt_mask(self, other: Self) -> Self::Mask { - self.simd_gt(other) - } - #[inline(always)] fn le_mask(self, other: Self) -> Self::Mask { self.simd_le(other) From 7f3141f2fce49a18b59a19a4a52760df72579499 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 5 Aug 2026 07:54:45 +0000 Subject: [PATCH 5/5] Amend CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0770e5ef09..4abf2b69d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +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]) +- Fix spurious `Error::NonFinite` from `Uniform::new_inclusive` on large finite float ranges such as `0.0..=f64::MAX` ([#1821]) [#1808]: https://github.com/rust-random/rand/pull/1808 -[#1809]: https://github.com/rust-random/rand/pull/1809 +[#1821]: https://github.com/rust-random/rand/pull/1821 ## [0.10.2] — 2026-07-02