Merge pull request #888 from dhardy/master
Add value-stability tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0550f47..e2b0f13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,13 @@
You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful.
+## [0.7.2] - 2019-09-16
+### Fixes
+- Fix dependency on `rand_core` 0.5.1 (#890)
+
+### Additions
+- Unit tests for value stability of distributions added (#888)
+
## [0.7.1] - 2019-09-13
### Fixes
- Fix `no_std` behaviour, appropriately enable c2-chacha's `std` feature (#844)
diff --git a/Cargo.toml b/Cargo.toml
index ebd4c89..c1a0fe3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rand"
-version = "0.7.1"
+version = "0.7.2"
authors = ["The Rand Project Developers", "The Rust Project Developers"]
license = "MIT OR Apache-2.0"
readme = "README.md"
@@ -30,6 +30,8 @@
std = ["rand_core/std", "rand_chacha/std", "alloc", "getrandom"]
alloc = ["rand_core/alloc"] # enables Vec and Box support (without std)
# re-export optional WASM dependencies to avoid breakage:
+# Warning: wasm-bindgen and stdweb features will be removed in rand 0.8;
+# recommended to activate via the getrandom crate instead.
wasm-bindgen = ["getrandom_package/wasm-bindgen"]
stdweb = ["getrandom_package/stdweb"]
getrandom = ["getrandom_package", "rand_core/getrandom"]
@@ -54,7 +56,7 @@
]
[dependencies]
-rand_core = { path = "rand_core", version = "0.5" }
+rand_core = { path = "rand_core", version = "0.5.1" }
rand_pcg = { path = "rand_pcg", version = "0.2", optional = true }
# Do not depend on 'getrandom_package' directly; use the 'getrandom' feature!
# This is a dependency because: we forward wasm feature flags
diff --git a/README.md b/README.md
index 5acbadb..66100fc 100644
--- a/README.md
+++ b/README.md
@@ -93,8 +93,10 @@
- `log` enables logging via the `log` crate
- `stdweb` implies `getrandom/stdweb` to enable
`getrandom` support on `wasm32-unknown-unknown`
+ (will be removed in rand 0.8; activate via `getrandom` crate instead)
- `wasm-bindgen` implies `getrandom/wasm-bindgen` to enable
`getrandom` support on `wasm32-unknown-unknown`
+ (will be removed in rand 0.8; activate via `getrandom` crate instead)
Additionally, these features configure Rand:
diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs
index 0e6bf9a..9c2b110 100644
--- a/rand_distr/src/binomial.rs
+++ b/rand_distr/src/binomial.rs
@@ -326,4 +326,22 @@
fn test_binomial_invalid_lambda_neg() {
Binomial::new(20, -10.0).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples(n: u64, p: f64, expected: &[u64]) {
+ let distr = Binomial::new(n, p).unwrap();
+ let mut rng = crate::test::rng(353);
+ let mut buf = [0; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ // We have multiple code paths: np < 10, p > 0.5
+ test_samples(2, 0.7, &[1, 1, 2, 1]);
+ test_samples(20, 0.3, &[7, 7, 5, 7]);
+ test_samples(2000, 0.6, &[1194, 1208, 1192, 1210]);
+ }
}
diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs
index 6b0e7c6..520b607 100644
--- a/rand_distr/src/cauchy.rs
+++ b/rand_distr/src/cauchy.rs
@@ -72,8 +72,7 @@
#[cfg(test)]
mod test {
- use crate::Distribution;
- use super::Cauchy;
+ use super::*;
fn median(mut numbers: &mut [f64]) -> f64 {
sort(&mut numbers);
@@ -117,4 +116,25 @@
fn test_cauchy_invalid_scale_neg() {
Cauchy::new(0.0, -10.0).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug>(m: N, s: N, expected: &[N])
+ where Standard: Distribution<N> {
+ let distr = Cauchy::new(m, s).unwrap();
+ let mut rng = crate::test::rng(353);
+ let mut buf = [m; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ // Warning: in a few cases, results vary slightly between different
+ // platforms, presumably due to differences in precision of system impls
+ // of the tan function. We work around this by avoiding these values.
+ test_samples(100f64, 10.0, &[77.93369152808678, 90.1606912098641,
+ 125.31516221323625, 86.10217834773925]);
+ test_samples(10f32, 7.0, &[15.023088, -5.446413, 3.7092876, 3.112482]);
+ }
}
diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs
index 71cf73c..cba063a 100644
--- a/rand_distr/src/dirichlet.rs
+++ b/rand_distr/src/dirichlet.rs
@@ -107,8 +107,7 @@
#[cfg(test)]
mod test {
- use super::Dirichlet;
- use crate::Distribution;
+ use super::*;
#[test]
fn test_dirichlet() {
@@ -151,4 +150,14 @@
fn test_dirichlet_invalid_alpha() {
Dirichlet::new_with_size(0.0f64, 2).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ let mut rng = crate::test::rng(223);
+ assert_eq!(rng.sample(Dirichlet::new(vec![1.0, 2.0, 3.0]).unwrap()),
+ vec![0.12941567177708177, 0.4702121891675036, 0.4003721390554146]);
+ assert_eq!(rng.sample(Dirichlet::new_with_size(8.0, 5).unwrap()),
+ vec![0.17684200044809556, 0.29915953935953055,
+ 0.1832858056608014, 0.1425623503573967, 0.19815030417417595]);
+ }
}
diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs
index 8322489..8695252 100644
--- a/rand_distr/src/exponential.rs
+++ b/rand_distr/src/exponential.rs
@@ -121,8 +121,7 @@
#[cfg(test)]
mod test {
- use crate::Distribution;
- use super::Exp;
+ use super::*;
#[test]
fn test_exp() {
@@ -142,4 +141,28 @@
fn test_exp_invalid_lambda_neg() {
Exp::new(-10.0).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug, D: Distribution<N>>
+ (distr: D, zero: N, expected: &[N])
+ {
+ let mut rng = crate::test::rng(223);
+ let mut buf = [zero; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ test_samples(Exp1, 0f32, &[1.079617, 1.8325565, 0.04601166, 0.34471703]);
+ test_samples(Exp1, 0f64, &[1.0796170642388276, 1.8325565304274,
+ 0.04601166186842716, 0.3447170217100157]);
+
+ test_samples(Exp::new(2.0).unwrap(), 0f32,
+ &[0.5398085, 0.91627824, 0.02300583, 0.17235851]);
+ test_samples(Exp::new(1.0).unwrap(), 0f64, &[
+ 1.0796170642388276, 1.8325565304274,
+ 0.04601166186842716, 0.3447170217100157]);
+ }
}
diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs
index 4018361..bbf861b 100644
--- a/rand_distr/src/gamma.rs
+++ b/rand_distr/src/gamma.rs
@@ -417,8 +417,7 @@
#[cfg(test)]
mod test {
- use crate::Distribution;
- use super::{Beta, ChiSquared, StudentT, FisherF};
+ use super::*;
#[test]
fn test_chi_squared_one() {
@@ -482,4 +481,60 @@
fn test_beta_invalid_dof() {
Beta::new(0., 0.).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug, D: Distribution<N>>
+ (distr: D, zero: N, expected: &[N])
+ {
+ let mut rng = crate::test::rng(223);
+ let mut buf = [zero; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ // Gamma has 3 cases: shape == 1, shape < 1, shape > 1
+ test_samples(Gamma::new(1.0, 5.0).unwrap(), 0f32,
+ &[5.398085, 9.162783, 0.2300583, 1.7235851]);
+ test_samples(Gamma::new(0.8, 5.0).unwrap(), 0f32,
+ &[0.5051203, 0.9048302, 3.095812, 1.8566116]);
+ test_samples(Gamma::new(1.1, 5.0).unwrap(), 0f64, &[
+ 7.783878094584059, 1.4939528171618057,
+ 8.638017638857592, 3.0949337228829004]);
+
+ // ChiSquared has 2 cases: k == 1, k != 1
+ test_samples(ChiSquared::new(1.0).unwrap(), 0f64, &[
+ 0.4893526200348249, 1.635249736808788,
+ 0.5013580219361969, 0.1457735613733489]);
+ test_samples(ChiSquared::new(0.1).unwrap(), 0f64, &[
+ 0.014824404726978617, 0.021602123937134326,
+ 0.0000003431429746851693, 0.00000002291755769542258]);
+ test_samples(ChiSquared::new(10.0).unwrap(), 0f32,
+ &[12.693656, 6.812016, 11.082001, 12.436167]);
+
+ // FisherF has same special cases as ChiSquared on each param
+ test_samples(FisherF::new(1.0, 13.5).unwrap(), 0f32,
+ &[0.32283646, 0.048049655, 0.0788893, 1.817178]);
+ test_samples(FisherF::new(1.0, 1.0).unwrap(), 0f32,
+ &[0.29925257, 3.4392934, 9.567652, 0.020074]);
+ test_samples(FisherF::new(0.7, 13.5).unwrap(), 0f64, &[
+ 3.3196593155045124, 0.3409169916262829,
+ 0.03377989856426519, 0.00004041672861036937]);
+
+ // StudentT has same special cases as ChiSquared
+ test_samples(StudentT::new(1.0).unwrap(), 0f32,
+ &[0.54703987, -1.8545331, 3.093162, -0.14168274]);
+ test_samples(StudentT::new(1.1).unwrap(), 0f64, &[
+ 0.7729195887949754, 1.2606210611616204,
+ -1.7553606501113175, -2.377641221169782]);
+
+ // Beta has same special cases as Gamma on each param
+ test_samples(Beta::new(1.0, 0.8).unwrap(), 0f32,
+ &[0.6444564, 0.357635, 0.4110078, 0.7347192]);
+ test_samples(Beta::new(0.7, 1.2).unwrap(), 0f64, &[
+ 0.6433129944095513, 0.5373371199711573,
+ 0.10313293199269491, 0.002472280249144378]);
+ }
}
diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs
index 882754f..0413cea 100644
--- a/rand_distr/src/normal.rs
+++ b/rand_distr/src/normal.rs
@@ -185,8 +185,7 @@
#[cfg(test)]
mod tests {
- use crate::Distribution;
- use super::{Normal, LogNormal};
+ use super::*;
#[test]
fn test_normal() {
@@ -216,4 +215,36 @@
fn test_log_normal_invalid_sd() {
LogNormal::new(10.0, -1.0).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug, D: Distribution<N>>
+ (distr: D, zero: N, expected: &[N])
+ {
+ let mut rng = crate::test::rng(213);
+ let mut buf = [zero; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ test_samples(StandardNormal, 0f32,
+ &[-0.11844189, 0.781378, 0.06563994, -1.1932899]);
+ test_samples(StandardNormal, 0f64, &[
+ -0.11844188827977231, 0.7813779637772346,
+ 0.06563993969580051, -1.1932899004186373]);
+
+ test_samples(Normal::new(0.0, 1.0).unwrap(), 0f32,
+ &[-0.11844189, 0.781378, 0.06563994, -1.1932899]);
+ test_samples(Normal::new(2.0, 0.5).unwrap(), 0f64, &[
+ 1.940779055860114, 2.3906889818886174,
+ 2.0328199698479, 1.4033550497906813]);
+
+ test_samples(LogNormal::new(0.0, 1.0).unwrap(), 0f32,
+ &[0.88830346, 2.1844804, 1.0678421, 0.30322206]);
+ test_samples(LogNormal::new(2.0, 0.5).unwrap(), 0f64, &[
+ 6.964174338639032, 10.921015733601452,
+ 7.6355881556915906, 4.068828213584092]);
+ }
}
diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs
index 33ea382..ccbce9c 100644
--- a/rand_distr/src/pareto.rs
+++ b/rand_distr/src/pareto.rs
@@ -66,8 +66,7 @@
#[cfg(test)]
mod tests {
- use crate::Distribution;
- use super::Pareto;
+ use super::*;
#[test]
#[should_panic]
@@ -86,4 +85,24 @@
assert!(r >= scale);
}
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug, D: Distribution<N>>
+ (distr: D, zero: N, expected: &[N])
+ {
+ let mut rng = crate::test::rng(213);
+ let mut buf = [zero; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ test_samples(Pareto::new(1.0, 1.0).unwrap(), 0f32,
+ &[1.0423688, 2.1235929, 4.132709, 1.4679428]);
+ test_samples(Pareto::new(2.0, 0.5).unwrap(), 0f64, &[
+ 9.019295276219136, 4.3097126018270595,
+ 6.837815045397157, 105.8826669383772]);
+ }
}
diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs
index 4f4a0b7..d720ab4 100644
--- a/rand_distr/src/poisson.rs
+++ b/rand_distr/src/poisson.rs
@@ -134,8 +134,7 @@
#[cfg(test)]
mod test {
- use crate::Distribution;
- use super::Poisson;
+ use super::*;
#[test]
fn test_poisson_10() {
@@ -230,4 +229,23 @@
fn test_poisson_invalid_lambda_neg() {
Poisson::new(-10.0).unwrap();
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug, D: Distribution<N>>
+ (distr: D, zero: N, expected: &[N])
+ {
+ let mut rng = crate::test::rng(223);
+ let mut buf = [zero; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ // Special cases: < 12, >= 12
+ test_samples(Poisson::new(7.0).unwrap(), 0f32, &[5.0, 11.0, 6.0, 5.0]);
+ test_samples(Poisson::new(7.0).unwrap(), 0f64, &[9.0, 5.0, 7.0, 6.0]);
+ test_samples(Poisson::new(27.0).unwrap(), 0f32, &[28.0, 32.0, 36.0, 36.0]);
+ }
}
diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs
index ddde380..3885032 100644
--- a/rand_distr/src/weibull.rs
+++ b/rand_distr/src/weibull.rs
@@ -63,8 +63,7 @@
#[cfg(test)]
mod tests {
- use crate::Distribution;
- use super::Weibull;
+ use super::*;
#[test]
#[should_panic]
@@ -83,4 +82,24 @@
assert!(r >= 0.);
}
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<N: Float + core::fmt::Debug, D: Distribution<N>>
+ (distr: D, zero: N, expected: &[N])
+ {
+ let mut rng = crate::test::rng(213);
+ let mut buf = [zero; 4];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ test_samples(Weibull::new(1.0, 1.0).unwrap(), 0f32,
+ &[0.041495778, 0.7531094, 1.4189332, 0.38386202]);
+ test_samples(Weibull::new(2.0, 0.5).unwrap(), 0f64, &[
+ 1.1343478702739669, 0.29470010050655226,
+ 0.7556151370284702, 7.877212340241561]);
+ }
}
diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs
index eadd056..614d842 100644
--- a/src/distributions/bernoulli.rs
+++ b/src/distributions/bernoulli.rs
@@ -163,4 +163,15 @@
let avg2 = (sum2 as f64) / (N as f64);
assert!((avg2 - (NUM as f64)/(DENOM as f64)).abs() < 5e-3);
}
+
+ #[test]
+ fn value_stability() {
+ let mut rng = crate::test::rng(3);
+ let distr = Bernoulli::new(0.4532).unwrap();
+ let mut buf = [false; 10];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(buf, [true, false, false, true, false, false, true, true, true, true]);
+ }
}
diff --git a/src/distributions/float.rs b/src/distributions/float.rs
index bda523a..bef18a5 100644
--- a/src/distributions/float.rs
+++ b/src/distributions/float.rs
@@ -168,11 +168,8 @@
#[cfg(test)]
mod tests {
- use crate::Rng;
- use crate::distributions::{Open01, OpenClosed01};
+ use super::*;
use crate::rngs::mock::StepRng;
- #[cfg(feature="simd_support")]
- use packed_simd::*;
const EPSILON32: f32 = ::core::f32::EPSILON;
const EPSILON64: f64 = ::core::f64::EPSILON;
@@ -256,4 +253,46 @@
test_f64! { f64x4_edge_cases, f64x4, f64x4::splat(0.0), f64x4::splat(EPSILON64) }
#[cfg(feature="simd_support")]
test_f64! { f64x8_edge_cases, f64x8, f64x8::splat(0.0), f64x8::splat(EPSILON64) }
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<T: Copy + core::fmt::Debug + PartialEq, D: Distribution<T>>(
+ distr: &D, zero: T, expected: &[T]
+ ) {
+ let mut rng = crate::test::rng(0x6f44f5646c2a7334);
+ let mut buf = [zero; 3];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(&buf, expected);
+ }
+
+ test_samples(&Standard, 0f32, &[0.0035963655, 0.7346052, 0.09778172]);
+ test_samples(&Standard, 0f64, &[0.7346051961657583,
+ 0.20298547462974248, 0.8166436635290655]);
+
+ test_samples(&OpenClosed01, 0f32, &[0.003596425, 0.73460525, 0.09778178]);
+ test_samples(&OpenClosed01, 0f64, &[0.7346051961657584,
+ 0.2029854746297426, 0.8166436635290656]);
+
+ test_samples(&Open01, 0f32, &[0.0035963655, 0.73460525, 0.09778172]);
+ test_samples(&Open01, 0f64, &[0.7346051961657584,
+ 0.20298547462974248, 0.8166436635290656]);
+
+ #[cfg(feature="simd_support")] {
+ // We only test a sub-set of types here. Values are identical to
+ // non-SIMD types; we assume this pattern continues across all
+ // SIMD types.
+
+ test_samples(&Standard, f32x2::new(0.0, 0.0), &[
+ f32x2::new(0.0035963655, 0.7346052),
+ f32x2::new(0.09778172, 0.20298547),
+ f32x2::new(0.34296435, 0.81664366)]);
+
+ test_samples(&Standard, f64x2::new(0.0, 0.0), &[
+ f64x2::new(0.7346051961657583, 0.20298547462974248),
+ f64x2::new(0.8166436635290655, 0.7423708925400552),
+ f64x2::new(0.16387782224016323, 0.9087068770169618)]);
+ }
+ }
}
diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs
index 5238339..63cd3b9 100644
--- a/src/distributions/integer.rs
+++ b/src/distributions/integer.rs
@@ -158,8 +158,7 @@
#[cfg(test)]
mod tests {
- use crate::Rng;
- use crate::distributions::{Standard};
+ use super::*;
#[test]
fn test_integers() {
@@ -181,4 +180,64 @@
#[cfg(not(target_os = "emscripten"))]
rng.sample::<u128, _>(Standard);
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<T: Copy + core::fmt::Debug + PartialEq>(
+ zero: T, expected: &[T]
+ )
+ where Standard: Distribution<T>
+ {
+ let mut rng = crate::test::rng(807);
+ let mut buf = [zero; 3];
+ for x in &mut buf {
+ *x = rng.sample(Standard);
+ }
+ assert_eq!(&buf, expected);
+ }
+
+ test_samples(0u8, &[9, 247, 111]);
+ test_samples(0u16, &[32265, 42999, 38255]);
+ test_samples(0u32, &[2220326409, 2575017975, 2018088303]);
+ test_samples(0u64, &[11059617991457472009,
+ 16096616328739788143, 1487364411147516184]);
+ test_samples(0u128, &[296930161868957086625409848350820761097,
+ 145644820879247630242265036535529306392,
+ 111087889832015897993126088499035356354]);
+ #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))]
+ test_samples(0usize, &[2220326409, 2575017975, 2018088303]);
+ #[cfg(target_pointer_width = "64")]
+ test_samples(0usize, &[11059617991457472009,
+ 16096616328739788143, 1487364411147516184]);
+
+ test_samples(0i8, &[9, -9, 111]);
+ // Skip further i* types: they are simple reinterpretation of u* samples
+
+ #[cfg(feature="simd_support")] {
+ // We only test a sub-set of types here and make assumptions about the rest.
+
+ test_samples(u8x2::default(), &[u8x2::new(9, 126),
+ u8x2::new(247, 167), u8x2::new(111, 149)]);
+ test_samples(u8x4::default(), &[u8x4::new(9, 126, 87, 132),
+ u8x4::new(247, 167, 123, 153), u8x4::new(111, 149, 73, 120)]);
+ test_samples(u8x8::default(), &[
+ u8x8::new(9, 126, 87, 132, 247, 167, 123, 153),
+ u8x8::new(111, 149, 73, 120, 68, 171, 98, 223),
+ u8x8::new(24, 121, 1, 50, 13, 46, 164, 20)]);
+
+ test_samples(i64x8::default(), &[
+ i64x8::new(-7387126082252079607, -2350127744969763473,
+ 1487364411147516184, 7895421560427121838,
+ 602190064936008898, 6022086574635100741,
+ -5080089175222015595, -4066367846667249123),
+ i64x8::new(9180885022207963908, 3095981199532211089,
+ 6586075293021332726, 419343203796414657,
+ 3186951873057035255, 5287129228749947252,
+ 444726432079249540, -1587028029513790706),
+ i64x8::new(6075236523189346388, 1351763722368165432,
+ -6192309979959753740, -7697775502176768592,
+ -4482022114172078123, 7522501477800909500,
+ -1837258847956201231, -586926753024886735)]);
+ }
+ }
}
diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs
index 02ece6f..f431699 100644
--- a/src/distributions/mod.rs
+++ b/src/distributions/mod.rs
@@ -173,6 +173,12 @@
/// advantage of not needing to consider thread safety, and for most
/// distributions efficient state-less sampling algorithms are available.
///
+/// Implementations are typically expected to be portable with reproducible
+/// results when used with a PRNG with fixed seed; see the
+/// [portability chapter](https://rust-random.github.io/book/portability.html)
+/// of The Rust Rand Book. In some cases this does not apply, e.g. the `usize`
+/// type requires different sampling on 32-bit and 64-bit machines.
+///
/// [`sample_iter`]: Distribution::method.sample_iter
pub trait Distribution<T> {
/// Generate a random value of `T`, using `rng` as the source of randomness.
diff --git a/src/distributions/other.rs b/src/distributions/other.rs
index 6ec0473..334b6f8 100644
--- a/src/distributions/other.rs
+++ b/src/distributions/other.rs
@@ -177,8 +177,8 @@
#[cfg(test)]
mod tests {
- use crate::{Rng, RngCore, Standard};
- use crate::distributions::Alphanumeric;
+ use super::*;
+ use crate::RngCore;
#[cfg(all(not(feature="std"), feature="alloc"))] use alloc::string::String;
#[test]
@@ -217,4 +217,37 @@
}
assert!(incorrect == false);
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<T: Copy + core::fmt::Debug + PartialEq, D: Distribution<T>>(
+ distr: &D, zero: T, expected: &[T]) {
+ let mut rng = crate::test::rng(807);
+ let mut buf = [zero; 5];
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(&buf, expected);
+ }
+
+ test_samples(&Standard, 'a', &['\u{8cdac}', '\u{a346a}', '\u{80120}', '\u{ed692}', '\u{35888}']);
+ test_samples(&Alphanumeric, 'a', &['h', 'm', 'e', '3', 'M']);
+ test_samples(&Standard, false, &[true, true, false, true, false]);
+ test_samples(&Standard, None as Option<bool>,
+ &[Some(true), None, Some(false), None, Some(false)]);
+ test_samples(&Standard, Wrapping(0i32), &[Wrapping(-2074640887),
+ Wrapping(-1719949321), Wrapping(2018088303),
+ Wrapping(-547181756), Wrapping(838957336)]);
+
+ // We test only sub-sets of tuple and array impls
+ test_samples(&Standard, (), &[(), (), (), (), ()]);
+ test_samples(&Standard, (false,), &[(true,), (true,), (false,), (true,), (false,)]);
+ test_samples(&Standard, (false,false), &[(true,true), (false,true),
+ (false,false), (true,false), (false,false)]);
+
+ test_samples(&Standard, [0u8; 0], &[[], [], [], [], []]);
+ test_samples(&Standard, [0u8; 3], &[[9, 247, 111],
+ [68, 24, 13], [174, 19, 194],
+ [172, 69, 213], [149, 207, 29]]);
+ }
}
diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs
index 8c90f4e..a12305c 100644
--- a/src/distributions/uniform.rs
+++ b/src/distributions/uniform.rs
@@ -66,9 +66,7 @@
//! struct MyF32(f32);
//!
//! #[derive(Clone, Copy, Debug)]
-//! struct UniformMyF32 {
-//! inner: UniformFloat<f32>,
-//! }
+//! struct UniformMyF32(UniformFloat<f32>);
//!
//! impl UniformSampler for UniformMyF32 {
//! type X = MyF32;
@@ -76,9 +74,7 @@
//! where B1: SampleBorrow<Self::X> + Sized,
//! B2: SampleBorrow<Self::X> + Sized
//! {
-//! UniformMyF32 {
-//! inner: UniformFloat::<f32>::new(low.borrow().0, high.borrow().0),
-//! }
+//! UniformMyF32(UniformFloat::<f32>::new(low.borrow().0, high.borrow().0))
//! }
//! fn new_inclusive<B1, B2>(low: B1, high: B2) -> Self
//! where B1: SampleBorrow<Self::X> + Sized,
@@ -87,7 +83,7 @@
//! UniformSampler::new(low, high)
//! }
//! fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
-//! MyF32(self.inner.sample(rng))
+//! MyF32(self.0.sample(rng))
//! }
//! }
//!
@@ -166,9 +162,7 @@
/// [`new`]: Uniform::new
/// [`new_inclusive`]: Uniform::new_inclusive
#[derive(Clone, Copy, Debug)]
-pub struct Uniform<X: SampleUniform> {
- inner: X::Sampler,
-}
+pub struct Uniform<X: SampleUniform>(X::Sampler);
impl<X: SampleUniform> Uniform<X> {
/// Create a new `Uniform` instance which samples uniformly from the half
@@ -177,7 +171,7 @@
where B1: SampleBorrow<X> + Sized,
B2: SampleBorrow<X> + Sized
{
- Uniform { inner: X::Sampler::new(low, high) }
+ Uniform(X::Sampler::new(low, high))
}
/// Create a new `Uniform` instance which samples uniformly from the closed
@@ -186,13 +180,13 @@
where B1: SampleBorrow<X> + Sized,
B2: SampleBorrow<X> + Sized
{
- Uniform { inner: X::Sampler::new_inclusive(low, high) }
+ Uniform(X::Sampler::new_inclusive(low, high))
}
}
impl<X: SampleUniform> Distribution<X> for Uniform<X> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> X {
- self.inner.sample(rng)
+ self.0.sample(rng)
}
}
@@ -251,6 +245,17 @@
/// more optimal implementations for single usage may be provided via this
/// method (which is the case for integers and floats).
/// Results may not be identical.
+ ///
+ /// Note that to use this method in a generic context, the type needs to be
+ /// retrieved via `SampleUniform::Sampler` as follows:
+ /// ```
+ /// use rand::{thread_rng, distributions::uniform::{SampleUniform, UniformSampler}};
+ /// # #[allow(unused)]
+ /// fn sample_from_range<T: SampleUniform>(lb: T, ub: T) -> T {
+ /// let mut rng = thread_rng();
+ /// <T as SampleUniform>::Sampler::sample_single(lb, ub, &mut rng)
+ /// }
+ /// ```
fn sample_single<R: Rng + ?Sized, B1, B2>(low: B1, high: B2, rng: &mut R)
-> Self::X
where B1: SampleBorrow<Self::X> + Sized,
@@ -929,11 +934,8 @@
#[cfg(test)]
mod tests {
- use crate::Rng;
+ use super::*;
use crate::rngs::mock::StepRng;
- use crate::distributions::uniform::Uniform;
- use crate::distributions::utils::FloatAsSIMD;
- #[cfg(feature="simd_support")] use packed_simd::*;
#[should_panic]
#[test]
@@ -1211,18 +1213,14 @@
x: f32,
}
#[derive(Clone, Copy, Debug)]
- struct UniformMyF32 {
- inner: UniformFloat<f32>,
- }
+ struct UniformMyF32(UniformFloat<f32>);
impl UniformSampler for UniformMyF32 {
type X = MyF32;
fn new<B1, B2>(low: B1, high: B2) -> Self
where B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized
{
- UniformMyF32 {
- inner: UniformFloat::<f32>::new(low.borrow().x, high.borrow().x),
- }
+ UniformMyF32(UniformFloat::<f32>::new(low.borrow().x, high.borrow().x))
}
fn new_inclusive<B1, B2>(low: B1, high: B2) -> Self
where B1: SampleBorrow<Self::X> + Sized,
@@ -1231,7 +1229,7 @@
UniformSampler::new(low, high)
}
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
- MyF32 { x: self.inner.sample(rng) }
+ MyF32 { x: self.0.sample(rng) }
}
}
impl SampleUniform for MyF32 {
@@ -1250,21 +1248,59 @@
#[test]
fn test_uniform_from_std_range() {
let r = Uniform::from(2u32..7);
- assert_eq!(r.inner.low, 2);
- assert_eq!(r.inner.range, 5);
+ assert_eq!(r.0.low, 2);
+ assert_eq!(r.0.range, 5);
let r = Uniform::from(2.0f64..7.0);
- assert_eq!(r.inner.low, 2.0);
- assert_eq!(r.inner.scale, 5.0);
+ assert_eq!(r.0.low, 2.0);
+ assert_eq!(r.0.scale, 5.0);
}
#[test]
fn test_uniform_from_std_range_inclusive() {
let r = Uniform::from(2u32..=6);
- assert_eq!(r.inner.low, 2);
- assert_eq!(r.inner.range, 5);
+ assert_eq!(r.0.low, 2);
+ assert_eq!(r.0.range, 5);
let r = Uniform::from(2.0f64..=7.0);
- assert_eq!(r.inner.low, 2.0);
- assert!(r.inner.scale > 5.0);
- assert!(r.inner.scale < 5.0 + 1e-14);
+ assert_eq!(r.0.low, 2.0);
+ assert!(r.0.scale > 5.0);
+ assert!(r.0.scale < 5.0 + 1e-14);
+ }
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<T: SampleUniform + Copy + core::fmt::Debug + PartialEq>(
+ lb: T, ub: T, expected_single: &[T], expected_multiple: &[T]
+ )
+ where Uniform<T>: Distribution<T>
+ {
+ let mut rng = crate::test::rng(897);
+ let mut buf = [lb; 3];
+
+ for x in &mut buf {
+ *x = T::Sampler::sample_single(lb, ub, &mut rng);
+ }
+ assert_eq!(&buf, expected_single);
+
+ let distr = Uniform::new(lb, ub);
+ for x in &mut buf {
+ *x = rng.sample(&distr);
+ }
+ assert_eq!(&buf, expected_multiple);
+ }
+
+ // We test on a sub-set of types; possibly we should do more.
+ // TODO: SIMD types
+
+ test_samples(11u8, 219, &[17, 66, 214], &[181, 93, 165]);
+ test_samples(11u32, 219, &[17, 66, 214], &[181, 93, 165]);
+
+ test_samples(0f32, 1e-2f32, &[0.0003070104, 0.0026630748, 0.00979833],
+ &[0.008194133, 0.00398172, 0.007428536]);
+ test_samples(-1e10f64, 1e10f64,
+ &[-4673848682.871551, 6388267422.932352, 4857075081.198343],
+ &[1173375212.1808167, 1917642852.109581, 2365076174.3153973]);
+
+ test_samples(Duration::new(2, 0), Duration::new(4, 0),
+ &[Duration::new(2,532615131), Duration::new(3,638826742), Duration::new(3,485707508)], &[Duration::new(3,117337521), Duration::new(3,191764285), Duration::new(3,236507617)]);
}
}
diff --git a/src/distributions/weighted/alias_method.rs b/src/distributions/weighted/alias_method.rs
index bdd4ba0..1ba0ea2 100644
--- a/src/distributions/weighted/alias_method.rs
+++ b/src/distributions/weighted/alias_method.rs
@@ -496,4 +496,22 @@
WeightedError::InvalidWeight
);
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<W: Weight>(weights: Vec<W>, buf: &mut [usize], expected: &[usize]) {
+ assert_eq!(buf.len(), expected.len());
+ let distr = WeightedIndex::new(weights).unwrap();
+ let mut rng = crate::test::rng(0x9c9fa0b0580a7031);
+ for r in buf.iter_mut() {
+ *r = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ let mut buf = [0; 10];
+ test_samples(vec![1i32,1,1,1,1,1,1,1,1], &mut buf, &[6, 5, 7, 5, 8, 7, 6, 2, 3, 7]);
+ test_samples(vec![0.7f32, 0.1, 0.1, 0.1], &mut buf, &[2, 0, 0, 0, 0, 0, 0, 0, 1, 3]);
+ test_samples(vec![1.0f64, 0.999, 0.998, 0.997], &mut buf, &[2, 1, 2, 3, 2, 1, 3, 2, 1, 1]);
+ }
}
diff --git a/src/distributions/weighted/mod.rs b/src/distributions/weighted/mod.rs
index 2711637..720859b 100644
--- a/src/distributions/weighted/mod.rs
+++ b/src/distributions/weighted/mod.rs
@@ -316,6 +316,35 @@
assert_eq!(distr.cumulative_weights, expected_distr.cumulative_weights);
}
}
+
+ #[test]
+ fn value_stability() {
+ fn test_samples<X: SampleUniform + PartialOrd, I>
+ (
+ weights: I,
+ buf: &mut [usize],
+ expected: &[usize]
+ )
+ where I: IntoIterator,
+ I::Item: SampleBorrow<X>,
+ X: for<'a> ::core::ops::AddAssign<&'a X> +
+ Clone +
+ Default
+ {
+ assert_eq!(buf.len(), expected.len());
+ let distr = WeightedIndex::new(weights).unwrap();
+ let mut rng = crate::test::rng(701);
+ for r in buf.iter_mut() {
+ *r = rng.sample(&distr);
+ }
+ assert_eq!(buf, expected);
+ }
+
+ let mut buf = [0; 10];
+ test_samples(&[1i32,1,1,1,1,1,1,1,1], &mut buf, &[0, 6, 2, 6, 3, 4, 7, 8, 2, 5]);
+ test_samples(&[0.7f32, 0.1, 0.1, 0.1], &mut buf, &[0, 0, 0, 1, 0, 0, 2, 3, 0, 0]);
+ test_samples(&[1.0f64, 0.999, 0.998, 0.997], &mut buf, &[2, 2, 1, 3, 2, 1, 3, 3, 2, 1]);
+ }
}
/// Error type returned from `WeightedIndex::new`.