From b5d070aafd0a008dd834412ca0b5880b397528b8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 14 Jul 2026 07:35:16 +0000 Subject: [PATCH 1/4] sample_efraimidis_spirakis: error on more than amount non-finite weights --- src/seq/index.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/seq/index.rs b/src/seq/index.rs index 65f55f64c6..e190280408 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -295,7 +295,8 @@ where /// an alternative. /// /// Error cases: -/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. +/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative +/// or more than `amount` elements are non-finite. /// /// This implementation uses `O(length + amount)` space and `O(length)` time. #[cfg(feature = "std")] @@ -341,7 +342,8 @@ where /// It uses `O(length + amount)` space and `O(length)` time. /// /// Error cases: -/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. +/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative +/// or more than `amount` elements are non-finite. #[cfg(feature = "std")] fn sample_efraimidis_spirakis( rng: &mut R, @@ -408,6 +410,10 @@ where if index < length { let mut x = rng.random::().ln() / candidates.peek().unwrap().key; while index < length { + if !x.is_finite() { + return Err(WeightError::InvalidWeight); + } + let weight = weight(index.as_usize()).into(); if weight > 0.0 { x -= weight; @@ -656,6 +662,30 @@ mod test { assert_eq!(r.unwrap().len(), 9); } + #[cfg(feature = "std")] + #[test] + fn test_sample_weighted_infinities() { + let mut rng = crate::test::rng(1351); + + for _ in 0..10 { + let result = sample_weighted(&mut rng, 5, |i| 2.0 / (2.0 - (i as f64)).abs(), 2); + assert!(result.is_ok()); + // Since one input has infinite weight, it must be selected: + assert!(result.unwrap().iter().any(|i| i == 2)); + } + + // Sampling from amount infinities should succeed + let weights = [1.0, 0.0, f32::INFINITY, 2.5, f32::INFINITY]; + let result = sample_weighted(&mut rng, weights.len(), |i| weights[i], 2); + assert!(result.is_ok()); + let mut results = result.unwrap().into_vec(); + results.sort(); + assert_eq!(results, [2, 4]); + + // Sampling from too many infinities is an error + assert!(sample_weighted(&mut rng, weights.len(), |i| weights[i], 1).is_err()); + } + #[test] fn value_stability_sample() { let do_test = |length, amount, values: &[u32]| { From 0bcd5eedc35bf49a724e468e7d674d1413390749 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 26 Jul 2026 16:06:56 +0100 Subject: [PATCH 2/4] Update doc for fn sample_weighted --- src/seq/index.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/seq/index.rs b/src/seq/index.rs index e190280408..40311cf4d4 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -295,8 +295,10 @@ where /// an alternative. /// /// Error cases: -/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative -/// or more than `amount` elements are non-finite. +/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative, +/// or when infinite weights fill the reservoir before all elements have +/// been processed (this always happens with more than `amount` infinite +/// weights, and may also happen with exactly `amount`, depending on order). /// /// This implementation uses `O(length + amount)` space and `O(length)` time. #[cfg(feature = "std")] @@ -342,8 +344,10 @@ where /// It uses `O(length + amount)` space and `O(length)` time. /// /// Error cases: -/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative -/// or more than `amount` elements are non-finite. +/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative, +/// or when infinite weights fill the reservoir before all elements have +/// been processed (this always happens with more than `amount` infinite +/// weights, and may also happen with exactly `amount`, depending on order). #[cfg(feature = "std")] fn sample_efraimidis_spirakis( rng: &mut R, From 9f1a153f66bf64d2021e4b12337fbe5a33f0eea4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Jul 2026 10:53:58 +0000 Subject: [PATCH 3/4] Note that fn Weight::checked_add_assign does not error on overflow-to-infinity --- src/distr/weighted/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/distr/weighted/mod.rs b/src/distr/weighted/mod.rs index 2bc012b197..49715e296d 100644 --- a/src/distr/weighted/mod.rs +++ b/src/distr/weighted/mod.rs @@ -28,6 +28,9 @@ pub trait Weight: Clone { /// Checked addition /// + /// Note that for floating-point formats with a representation of infinity, + /// overflow-to-infinity is not considered an error. + /// /// - `Result::Ok`: On success, `v` is added to `self` /// - `Result::Err`: Returns an error when `Self` cannot represent the /// result of `self + v` (i.e. overflow). The value of `self` should be From aa6c92d4c3006bea4f77a1dbe90d4221edfeaf0b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Jul 2026 10:59:17 +0000 Subject: [PATCH 4/4] Docs extracted from #1811 --- src/seq/slice.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 267733a827..64bdd3b671 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -88,6 +88,7 @@ pub trait IndexedRandom: Index { /// Chooses `amount` elements from the slice at random, without repetition, /// and in random order. The returned iterator is appropriate both for /// collection into a `Vec` and filling an existing buffer (see example). + /// If `amount > self.len()`, all available elements are sampled. /// /// In case this API is not sufficiently flexible, use [`index::sample`]. /// @@ -126,7 +127,7 @@ pub trait IndexedRandom: Index { /// Uniformly sample a fixed-size array of distinct elements from self /// /// Chooses `N` elements from the slice at random, without repetition, - /// and in random order. + /// and in random order. Returns `None` if (and only if) `N > self.len()`. /// /// For slices, complexity is the same as [`index::sample_array`]. ///