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`.