Skip to content

Commit a681d0f

Browse files
authored
Merge pull request #36 from rust-random/push-qyzktnrnrxok
Fix Geometric::new for small p>0
2 parents a9c0e28 + ed1b718 commit a681d0f

File tree

5 files changed

+48
-10
lines changed

5 files changed

+48
-10
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- `Dirichlet` no longer uses `const` generics, which means that its size is not required at compile time. Essentially a revert of rand#1292. (#15)
1717
- Add `Dirichlet::new_with_size` constructor (#15)
1818

19+
### Fixes
20+
- Fix `Geometric::new` for small `p > 0` where `1 - p` rounds to 1 (#36)
21+
1922
## [0.5.2]
2023

2124
### API Changes

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ serde = { version = "1.0.103", features = ["derive"], optional = true }
3939
serde_with = { version = "3", optional = true }
4040

4141
[dev-dependencies]
42-
rand_pcg = { version = "0.9.0" }
42+
rand_pcg = { version = "0.10.0-rc.1" }
4343
# For inline examples
44-
rand = { version = "0.10.0-rc.0", features = ["small_rng"] }
44+
rand = { version = "0.10.0-rc.5", features = ["small_rng"] }
4545
# Histogram implementation for testing uniformity
4646
average = { version = "0.16", features = [ "std" ] }
4747
# Special functions for testing distributions

benches/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ publish = false
77
[dependencies]
88

99
[dev-dependencies]
10-
rand = { version = "0.10.0-rc.0", features = ["small_rng", "nightly"] }
11-
rand_pcg = "0.9.0"
10+
rand = { version = "0.10.0-rc.5", features = ["small_rng", "nightly"] }
11+
rand_pcg = "0.10.0-rc.1"
1212
rand_distr = { path = ".." }
1313
criterion = "0.5"
1414
criterion-cycles-per-byte = "0.6"

src/geometric.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,25 @@ impl fmt::Display for Error {
6767
impl std::error::Error for Error {}
6868

6969
impl Geometric {
70-
/// Construct a new `Geometric` with the given shape parameter `p`
71-
/// (probability of success on each trial).
70+
/// Construct a new `Geometric` distribution
71+
///
72+
/// The shape parameter `p` is the probability of success on each trial.
73+
///
74+
/// ### Edge cases
75+
///
76+
/// If `p == 0.0` or `1.0 - p` rounds to `1.0` then sampling returns
77+
/// `u64::MAX`.
7278
pub fn new(p: f64) -> Result<Self, Error> {
79+
let mut pi = 1.0 - p;
7380
if !p.is_finite() || !(0.0..=1.0).contains(&p) {
7481
Err(Error::InvalidProbability)
75-
} else if p == 0.0 || p >= 2.0 / 3.0 {
76-
Ok(Geometric { p, pi: p, k: 0 })
82+
} else if pi == 1.0 || p >= 2.0 / 3.0 {
83+
Ok(Geometric { p, pi, k: 0 })
7784
} else {
7885
let (pi, k) = {
7986
// choose smallest k such that pi = (1 - p)^(2^k) <= 0.5
8087
let mut k = 1;
81-
let mut pi = (1.0 - p).powi(2);
88+
pi = pi * pi;
8289
while pi > 0.5 {
8390
k += 1;
8491
pi = pi * pi;
@@ -106,7 +113,7 @@ impl Distribution<u64> for Geometric {
106113
return failures;
107114
}
108115

109-
if self.p == 0.0 {
116+
if self.pi == 1.0 {
110117
return u64::MAX;
111118
}
112119

@@ -264,4 +271,18 @@ mod test {
264271
fn geometric_distributions_can_be_compared() {
265272
assert_eq!(Geometric::new(1.0), Geometric::new(1.0));
266273
}
274+
275+
#[test]
276+
fn small_p() {
277+
let a = f64::EPSILON / 2.0;
278+
assert!(1.0 - a < 1.0); // largest repr. value < 1
279+
assert!(Geometric::new(a).is_ok());
280+
281+
let b = f64::EPSILON / 4.0;
282+
assert!(b > 0.0);
283+
assert!(1.0 - b == 1.0); // rounds to 1
284+
let d = Geometric::new(b).unwrap();
285+
let mut rng = crate::test::VoidRng;
286+
assert_eq!(d.sample(&mut rng), u64::MAX);
287+
}
267288
}

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ mod test {
155155
// NOTE: Some distributions have tests checking only that samples can be
156156
// generated. This is redundant with vector and correctness tests.
157157

158+
/// An RNG which panics on first use
159+
pub struct VoidRng;
160+
impl rand::RngCore for VoidRng {
161+
fn next_u32(&mut self) -> u32 {
162+
panic!("usage of VoidRng")
163+
}
164+
fn next_u64(&mut self) -> u64 {
165+
panic!("usage of VoidRng")
166+
}
167+
fn fill_bytes(&mut self, _: &mut [u8]) {
168+
panic!("usage of VoidRng")
169+
}
170+
}
171+
158172
/// Construct a deterministic RNG with the given seed
159173
pub fn rng(seed: u64) -> impl rand::RngCore {
160174
// For tests, we want a statistically good, fast, reproducible RNG.

0 commit comments

Comments
 (0)