From 579d7f3ac99e753d3492c88a70de9459cc2383aa Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 1 Dec 2025 08:21:11 +0000 Subject: [PATCH 1/4] Add VoidRng --- src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2d01a19..dfa5e7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,6 +155,20 @@ mod test { // NOTE: Some distributions have tests checking only that samples can be // generated. This is redundant with vector and correctness tests. + /// An RNG which panics on first use + pub struct VoidRng; + impl rand::RngCore for VoidRng { + fn next_u32(&mut self) -> u32 { + panic!("usage of VoidRng") + } + fn next_u64(&mut self) -> u64 { + panic!("usage of VoidRng") + } + fn fill_bytes(&mut self, _: &mut [u8]) { + panic!("usage of VoidRng") + } + } + /// Construct a deterministic RNG with the given seed pub fn rng(seed: u64) -> impl rand::RngCore { // For tests, we want a statistically good, fast, reproducible RNG. From 5c9bedccfbfe2ec9d89bcbe353e0ded8b6b1131d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 1 Dec 2025 08:21:22 +0000 Subject: [PATCH 2/4] Geometric: handle p>0 where 1-p rounds to 1 --- CHANGELOG.md | 3 +++ src/geometric.rs | 23 +++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f448097..0831df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Dirichlet` no longer uses `const` generics, which means that its size is not required at compile time. Essentially a revert of rand#1292. (#15) - Add `Dirichlet::new_with_size` constructor (#15) +### Fixes +- Fix `Geometric::new` for small `p > 0` where `1 - p` rounds to 1 (#36) + ## [0.5.2] ### API Changes diff --git a/src/geometric.rs b/src/geometric.rs index 74d30a4..9026bab 100644 --- a/src/geometric.rs +++ b/src/geometric.rs @@ -70,15 +70,16 @@ impl Geometric { /// Construct a new `Geometric` with the given shape parameter `p` /// (probability of success on each trial). pub fn new(p: f64) -> Result { + let mut pi = 1.0 - p; if !p.is_finite() || !(0.0..=1.0).contains(&p) { Err(Error::InvalidProbability) - } else if p == 0.0 || p >= 2.0 / 3.0 { - Ok(Geometric { p, pi: p, k: 0 }) + } else if pi == 1.0 || p >= 2.0 / 3.0 { + Ok(Geometric { p, pi, k: 0 }) } else { let (pi, k) = { // choose smallest k such that pi = (1 - p)^(2^k) <= 0.5 let mut k = 1; - let mut pi = (1.0 - p).powi(2); + pi = pi * pi; while pi > 0.5 { k += 1; pi = pi * pi; @@ -106,7 +107,7 @@ impl Distribution for Geometric { return failures; } - if self.p == 0.0 { + if self.pi == 1.0 { return u64::MAX; } @@ -264,4 +265,18 @@ mod test { fn geometric_distributions_can_be_compared() { assert_eq!(Geometric::new(1.0), Geometric::new(1.0)); } + + #[test] + fn small_p() { + let a = f64::EPSILON / 2.0; + assert!(1.0 - a < 1.0); // largest repr. value < 1 + assert!(Geometric::new(a).is_ok()); + + let b = f64::EPSILON / 4.0; + assert!(b > 0.0); + assert!(1.0 - b == 1.0); // rounds to 1 + let d = Geometric::new(b).unwrap(); + let mut rng = crate::test::VoidRng; + assert_eq!(d.sample(&mut rng), u64::MAX); + } } From a45c12476ca1ca255046ebee326f6fa78d78e25f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 1 Dec 2025 08:47:15 +0000 Subject: [PATCH 3/4] Bump rand, rand_pcg pre-release versions --- Cargo.toml | 4 ++-- benches/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94bcd11..6856af1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,9 +39,9 @@ serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = "3", optional = true } [dev-dependencies] -rand_pcg = { version = "0.9.0" } +rand_pcg = { version = "0.10.0-rc.1" } # For inline examples -rand = { version = "0.10.0-rc.0", features = ["small_rng"] } +rand = { version = "0.10.0-rc.5", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.16", features = [ "std" ] } # Special functions for testing distributions diff --git a/benches/Cargo.toml b/benches/Cargo.toml index dee9c49..4d2d3ce 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -7,8 +7,8 @@ publish = false [dependencies] [dev-dependencies] -rand = { version = "0.10.0-rc.0", features = ["small_rng", "nightly"] } -rand_pcg = "0.9.0" +rand = { version = "0.10.0-rc.5", features = ["small_rng", "nightly"] } +rand_pcg = "0.10.0-rc.1" rand_distr = { path = ".." } criterion = "0.5" criterion-cycles-per-byte = "0.6" From ed1b71806956bdfaf38a0f736f1580c4a3e5d92f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 1 Dec 2025 10:44:51 +0000 Subject: [PATCH 4/4] Document fn Geometric::new edge case --- src/geometric.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/geometric.rs b/src/geometric.rs index 9026bab..59db3d4 100644 --- a/src/geometric.rs +++ b/src/geometric.rs @@ -67,8 +67,14 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Geometric { - /// Construct a new `Geometric` with the given shape parameter `p` - /// (probability of success on each trial). + /// Construct a new `Geometric` distribution + /// + /// The shape parameter `p` is the probability of success on each trial. + /// + /// ### Edge cases + /// + /// If `p == 0.0` or `1.0 - p` rounds to `1.0` then sampling returns + /// `u64::MAX`. pub fn new(p: f64) -> Result { let mut pi = 1.0 - p; if !p.is_finite() || !(0.0..=1.0).contains(&p) {