From 20afad9b7d6e9fbbf955d7db400de2d08759db2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=20=5BArtyom=20Pavlov=5D?= Date: Tue, 20 Oct 2020 05:17:56 +0300 Subject: [PATCH] merge master --- CHANGELOG.md | 11 +- Cargo.toml | 9 +- README.md | 8 +- rand_chacha/src/guts.rs | 1 + rand_core/Cargo.toml | 2 +- rand_core/src/error.rs | 15 ++ rand_core/src/lib.rs | 1 - rand_distr/CHANGELOG.md | 7 +- rand_distr/benches/distributions.rs | 16 +- rand_distr/src/binomial.rs | 37 +++-- rand_distr/src/gamma.rs | 180 +++++++++++++++++++++-- rand_distr/src/inverse_gaussian.rs | 1 + rand_distr/src/lib.rs | 67 ++++++--- rand_distr/src/normal.rs | 158 +++++++++++++++++--- rand_distr/src/unit_circle.rs | 17 --- rand_distr/src/unit_sphere.rs | 17 --- rand_distr/src/weighted_alias.rs | 6 +- rand_distr/tests/value_stability.rs | 32 ++-- rand_pcg/src/lib.rs | 1 - src/distributions/integer.rs | 10 +- src/distributions/mod.rs | 12 +- src/distributions/uniform.rs | 12 +- src/distributions/utils.rs | 7 +- src/lib.rs | 7 +- src/rng.rs | 17 ++- src/rngs/mod.rs | 8 +- src/rngs/small.rs | 34 +++-- src/rngs/thread.rs | 57 +++++--- src/rngs/xoshiro128plusplus.rs | 118 +++++++++++++++ src/rngs/xoshiro256plusplus.rs | 122 ++++++++++++++++ src/seq/mod.rs | 217 +++++++++++++++++++++++++++- utils/ci/script.sh | 1 + 32 files changed, 990 insertions(+), 218 deletions(-) create mode 100644 src/rngs/xoshiro128plusplus.rs create mode 100644 src/rngs/xoshiro256plusplus.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8b3236eae..3d6cc136d75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,19 +14,28 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Added a `serde1` feature and added Serialize/Deserialize to `UniformInt` and `WeightedIndex` (#974) - Document types supported by `random` (#994) - Implement weighted sampling without replacement (#976, #1013) +- Add `IteratorRandom::choose_stable` as an alternative to `choose` which does not depend on size hints (#1057) ### Changes +- `getrandom` updated to v0.2 (#1041) +- `ThreadRng` is no longer `Copy` to enable safe usage within thread-local destructors (see #968) - `gen_range(a, b)` was replaced with `gen_range(a..b)`, and `gen_range(a..=b)` is supported (#744, #1003). Note that `a` and `b` can no longer be references or SIMD types. - Replace `AsByteSliceMut` with `Fill` (#940) - Move alias method for `WeightedIndex` to `rand_distr` (#945) - `Alphanumeric` samples bytes instead of chars (#935) - The minimum supported Rust version is now 1.36 (#1011) +- Restrict `rand::rngs::adapter` to `std` (#1027) - Better NaN handling for `WeightedIndex` (#1005) - Implement `IntoIterator` for `IndexVec`, replacing the `into_iter` method (#1007) - Reduce packaged crate size (#983) -- Drop some unsafe code (#962, #963) +- Drop some unsafe code (#962, #963, #1011) - Improve treatment of rounding errors in `WeightedIndex::update_weights` (#956) +- `StdRng`: Switch from ChaCha20 to ChaCha12 for better performance (#1028) +- `SmallRng`: Replace PCG algorithm with xoshiro{128,256}++ (#1038) +- The `nightly` feature no longer implies the `simd_support` feature (#1048) +- Fix `simd_support` feature to work on current nightlies (#1056) +- Improve accuracy and performance of `IteratorRandom::choose` (#1059) ## [0.7.3] - 2020-01-10 ### Fixes diff --git a/Cargo.toml b/Cargo.toml index c0d8cba184a..9284f3084fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ appveyor = { repository = "rust-random/rand" } [features] # Meta-features: default = ["std", "std_rng"] -nightly = ["simd_support"] # enables all features requiring nightly rust +nightly = [] # enables performance optimizations requiring nightly rust serde1 = ["serde"] # Option (enabled by default): without "std" rand uses libcore; this option @@ -43,7 +43,7 @@ simd_support = ["packed_simd"] std_rng = ["rand_chacha", "rand_hc"] # Option: enable SmallRng -small_rng = ["rand_pcg"] +small_rng = [] [workspace] members = [ @@ -56,14 +56,13 @@ members = [ [dependencies] rand_core = { path = "rand_core", version = "0.5.1" } -rand_pcg = { path = "rand_pcg", version = "0.2.1", optional = true } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } [dependencies.packed_simd] # NOTE: so far no version works reliably due to dependence on unstable features -version = "0.3" -# git = "https://github.com/rust-lang-nursery/packed_simd" +package = "packed_simd_2" +version = "0.3.4" optional = true features = ["into_bits"] diff --git a/README.md b/README.md index 0fc38546c99..867a543b91a 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,13 @@ Optionally, the following dependencies can be enabled: Additionally, these features configure Rand: - `small_rng` enables inclusion of the `SmallRng` PRNG -- `nightly` enables all experimental features +- `nightly` enables some optimizations requiring nightly Rust - `simd_support` (experimental) enables sampling of SIMD values - (uniformly random SIMD integers and floats) + (uniformly random SIMD integers and floats), requiring nightly Rust + +Note that nightly features are not stable and therefore not all library and +compiler versions will be compatible. This is especially true of Rand's +experimental `simd_support` feature. Rand supports limited functionality in `no_std` mode (enabled via `default-features = false`). In this case, `OsRng` and `from_entropy` are diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 38ea5bde3b1..27ff957a92c 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -101,6 +101,7 @@ impl ChaCha { } } +#[allow(clippy::many_single_char_names)] #[inline(always)] fn refill_wide_impl( m: Mach, state: &mut ChaCha, drounds: u32, out: &mut [u8; BUFSZ], diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 1f18edabc23..4b85940ff70 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -25,7 +25,7 @@ serde1 = ["serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } -getrandom = { version = "0.1", optional = true } +getrandom = { version = "0.2", optional = true } [package.metadata.docs.rs] # To build locally: diff --git a/rand_core/src/error.rs b/rand_core/src/error.rs index b11e170c02f..d2e467da84d 100644 --- a/rand_core/src/error.rs +++ b/rand_core/src/error.rs @@ -28,10 +28,14 @@ pub struct Error { impl Error { /// Codes at or above this point can be used by users to define their own /// custom errors. + /// + /// This is identical to [`getrandom::Error::CUSTOM_START`](https://docs.rs/getrandom/latest/getrandom/struct.Error.html#associatedconstant.CUSTOM_START). pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30); /// Codes below this point represent OS Errors (i.e. positive i32 values). /// Codes at or above this point, but below [`Error::CUSTOM_START`] are /// reserved for use by the `rand` and `getrandom` crates. + /// + /// This is identical to [`getrandom::Error::INTERNAL_START`](https://docs.rs/getrandom/latest/getrandom/struct.Error.html#associatedconstant.INTERNAL_START). pub const INTERNAL_START: u32 = 1 << 31; /// Construct from any type supporting `std::error::Error` @@ -208,3 +212,14 @@ impl fmt::Display for ErrorCode { #[cfg(feature = "std")] impl std::error::Error for ErrorCode {} + +#[cfg(test)] +mod test { + #[cfg(feature = "getrandom")] + #[test] + fn test_error_codes() { + // Make sure the values are the same as in `getrandom`. + assert_eq!(super::Error::CUSTOM_START, getrandom::Error::CUSTOM_START); + assert_eq!(super::Error::INTERNAL_START, getrandom::Error::INTERNAL_START); + } +} diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index c38ae245dd3..bcebe487226 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -35,7 +35,6 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] -#![allow(clippy::unreadable_literal)] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![no_std] diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index f93a2af1173..d3e30df9003 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +- New `Beta` sampling algorithm for improved performance and accuracy (#1000) +- `Normal` and `LogNormal` now support `from_mean_cv` and `from_zscore` (#1044) +- Variants of `NormalError` changed (#1044) + ## [0.3.0] - 2020-08-25 - Move alias method for `WeightedIndex` from `rand` (#945) - Rename `WeightedIndex` to `WeightedAliasIndex` (#1008) @@ -12,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove `Distribution` impl for `Poisson` (#987) - Tweak `Dirichlet` and `alias_method` to use boxed slice instead of `Vec` (#987) - Use whitelist for package contents, reducing size by 5kb (#983) -- Add case `lambda = 0` in the parametrixation of `Exp` (#972) +- Add case `lambda = 0` in the parametrization of `Exp` (#972) - Implement inverse Gaussian distribution (#954) - Reformatting and use of `rustfmt::skip` (#926) - All error types now implement `std::error::Error` (#919) diff --git a/rand_distr/benches/distributions.rs b/rand_distr/benches/distributions.rs index b0cd96dc6ab..6776901d224 100644 --- a/rand_distr/benches/distributions.rs +++ b/rand_distr/benches/distributions.rs @@ -20,7 +20,7 @@ use std::mem::size_of; use test::Bencher; use rand::prelude::*; -use rand_distr::{weighted::WeightedIndex, *}; +use rand_distr::*; // At this time, distributions are optimised for 64-bit platforms. use rand_pcg::Pcg64Mcg; @@ -112,11 +112,15 @@ distr_float!(distr_normal, f64, Normal::new(-1.23, 4.56).unwrap()); distr_float!(distr_log_normal, f64, LogNormal::new(-1.23, 4.56).unwrap()); distr_float!(distr_gamma_large_shape, f64, Gamma::new(10., 1.0).unwrap()); distr_float!(distr_gamma_small_shape, f64, Gamma::new(0.1, 1.0).unwrap()); +distr_float!(distr_beta_small_param, f64, Beta::new(0.1, 0.1).unwrap()); +distr_float!(distr_beta_large_param_similar, f64, Beta::new(101., 95.).unwrap()); +distr_float!(distr_beta_large_param_different, f64, Beta::new(10., 1000.).unwrap()); +distr_float!(distr_beta_mixed_param, f64, Beta::new(0.5, 100.).unwrap()); distr_float!(distr_cauchy, f64, Cauchy::new(4.2, 6.9).unwrap()); distr_float!(distr_triangular, f64, Triangular::new(0., 1., 0.9).unwrap()); distr_int!(distr_binomial, u64, Binomial::new(20, 0.7).unwrap()); distr_int!(distr_binomial_small, u64, Binomial::new(1000000, 1e-30).unwrap()); -distr_int!(distr_poisson, u64, Poisson::new(4.0).unwrap()); +distr_float!(distr_poisson, f64, Poisson::new(4.0).unwrap()); distr!(distr_bernoulli, bool, Bernoulli::new(0.18).unwrap()); distr_arr!(distr_circle, [f64; 2], UnitCircle); distr_arr!(distr_sphere, [f64; 3], UnitSphere); @@ -127,10 +131,10 @@ distr_int!(distr_weighted_u32, usize, WeightedIndex::new(&[1u32, 2, 3, 4, 12, 0, distr_int!(distr_weighted_f64, usize, WeightedIndex::new(&[1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); distr_int!(distr_weighted_large_set, usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); -distr_int!(distr_weighted_alias_method_i8, usize, weighted::alias_method::WeightedIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); -distr_int!(distr_weighted_alias_method_u32, usize, weighted::alias_method::WeightedIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); -distr_int!(distr_weighted_alias_method_f64, usize, weighted::alias_method::WeightedIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); -distr_int!(distr_weighted_alias_method_large_set, usize, weighted::alias_method::WeightedIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); +distr_int!(distr_weighted_alias_method_i8, usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); +distr_int!(distr_weighted_alias_method_u32, usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); +distr_int!(distr_weighted_alias_method_f64, usize, WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); +distr_int!(distr_weighted_alias_method_large_set, usize, WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); #[bench] diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index eec3c5b34b3..476ae64f559 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -12,6 +12,7 @@ use crate::{Distribution, Uniform}; use rand::Rng; use core::fmt; +use core::cmp::Ordering; /// The binomial distribution `Binomial(n, p)`. /// @@ -210,24 +211,28 @@ impl Distribution for Binomial { let s = p / q; let a = s * (n + 1.); let mut f = 1.0; - if m < y { - let mut i = m; - loop { - i += 1; - f *= a / (i as f64) - s; - if i == y { - break; + match m.cmp(&y) { + Ordering::Less => { + let mut i = m; + loop { + i += 1; + f *= a / (i as f64) - s; + if i == y { + break; + } } - } - } else if m > y { - let mut i = y; - loop { - i += 1; - f /= a / (i as f64) - s; - if i == m { - break; + }, + Ordering::Greater => { + let mut i = y; + loop { + i += 1; + f /= a / (i as f64) - s; + if i == m { + break; + } } - } + }, + Ordering::Equal => {}, } if v > f { continue; diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 34cb45dfb36..5e98dbdfcfc 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -495,6 +495,38 @@ where } } +/// The algorithm used for sampling the Beta distribution. +/// +/// Reference: +/// +/// R. C. H. Cheng (1978). +/// Generating beta variates with nonintegral shape parameters. +/// Communications of the ACM 21, 317-322. +/// https://doi.org/10.1145/359460.359482 +#[derive(Clone, Copy, Debug)] +enum BetaAlgorithm { + BB(BB), + BC(BC), +} + +/// Algorithm BB for `min(alpha, beta) > 1`. +#[derive(Clone, Copy, Debug)] +struct BB { + alpha: N, + beta: N, + gamma: N, +} + +/// Algorithm BC for `min(alpha, beta) <= 1`. +#[derive(Clone, Copy, Debug)] +struct BC { + alpha: N, + beta: N, + delta: N, + kappa1: N, + kappa2: N, +} + /// The Beta distribution with shape parameters `alpha` and `beta`. /// /// # Example @@ -510,12 +542,10 @@ where pub struct Beta where F: Float, - StandardNormal: Distribution, - Exp1: Distribution, Open01: Distribution, { - gamma_a: Gamma, - gamma_b: Gamma, + a: F, b: F, switched_params: bool, + algorithm: BetaAlgorithm, } /// Error type returned from `Beta::new`. @@ -542,31 +572,142 @@ impl std::error::Error for BetaError {} impl Beta where F: Float, - StandardNormal: Distribution, - Exp1: Distribution, Open01: Distribution, { /// Construct an object representing the `Beta(alpha, beta)` /// distribution. pub fn new(alpha: F, beta: F) -> Result, BetaError> { - Ok(Beta { - gamma_a: Gamma::new(alpha, F::one()).map_err(|_| BetaError::AlphaTooSmall)?, - gamma_b: Gamma::new(beta, F::one()).map_err(|_| BetaError::BetaTooSmall)?, - }) + if !(alpha > F::zero()) { + return Err(BetaError::AlphaTooSmall); + } + if !(beta > F::zero()) { + return Err(BetaError::BetaTooSmall); + } + // From now on, we use the notation from the reference, + // i.e. `alpha` and `beta` are renamed to `a0` and `b0`. + let (a0, b0) = (alpha, beta); + let (a, b, switched_params) = if a0 < b0 { + (a0, b0, false) + } else { + (b0, a0, true) + }; + if a > F::one() { + // Algorithm BB + let alpha = a + b; + let beta = ((alpha - F::from(2.).unwrap()) + / (F::from(2.).unwrap()*a*b - alpha)).sqrt(); + let gamma = a + F::one() / beta; + + Ok(Beta { + a, b, switched_params, + algorithm: BetaAlgorithm::BB(BB { + alpha, beta, gamma, + }) + }) + } else { + // Algorithm BC + // + // Here `a` is the maximum instead of the minimum. + let (a, b, switched_params) = (b, a, !switched_params); + let alpha = a + b; + let beta = F::one() / b; + let delta = F::one() + a - b; + let kappa1 = delta + * (F::from(1. / 18. / 4.).unwrap() + F::from(3. / 18. / 4.).unwrap()*b) + / (a*beta - F::from(14. / 18.).unwrap()); + let kappa2 = F::from(0.25).unwrap() + + (F::from(0.5).unwrap() + F::from(0.25).unwrap()/delta)*b; + + Ok(Beta { + a, b, switched_params, + algorithm: BetaAlgorithm::BC(BC { + alpha, beta, delta, kappa1, kappa2, + }) + }) + } } } impl Distribution for Beta where F: Float, - StandardNormal: Distribution, - Exp1: Distribution, Open01: Distribution, { fn sample(&self, rng: &mut R) -> F { - let x = self.gamma_a.sample(rng); - let y = self.gamma_b.sample(rng); - x / (x + y) + let mut w; + match self.algorithm { + BetaAlgorithm::BB(algo) => { + loop { + // 1. + let u1 = rng.sample(Open01); + let u2 = rng.sample(Open01); + let v = algo.beta * (u1 / (F::one() - u1)).ln(); + w = self.a * v.exp(); + let z = u1*u1 * u2; + let r = algo.gamma * v - F::from(4.).unwrap().ln(); + let s = self.a + r - w; + // 2. + if s + F::one() + F::from(5.).unwrap().ln() + >= F::from(5.).unwrap() * z { + break; + } + // 3. + let t = z.ln(); + if s >= t { + break; + } + // 4. + if !(r + algo.alpha * (algo.alpha / (self.b + w)).ln() < t) { + break; + } + } + }, + BetaAlgorithm::BC(algo) => { + loop { + let z; + // 1. + let u1 = rng.sample(Open01); + let u2 = rng.sample(Open01); + if u1 < F::from(0.5).unwrap() { + // 2. + let y = u1 * u2; + z = u1 * y; + if F::from(0.25).unwrap() * u2 + z - y >= algo.kappa1 { + continue; + } + } else { + // 3. + z = u1 * u1 * u2; + if z <= F::from(0.25).unwrap() { + let v = algo.beta * (u1 / (F::one() - u1)).ln(); + w = self.a * v.exp(); + break; + } + // 4. + if z >= algo.kappa2 { + continue; + } + } + // 5. + let v = algo.beta * (u1 / (F::one() - u1)).ln(); + w = self.a * v.exp(); + if !(algo.alpha * ((algo.alpha / (self.b + w)).ln() + v) + - F::from(4.).unwrap().ln() < z.ln()) { + break; + }; + } + }, + }; + // 5. for BB, 6. for BC + if !self.switched_params { + if w == F::infinity() { + // Assuming `b` is finite, for large `w`: + return F::one(); + } + w / (self.b + w) + } else { + self.b / (self.b + w) + } } } @@ -636,4 +777,13 @@ mod test { fn test_beta_invalid_dof() { Beta::new(0., 0.).unwrap(); } + + #[test] + fn test_beta_small_param() { + let beta = Beta::::new(1e-3, 1e-3).unwrap(); + let mut rng = crate::test::rng(206); + for i in 0..1000 { + assert!(!beta.sample(&mut rng).is_nan(), "failed at i={}", i); + } + } } diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index dee77686008..7af645a23c4 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -51,6 +51,7 @@ where StandardNormal: Distribution, Standard: Distribution, { + #[allow(clippy::many_single_char_names)] fn sample(&self, rng: &mut R) -> F where R: Rng + ?Sized { let mu = self.mean; diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index d43def229dc..957e0424c94 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -114,31 +114,8 @@ pub use weighted_alias::WeightedAliasIndex; pub use num_traits; -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub mod weighted_alias; - -mod binomial; -mod cauchy; -mod dirichlet; -mod exponential; -mod gamma; -mod inverse_gaussian; -mod normal; -mod normal_inverse_gaussian; -mod pareto; -mod pert; -mod poisson; -mod triangular; -mod unit_ball; -mod unit_circle; -mod unit_disc; -mod unit_sphere; -mod utils; -mod weibull; -mod ziggurat_tables; - #[cfg(test)] +#[macro_use] mod test { // Notes on testing // @@ -167,4 +144,46 @@ mod test { const INC: u64 = 11634580027462260723; rand_pcg::Pcg32::new(seed, INC) } + + /// Assert that two numbers are almost equal to each other. + /// + /// On panic, this macro will print the values of the expressions with their + /// debug representations. + macro_rules! assert_almost_eq { + ($a:expr, $b:expr, $prec:expr) => { + let diff = ($a - $b).abs(); + if diff > $prec { + panic!( + "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ + (left: `{}`, right: `{}`)", + diff, $prec, $a, $b + ); + } + }; + } } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +pub mod weighted_alias; + +mod binomial; +mod cauchy; +mod dirichlet; +mod exponential; +mod gamma; +mod inverse_gaussian; +mod normal; +mod normal_inverse_gaussian; +mod pareto; +mod pert; +mod poisson; +mod triangular; +mod unit_ball; +mod unit_circle; +mod unit_disc; +mod unit_sphere; +mod utils; +mod weibull; +mod ziggurat_tables; + diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 8067c5b9a6b..1e9d7f789c1 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -122,14 +122,17 @@ where F: Float, StandardNormal: Distribution /// Error type returned from `Normal::new` and `LogNormal::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { - /// `std_dev < 0` or `nan`. - StdDevTooSmall, + /// The mean value is too small (log-normal samples must be positive) + MeanTooSmall, + /// The standard deviation or other dispersion parameter is not finite. + BadVariance, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::StdDevTooSmall => "std_dev < 0 or is NaN in normal distribution", + Error::MeanTooSmall => "mean < 0 or NaN in log-normal distribution", + Error::BadVariance => "variation parameter is non-finite in (log)normal distribution", }) } } @@ -140,23 +143,58 @@ impl std::error::Error for Error {} impl Normal where F: Float, StandardNormal: Distribution { - /// Construct a new `Normal` distribution with the given mean and - /// standard deviation. + /// Construct, from mean and standard deviation + /// + /// Parameters: + /// + /// - mean (`μ`, unrestricted) + /// - standard deviation (`σ`, must be finite) #[inline] pub fn new(mean: F, std_dev: F) -> Result, Error> { - if !(std_dev >= F::zero()) { - return Err(Error::StdDevTooSmall); + if !std_dev.is_finite() { + return Err(Error::BadVariance); } Ok(Normal { mean, std_dev }) } + + /// Construct, from mean and coefficient of variation + /// + /// Parameters: + /// + /// - mean (`μ`, unrestricted) + /// - coefficient of variation (`cv = abs(σ / μ)`) + #[inline] + pub fn from_mean_cv(mean: F, cv: F) -> Result, Error> { + if !cv.is_finite() || cv < F::zero() { + return Err(Error::BadVariance); + } + let std_dev = cv * mean; + Ok(Normal { mean, std_dev }) + } + + /// Sample from a z-score + /// + /// This may be useful for generating correlated samples `x1` and `x2` + /// from two different distributions, as follows. + /// ``` + /// # use rand::prelude::*; + /// # use rand_distr::{Normal, StandardNormal}; + /// let mut rng = thread_rng(); + /// let z = StandardNormal.sample(&mut rng); + /// let x1 = Normal::new(0.0, 1.0).unwrap().from_zscore(z); + /// let x2 = Normal::new(2.0, -3.0).unwrap().from_zscore(z); + /// ``` + #[inline] + pub fn from_zscore(&self, zscore: F) -> F { + self.mean + self.std_dev * zscore + } } impl Distribution for Normal where F: Float, StandardNormal: Distribution { fn sample(&self, rng: &mut R) -> F { - let n: F = rng.sample(StandardNormal); - self.mean + self.std_dev * n + self.from_zscore(rng.sample(StandardNormal)) } } @@ -186,22 +224,78 @@ where F: Float, StandardNormal: Distribution impl LogNormal where F: Float, StandardNormal: Distribution { - /// Construct a new `LogNormal` distribution with the given mean - /// and standard deviation of the logarithm of the distribution. + /// Construct, from (log-space) mean and standard deviation + /// + /// Parameters are the "standard" log-space measures (these are the mean + /// and standard deviation of the logarithm of samples): + /// + /// - `mu` (`μ`, unrestricted) is the mean of the underlying distribution + /// - `sigma` (`σ`, must be finite) is the standard deviation of the + /// underlying Normal distribution + #[inline] + pub fn new(mu: F, sigma: F) -> Result, Error> { + let norm = Normal::new(mu, sigma)?; + Ok(LogNormal { norm }) + } + + /// Construct, from (linear-space) mean and coefficient of variation + /// + /// Parameters are linear-space measures: + /// + /// - mean (`μ > 0`) is the (real) mean of the distribution + /// - coefficient of variation (`cv = σ / μ`, requiring `cv ≥ 0`) is a + /// standardized measure of dispersion + /// + /// As a special exception, `μ = 0, cv = 0` is allowed (samples are `-inf`). #[inline] - pub fn new(mean: F, std_dev: F) -> Result, Error> { - if !(std_dev >= F::zero()) { - return Err(Error::StdDevTooSmall); + pub fn from_mean_cv(mean: F, cv: F) -> Result, Error> { + if cv == F::zero() { + let mu = mean.ln(); + let norm = Normal::new(mu, F::zero()).unwrap(); + return Ok(LogNormal { norm }); } - Ok(LogNormal { - norm: Normal::new(mean, std_dev).unwrap(), - }) + if !(mean > F::zero()) { + return Err(Error::MeanTooSmall); + } + if !(cv >= F::zero()) { + return Err(Error::BadVariance); + } + + // Using X ~ lognormal(μ, σ), CV² = Var(X) / E(X)² + // E(X) = exp(μ + σ² / 2) = exp(μ) × exp(σ² / 2) + // Var(X) = exp(2μ + σ²)(exp(σ²) - 1) = E(X)² × (exp(σ²) - 1) + // but Var(X) = (CV × E(X))² so CV² = exp(σ²) - 1 + // thus σ² = log(CV² + 1) + // and exp(μ) = E(X) / exp(σ² / 2) = E(X) / sqrt(CV² + 1) + let a = F::one() + cv * cv; // e + let mu = F::from(0.5).unwrap() * (mean * mean / a).ln(); + let sigma = a.ln().sqrt(); + let norm = Normal::new(mu, sigma)?; + Ok(LogNormal { norm }) + } + + /// Sample from a z-score + /// + /// This may be useful for generating correlated samples `x1` and `x2` + /// from two different distributions, as follows. + /// ``` + /// # use rand::prelude::*; + /// # use rand_distr::{LogNormal, StandardNormal}; + /// let mut rng = thread_rng(); + /// let z = StandardNormal.sample(&mut rng); + /// let x1 = LogNormal::from_mean_cv(3.0, 1.0).unwrap().from_zscore(z); + /// let x2 = LogNormal::from_mean_cv(2.0, 4.0).unwrap().from_zscore(z); + /// ``` + #[inline] + pub fn from_zscore(&self, zscore: F) -> F { + self.norm.from_zscore(zscore).exp() } } impl Distribution for LogNormal where F: Float, StandardNormal: Distribution { + #[inline] fn sample(&self, rng: &mut R) -> F { self.norm.sample(rng).exp() } @@ -220,12 +314,15 @@ mod tests { } } #[test] - #[should_panic] + fn test_normal_cv() { + let norm = Normal::from_mean_cv(1024.0, 1.0 / 256.0).unwrap(); + assert_eq!((norm.mean, norm.std_dev), (1024.0, 4.0)); + } + #[test] fn test_normal_invalid_sd() { - Normal::new(10.0, -1.0).unwrap(); + assert!(Normal::from_mean_cv(10.0, -1.0).is_err()); } - #[test] fn test_log_normal() { let lnorm = LogNormal::new(10.0, 10.0).unwrap(); @@ -235,8 +332,25 @@ mod tests { } } #[test] - #[should_panic] + fn test_log_normal_cv() { + let lnorm = LogNormal::from_mean_cv(0.0, 0.0).unwrap(); + assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (-std::f64::INFINITY, 0.0)); + + let lnorm = LogNormal::from_mean_cv(1.0, 0.0).unwrap(); + assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (0.0, 0.0)); + + let e = std::f64::consts::E; + let lnorm = LogNormal::from_mean_cv(e.sqrt(), (e - 1.0).sqrt()).unwrap(); + assert_almost_eq!(lnorm.norm.mean, 0.0, 2e-16); + assert_almost_eq!(lnorm.norm.std_dev, 1.0, 2e-16); + + let lnorm = LogNormal::from_mean_cv(e.powf(1.5), (e - 1.0).sqrt()).unwrap(); + assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (1.0, 1.0)); + } + #[test] fn test_log_normal_invalid_sd() { - LogNormal::new(10.0, -1.0).unwrap(); + assert!(LogNormal::from_mean_cv(-1.0, 1.0).is_err()); + assert!(LogNormal::from_mean_cv(0.0, 1.0).is_err()); + assert!(LogNormal::from_mean_cv(1.0, -1.0).is_err()); } } diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index fee9d8ce78f..29e5c9a5939 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -56,23 +56,6 @@ mod tests { use super::UnitCircle; use crate::Distribution; - /// Assert that two numbers are almost equal to each other. - /// - /// On panic, this macro will print the values of the expressions with their - /// debug representations. - macro_rules! assert_almost_eq { - ($a:expr, $b:expr, $prec:expr) => { - let diff = ($a - $b).abs(); - if diff > $prec { - panic!( - "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ - (left: `{}`, right: `{}`)", - diff, $prec, $a, $b - ); - } - }; - } - #[test] fn norm() { let mut rng = crate::test::rng(1); diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index a5ec0e009ac..b167a5d5d63 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -51,23 +51,6 @@ mod tests { use super::UnitSphere; use crate::Distribution; - /// Assert that two numbers are almost equal to each other. - /// - /// On panic, this macro will print the values of the expressions with their - /// debug representations. - macro_rules! assert_almost_eq { - ($a:expr, $b:expr, $prec:expr) => { - let diff = ($a - $b).abs(); - if diff > $prec { - panic!( - "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ - (left: `{}`, right: `{}`)", - diff, $prec, $a, $b - ); - } - }; - } - #[test] fn norm() { let mut rng = crate::test::rng(1); diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index f0052280865..527aece7479 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -261,7 +261,7 @@ where Uniform: Clone Self { aliases: self.aliases.clone(), no_alias_odds: self.no_alias_odds.clone(), - uniform_index: self.uniform_index.clone(), + uniform_index: self.uniform_index, uniform_within_weight_sum: self.uniform_within_weight_sum.clone(), } } @@ -299,7 +299,7 @@ pub trait AliasableWeight: /// Sums all values in slice `values`. fn sum(values: &[Self]) -> Self { - values.iter().map(|x| *x).sum() + values.iter().copied().sum() } } @@ -324,7 +324,7 @@ macro_rules! impl_weight_for_float { /// rounding errors when there are many floating point values. fn pairwise_sum(values: &[T]) -> T { if values.len() <= 32 { - values.iter().map(|x| *x).sum() + values.iter().copied().sum() } else { let mid = values.len() / 2; let (a, b) = values.split_at(mid); diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index 192ba748b7f..7d7316096ca 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -121,11 +121,11 @@ fn normal_inverse_gaussian_stability() { fn pert_stability() { // mean = 4, var = 12/7 test_samples(860, Pert::new(2., 10., 3.).unwrap(), &[ - 4.631484136029422f64, - 3.307201472321789f64, - 3.29995019556348f64, - 3.66835483991721f64, - 3.514246139933899f64, + 4.908681667460367, + 4.014196196158352, + 2.6489397149197234, + 3.4569780580044727, + 4.242864311947118, ]); } @@ -200,15 +200,21 @@ fn gamma_stability() { -2.377641221169782, ]); - // Beta has same special cases as Gamma on each param + // Beta has two special cases: + // + // 1. min(alpha, beta) <= 1 + // 2. min(alpha, beta) > 1 test_samples(223, Beta::new(1.0, 0.8).unwrap(), &[ - 0.6444564f32, 0.357635, 0.4110078, 0.7347192, - ]); - test_samples(223, Beta::new(0.7, 1.2).unwrap(), &[ - 0.6433129944095513f64, - 0.5373371199711573, - 0.10313293199269491, - 0.002472280249144378, + 0.8300703726659456, + 0.8134131062097899, + 0.47912589330631555, + 0.25323238071138526, + ]); + test_samples(223, Beta::new(3.0, 1.2).unwrap(), &[ + 0.49563509121756827, + 0.9551305482256759, + 0.5151181353461637, + 0.7551732971235077, ]); } diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 2ffa0b878f5..c25bc439a4c 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -35,7 +35,6 @@ )] #![deny(missing_docs)] #![deny(missing_debug_implementations)] -#![allow(clippy::unreadable_literal)] #![no_std] #[cfg(not(target_os = "emscripten"))] mod pcg128; diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index f2db1f1c623..8a2ce4cf1e6 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -10,9 +10,10 @@ use crate::distributions::{Distribution, Standard}; use crate::Rng; -#[cfg(all(target_arch = "x86", feature = "nightly"))] use core::arch::x86::*; -#[cfg(all(target_arch = "x86_64", feature = "nightly"))] -use core::arch::x86_64::*; +#[cfg(all(target_arch = "x86", feature = "simd_support"))] +use core::arch::x86::{__m128i, __m256i}; +#[cfg(all(target_arch = "x86_64", feature = "simd_support"))] +use core::arch::x86_64::{__m128i, __m256i}; #[cfg(not(target_os = "emscripten"))] use core::num::NonZeroU128; use core::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; #[cfg(feature = "simd_support")] use packed_simd::*; @@ -155,10 +156,9 @@ simd_impl!(256, u8x32, i8x32, u16x16, i16x16, u32x8, i32x8, u64x4, i64x4,); simd_impl!(512, u8x64, i8x64, u16x32, i16x32, u32x16, i32x16, u64x8, i64x8,); #[cfg(all( feature = "simd_support", - feature = "nightly", any(target_arch = "x86", target_arch = "x86_64") ))] -simd_impl!((__m64, u8x8), (__m128i, u8x16), (__m256i, u8x32),); +simd_impl!((__m128i, u8x16), (__m256i, u8x32),); #[cfg(test)] mod tests { diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 2bd47952056..652f52a1831 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -162,17 +162,21 @@ pub trait Distribution { /// use rand::thread_rng; /// use rand::distributions::{Distribution, Alphanumeric, Uniform, Standard}; /// - /// let rng = thread_rng(); + /// let mut rng = thread_rng(); /// /// // Vec of 16 x f32: - /// let v: Vec = Standard.sample_iter(rng).take(16).collect(); + /// let v: Vec = Standard.sample_iter(&mut rng).take(16).collect(); /// /// // String: - /// let s: String = Alphanumeric.sample_iter(rng).take(7).map(char::from).collect(); + /// let s: String = Alphanumeric + /// .sample_iter(&mut rng) + /// .take(7) + /// .map(char::from) + /// .collect(); /// /// // Dice-rolling: /// let die_range = Uniform::new_inclusive(1, 6); - /// let mut roll_die = die_range.sample_iter(rng); + /// let mut roll_die = die_range.sample_iter(&mut rng); /// while roll_die.next().unwrap() != 6 { /// println!("Not a 6; rolling again!"); /// } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 480b859644c..819a0bccd99 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -561,7 +561,7 @@ uniform_int_impl! { usize, usize, usize } #[cfg(not(target_os = "emscripten"))] uniform_int_impl! { u128, u128, u128 } -#[cfg(all(feature = "simd_support", feature = "nightly"))] +#[cfg(feature = "simd_support")] macro_rules! uniform_simd_int_impl { ($ty:ident, $unsigned:ident, $u_scalar:ident) => { // The "pick the largest zone that can fit in an `u32`" optimization @@ -668,7 +668,7 @@ macro_rules! uniform_simd_int_impl { }; } -#[cfg(all(feature = "simd_support", feature = "nightly"))] +#[cfg(feature = "simd_support")] uniform_simd_int_impl! { (u64x2, i64x2), (u64x4, i64x4), @@ -676,7 +676,7 @@ uniform_simd_int_impl! { u64 } -#[cfg(all(feature = "simd_support", feature = "nightly"))] +#[cfg(feature = "simd_support")] uniform_simd_int_impl! { (u32x2, i32x2), (u32x4, i32x4), @@ -685,7 +685,7 @@ uniform_simd_int_impl! { u32 } -#[cfg(all(feature = "simd_support", feature = "nightly"))] +#[cfg(feature = "simd_support")] uniform_simd_int_impl! { (u16x2, i16x2), (u16x4, i16x4), @@ -695,7 +695,7 @@ uniform_simd_int_impl! { u16 } -#[cfg(all(feature = "simd_support", feature = "nightly"))] +#[cfg(feature = "simd_support")] uniform_simd_int_impl! { (u8x2, i8x2), (u8x4, i8x4), @@ -1194,7 +1194,7 @@ mod tests { #[cfg(not(target_os = "emscripten"))] t!(i128, u128); - #[cfg(all(feature = "simd_support", feature = "nightly"))] + #[cfg(feature = "simd_support")] { t!(u8x2, u8x4, u8x8, u8x16, u8x32, u8x64 => u8); t!(i8x2, i8x4, i8x8, i8x16, i8x32, i8x64 => i8); diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index edcc6e359e1..e3bceb8a96c 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -143,7 +143,7 @@ wmul_impl_usize! { u32 } #[cfg(target_pointer_width = "64")] wmul_impl_usize! { u64 } -#[cfg(all(feature = "simd_support", feature = "nightly"))] +#[cfg(feature = "simd_support")] mod simd_wmul { use super::*; #[cfg(target_arch = "x86")] use core::arch::x86::*; @@ -159,9 +159,8 @@ mod simd_wmul { } wmul_impl! { (u16x2, u32x2),, 16 } - #[cfg(not(target_feature = "sse2"))] wmul_impl! { (u16x4, u32x4),, 16 } - #[cfg(not(target_feature = "sse4.2"))] + #[cfg(not(target_feature = "sse2"))] wmul_impl! { (u16x8, u32x8),, 16 } #[cfg(not(target_feature = "avx2"))] wmul_impl! { (u16x16, u32x16),, 16 } @@ -187,8 +186,6 @@ mod simd_wmul { } #[cfg(target_feature = "sse2")] - wmul_impl_16! { u16x4, __m64, _mm_mulhi_pu16, _mm_mullo_pi16 } - #[cfg(target_feature = "sse4.2")] wmul_impl_16! { u16x8, __m128i, _mm_mulhi_epu16, _mm_mullo_epi16 } #[cfg(target_feature = "avx2")] wmul_impl_16! { u16x16, __m256i, _mm256_mulhi_epu16, _mm256_mullo_epi16 } diff --git a/src/lib.rs b/src/lib.rs index d6b13acdd2d..8bf7a9df126 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,13 +49,12 @@ #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] -#![cfg_attr(all(feature = "simd_support", feature = "nightly"), feature(stdsimd))] +#![cfg_attr(feature = "simd_support", feature(stdsimd))] #![cfg_attr(feature = "nightly", feature(slice_partition_at_index))] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( - clippy::excessive_precision, - clippy::unreadable_literal, - clippy::float_cmp + clippy::float_cmp, + clippy::neg_cmp_op_on_partial_ord, )] #[cfg(feature = "std")] extern crate std; diff --git a/src/rng.rs b/src/rng.rs index 5261b1c14aa..8162c6f196e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -165,21 +165,24 @@ pub trait Rng: RngCore { /// use rand::{thread_rng, Rng}; /// use rand::distributions::{Alphanumeric, Uniform, Standard}; /// - /// let rng = thread_rng(); + /// let mut rng = thread_rng(); /// /// // Vec of 16 x f32: - /// let v: Vec = rng.sample_iter(Standard).take(16).collect(); + /// let v: Vec = (&mut rng).sample_iter(Standard).take(16).collect(); /// /// // String: - /// let s: String = rng.sample_iter(Alphanumeric).take(7).map(char::from).collect(); + /// let s: String = (&mut rng).sample_iter(Alphanumeric) + /// .take(7) + /// .map(char::from) + /// .collect(); /// /// // Combined values - /// println!("{:?}", rng.sample_iter(Standard).take(5) + /// println!("{:?}", (&mut rng).sample_iter(Standard).take(5) /// .collect::>()); /// /// // Dice-rolling: /// let die_range = Uniform::new_inclusive(1, 6); - /// let mut roll_die = rng.sample_iter(die_range); + /// let mut roll_die = (&mut rng).sample_iter(die_range); /// while roll_die.next().unwrap() != 6 { /// println!("Not a 6; rolling again!"); /// } @@ -260,7 +263,7 @@ pub trait Rng: RngCore { /// /// If `p < 0` or `p > 1`. /// - /// [`Bernoulli`]: distributions::bernoulli::Bernoulli + /// [`Bernoulli`]: distributions::Bernoulli #[inline] fn gen_bool(&mut self, p: f64) -> bool { let d = distributions::Bernoulli::new(p).unwrap(); @@ -289,7 +292,7 @@ pub trait Rng: RngCore { /// println!("{}", rng.gen_ratio(2, 3)); /// ``` /// - /// [`Bernoulli`]: distributions::bernoulli::Bernoulli + /// [`Bernoulli`]: distributions::Bernoulli #[inline] fn gen_ratio(&mut self, numerator: u32, denominator: u32) -> bool { let d = distributions::Bernoulli::from_ratio(numerator, denominator).unwrap(); diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index f866409579b..ac3c2c595da 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -58,7 +58,7 @@ //! is local, it is typically much faster than [`OsRng`]. It should be //! secure, though the paranoid may prefer [`OsRng`]. //! - [`StdRng`] is a CSPRNG chosen for good performance and trust of security -//! (based on reviews, maturity and usage). The current algorithm is ChaCha20, +//! (based on reviews, maturity and usage). The current algorithm is ChaCha12, //! which is well established and rigorously analysed. //! [`StdRng`] provides the algorithm used by [`ThreadRng`] but without //! periodic reseeding. @@ -101,7 +101,13 @@ pub mod mock; // Public so we don't export `StepRng` directly, making it a bit // more clear it is intended for testing. + +#[cfg(all(feature = "small_rng", target_pointer_width = "64"))] +mod xoshiro256plusplus; +#[cfg(all(feature = "small_rng", not(target_pointer_width = "64")))] +mod xoshiro128plusplus; #[cfg(feature = "small_rng")] mod small; + #[cfg(feature = "std_rng")] mod std; #[cfg(all(feature = "std", feature = "std_rng"))] pub(crate) mod thread; diff --git a/src/rngs/small.rs b/src/rngs/small.rs index e0b56315935..fb0e0d119b6 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -10,30 +10,36 @@ use rand_core::{Error, RngCore, SeedableRng}; -#[cfg(all(not(target_os = "emscripten"), target_pointer_width = "64"))] -type Rng = rand_pcg::Pcg64Mcg; -#[cfg(not(all(not(target_os = "emscripten"), target_pointer_width = "64")))] -type Rng = rand_pcg::Pcg32; +#[cfg(target_pointer_width = "64")] +type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus; +#[cfg(not(target_pointer_width = "64"))] +type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; /// A small-state, fast non-crypto PRNG /// /// `SmallRng` may be a good choice when a PRNG with small state, cheap /// initialization, good statistical quality and good performance are required. -/// It is **not** a good choice when security against prediction or -/// reproducibility are important. +/// Note that depending on the application, [`StdRng`] may be faster on many +/// modern platforms while providing higher-quality randomness. Furthermore, +/// `SmallRng` is **not** a good choice when: +/// - Security against prediction is important. Use [`StdRng`] instead. +/// - Seeds with many zeros are provided. In such cases, it takes `SmallRng` +/// about 10 samples to produce 0 and 1 bits with equal probability. Either +/// provide seeds with an approximately equal number of 0 and 1 (for example +/// by using [`SeedableRng::from_entropy`] or [`SeedableRng::seed_from_u64`]), +/// or use [`StdRng`] instead. /// /// The algorithm is deterministic but should not be considered reproducible /// due to dependence on platform and possible replacement in future /// library versions. For a reproducible generator, use a named PRNG from an -/// external crate, e.g. [rand_pcg] or [rand_chacha]. +/// external crate, e.g. [rand_xoshiro] or [rand_chacha]. /// Refer also to [The Book](https://rust-random.github.io/book/guide-rngs.html). /// -/// The PRNG algorithm in `SmallRng` is chosen to be -/// efficient on the current platform, without consideration for cryptography -/// or security. The size of its state is much smaller than [`StdRng`]. -/// The current algorithm is [`Pcg64Mcg`](rand_pcg::Pcg64Mcg) on 64-bit -/// platforms and [`Pcg32`](rand_pcg::Pcg32) on 32-bit platforms. Both are -/// implemented by the [rand_pcg] crate. +/// The PRNG algorithm in `SmallRng` is chosen to be efficient on the current +/// platform, without consideration for cryptography or security. The size of +/// its state is much smaller than [`StdRng`]. The current algorithm is +/// `Xoshiro256PlusPlus` on 64-bit platforms and `Xoshiro128PlusPlus` on 32-bit +/// platforms. Both are also implemented by the [rand_xoshiro] crate. /// /// # Examples /// @@ -69,7 +75,7 @@ type Rng = rand_pcg::Pcg32; /// [`StdRng`]: crate::rngs::StdRng /// [`thread_rng`]: crate::thread_rng /// [rand_chacha]: https://crates.io/crates/rand_chacha -/// [rand_pcg]: https://crates.io/crates/rand_pcg +/// [rand_xoshiro]: https://crates.io/crates/rand_xoshiro #[cfg_attr(doc_cfg, doc(cfg(feature = "small_rng")))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct SmallRng(Rng); diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index f7b0cf23953..480868c314b 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -9,7 +9,7 @@ //! Thread-local random number generator use core::cell::UnsafeCell; -use core::ptr::NonNull; +use std::rc::Rc; use std::thread_local; use super::std::Core; @@ -37,17 +37,19 @@ use crate::{CryptoRng, Error, RngCore, SeedableRng}; // of 32 kB and less. We choose 64 kB to avoid significant overhead. const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; -/// The type returned by [`thread_rng`], essentially just a reference to the -/// PRNG in thread-local memory. +/// A reference to the thread-local generator /// -/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance. -/// As hinted by the name, the generator is thread-local. `ThreadRng` is a -/// handle to this generator and thus supports `Copy`, but not `Send` or `Sync`. +/// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`. +/// This handle is safe to use everywhere (including thread-local destructors) +/// but cannot be passed between threads (is not `Send` or `Sync`). /// -/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed -/// the PRNG from fresh entropy every 64 kiB of random data. -/// [`OsRng`] is used to provide seed data. +/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance +/// and is automatically seeded from [`OsRng`]. /// +/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed +/// the PRNG from fresh entropy every 64 kiB of random data as well as after a +/// fork on Unix (though not quite immediately; see documentation of +/// [`ReseedingRng`]). /// Note that the reseeding is done as an extra precaution against side-channel /// attacks and mis-use (e.g. if somehow weak entropy were supplied initially). /// The PRNG algorithms used are assumed to be secure. @@ -55,20 +57,22 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct ThreadRng { - // inner raw pointer implies type is neither Send nor Sync - rng: NonNull>, + // Rc is explictly !Send and !Sync + rng: Rc>>, } thread_local!( - static THREAD_RNG_KEY: UnsafeCell> = { + // We require Rc<..> to avoid premature freeing when thread_rng is used + // within thread-local destructors. See #968. + static THREAD_RNG_KEY: Rc>> = { let r = Core::from_rng(OsRng).unwrap_or_else(|err| panic!("could not initialize thread_rng: {}", err)); let rng = ReseedingRng::new(r, THREAD_RNG_RESEED_THRESHOLD, OsRng); - UnsafeCell::new(rng) + Rc::new(UnsafeCell::new(rng)) } ); @@ -81,9 +85,8 @@ thread_local!( /// For more information see [`ThreadRng`]. #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] pub fn thread_rng() -> ThreadRng { - let raw = THREAD_RNG_KEY.with(|t| t.get()); - let nn = NonNull::new(raw).unwrap(); - ThreadRng { rng: nn } + let rng = THREAD_RNG_KEY.with(|t| t.clone()); + ThreadRng { rng } } impl Default for ThreadRng { @@ -100,20 +103,32 @@ impl RngCore for ThreadRng { #[inline(always)] fn next_u32(&mut self) -> u32 { - unsafe { self.rng.as_mut().next_u32() } + // SAFETY: We must make sure to stop using `rng` before anyone else + // creates another mutable reference + let rng = unsafe { &mut *self.rng.get() }; + rng.next_u32() } #[inline(always)] fn next_u64(&mut self) -> u64 { - unsafe { self.rng.as_mut().next_u64() } + // SAFETY: We must make sure to stop using `rng` before anyone else + // creates another mutable reference + let rng = unsafe { &mut *self.rng.get() }; + rng.next_u64() } fn fill_bytes(&mut self, dest: &mut [u8]) { - unsafe { self.rng.as_mut().fill_bytes(dest) } + // SAFETY: We must make sure to stop using `rng` before anyone else + // creates another mutable reference + let rng = unsafe { &mut *self.rng.get() }; + rng.fill_bytes(dest) } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - unsafe { self.rng.as_mut().try_fill_bytes(dest) } + // SAFETY: We must make sure to stop using `rng` before anyone else + // creates another mutable reference + let rng = unsafe { &mut *self.rng.get() }; + rng.try_fill_bytes(dest) } } diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs new file mode 100644 index 00000000000..ece98fafd6a --- /dev/null +++ b/src/rngs/xoshiro128plusplus.rs @@ -0,0 +1,118 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[cfg(feature="serde1")] use serde::{Serialize, Deserialize}; +use rand_core::impls::{next_u64_via_u32, fill_bytes_via_next}; +use rand_core::le::read_u32_into; +use rand_core::{SeedableRng, RngCore, Error}; + +/// A xoshiro128++ random number generator. +/// +/// The xoshiro128++ algorithm is not suitable for cryptographic purposes, but +/// is very fast and has excellent statistical properties. +/// +/// The algorithm used here is translated from [the `xoshiro128plusplus.c` +/// reference source code](http://xoshiro.di.unimi.it/xoshiro128plusplus.c) by +/// David Blackman and Sebastiano Vigna. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))] +pub struct Xoshiro128PlusPlus { + s: [u32; 4], +} + +impl SeedableRng for Xoshiro128PlusPlus { + type Seed = [u8; 16]; + + /// Create a new `Xoshiro128PlusPlus`. If `seed` is entirely 0, it will be + /// mapped to a different seed. + #[inline] + fn from_seed(seed: [u8; 16]) -> Xoshiro128PlusPlus { + if seed.iter().all(|&x| x == 0) { + return Self::seed_from_u64(0); + } + let mut state = [0; 4]; + read_u32_into(&seed, &mut state); + Xoshiro128PlusPlus { s: state } + } + + /// Create a new `Xoshiro128PlusPlus` from a `u64` seed. + /// + /// This uses the SplitMix64 generator internally. + fn seed_from_u64(mut state: u64) -> Self { + const PHI: u64 = 0x9e3779b97f4a7c15; + let mut seed = Self::Seed::default(); + for chunk in seed.as_mut().chunks_mut(8) { + state = state.wrapping_add(PHI); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z = z ^ (z >> 31); + chunk.copy_from_slice(&z.to_le_bytes()); + } + Self::from_seed(seed) + } +} + +impl RngCore for Xoshiro128PlusPlus { + #[inline] + fn next_u32(&mut self) -> u32 { + let result_starstar = self.s[0] + .wrapping_add(self.s[3]) + .rotate_left(7) + .wrapping_add(self.s[0]); + + let t = self.s[1] << 9; + + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; + + self.s[2] ^= t; + + self.s[3] = self.s[3].rotate_left(11); + + result_starstar + } + + #[inline] + fn next_u64(&mut self) -> u64 { + next_u64_via_u32(self) + } + + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + fill_bytes_via_next(self, dest); + } + + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reference() { + let mut rng = Xoshiro128PlusPlus::from_seed( + [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]); + // These values were produced with the reference implementation: + // http://xoshiro.di.unimi.it/xoshiro128plusplus.c + let expected = [ + 641, 1573767, 3222811527, 3517856514, 836907274, 4247214768, + 3867114732, 1355841295, 495546011, 621204420, + ]; + for &e in &expected { + assert_eq!(rng.next_u32(), e); + } + } +} diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs new file mode 100644 index 00000000000..cd373c30669 --- /dev/null +++ b/src/rngs/xoshiro256plusplus.rs @@ -0,0 +1,122 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[cfg(feature="serde1")] use serde::{Serialize, Deserialize}; +use rand_core::impls::fill_bytes_via_next; +use rand_core::le::read_u64_into; +use rand_core::{SeedableRng, RngCore, Error}; + +/// A xoshiro256** random number generator. +/// +/// The xoshiro256** algorithm is not suitable for cryptographic purposes, but +/// is very fast and has excellent statistical properties. +/// +/// The algorithm used here is translated from [the `xoshiro256plusplus.c` +/// reference source code](http://xoshiro.di.unimi.it/xoshiro256plusplus.c) by +/// David Blackman and Sebastiano Vigna. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))] +pub struct Xoshiro256PlusPlus { + s: [u64; 4], +} + +impl SeedableRng for Xoshiro256PlusPlus { + type Seed = [u8; 32]; + + /// Create a new `Xoshiro256PlusPlus`. If `seed` is entirely 0, it will be + /// mapped to a different seed. + #[inline] + fn from_seed(seed: [u8; 32]) -> Xoshiro256PlusPlus { + if seed.iter().all(|&x| x == 0) { + return Self::seed_from_u64(0); + } + let mut state = [0; 4]; + read_u64_into(&seed, &mut state); + Xoshiro256PlusPlus { s: state } + } + + /// Create a new `Xoshiro256PlusPlus` from a `u64` seed. + /// + /// This uses the SplitMix64 generator internally. + fn seed_from_u64(mut state: u64) -> Self { + const PHI: u64 = 0x9e3779b97f4a7c15; + let mut seed = Self::Seed::default(); + for chunk in seed.as_mut().chunks_mut(8) { + state = state.wrapping_add(PHI); + let mut z = state; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + z = z ^ (z >> 31); + chunk.copy_from_slice(&z.to_le_bytes()); + } + Self::from_seed(seed) + } +} + +impl RngCore for Xoshiro256PlusPlus { + #[inline] + fn next_u32(&mut self) -> u32 { + // The lowest bits have some linear dependencies, so we use the + // upper bits instead. + (self.next_u64() >> 32) as u32 + } + + #[inline] + fn next_u64(&mut self) -> u64 { + let result_plusplus = self.s[0] + .wrapping_add(self.s[3]) + .rotate_left(23) + .wrapping_add(self.s[0]); + + let t = self.s[1] << 17; + + self.s[2] ^= self.s[0]; + self.s[3] ^= self.s[1]; + self.s[1] ^= self.s[2]; + self.s[0] ^= self.s[3]; + + self.s[2] ^= t; + + self.s[3] = self.s[3].rotate_left(45); + + result_plusplus + } + + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + fill_bytes_via_next(self, dest); + } + + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reference() { + let mut rng = Xoshiro256PlusPlus::from_seed( + [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]); + // These values were produced with the reference implementation: + // http://xoshiro.di.unimi.it/xoshiro256plusplus.c + let expected = [ + 41943041, 58720359, 3588806011781223, 3591011842654386, + 9228616714210784205, 9973669472204895162, 14011001112246962877, + 12406186145184390807, 15849039046786891736, 10450023813501588000, + ]; + for &e in &expected { + assert_eq!(rng.next_u64(), e); + } + } +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index dd542228689..f8d21c9a444 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -265,7 +265,8 @@ pub trait SliceRandom { /// Extension trait on iterators, providing random sampling methods. /// -/// This trait is implemented on all sized iterators, providing methods for +/// This trait is implemented on all iterators `I` where `I: Iterator + Sized` +/// and provides methods for /// choosing one or more elements. You must `use` this trait: /// /// ``` @@ -291,14 +292,20 @@ pub trait IteratorRandom: Iterator + Sized { /// available, complexity is `O(n)` where `n` is the iterator length. /// Partial hints (where `lower > 0`) also improve performance. /// - /// For slices, prefer [`SliceRandom::choose`] which guarantees `O(1)` - /// performance. + /// Note that the output values and the number of RNG samples used + /// depends on size hints. In particular, `Iterator` combinators that don't + /// change the values yielded but change the size hints may result in + /// `choose` returning different elements. If you want consistent results + /// and RNG usage consider using [`choose_stable`]. fn choose(mut self, rng: &mut R) -> Option where R: Rng + ?Sized { let (mut lower, mut upper) = self.size_hint(); let mut consumed = 0; let mut result = None; + // Handling for this condition outside the loop allows the optimizer to eliminate the loop + // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. + // seq_iter_choose_from_1000. if upper == Some(lower) { return if lower == 0 { None @@ -330,8 +337,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } consumed += 1; - let denom = consumed as f64; // accurate to 2^53 elements - if rng.gen_bool(1.0 / denom) { + if gen_index(rng, consumed) == 0 { result = elem; } } @@ -342,6 +348,62 @@ pub trait IteratorRandom: Iterator + Sized { } } + /// Choose one element at random from the iterator. + /// + /// Returns `None` if and only if the iterator is empty. + /// + /// This method is very similar to [`choose`] except that the result + /// only depends on the length of the iterator and the values produced by + /// `rng`. Notably for any iterator of a given length this will make the + /// same requests to `rng` and if the same sequence of values are produced + /// the same index will be selected from `self`. This may be useful if you + /// need consistent results no matter what type of iterator you are working + /// with. If you do not need this stability prefer [`choose`]. + /// + /// Note that this method still uses [`Iterator::size_hint`] to skip + /// constructing elements where possible, however the selection and `rng` + /// calls are the same in the face of this optimization. If you want to + /// force every element to be created regardless call `.inspect(|e| ())`. + fn choose_stable(mut self, rng: &mut R) -> Option + where R: Rng + ?Sized { + let mut consumed = 0; + let mut result = None; + + loop { + // Currently the only way to skip elements is `nth()`. So we need to + // store what index to access next here. + // This should be replaced by `advance_by()` once it is stable: + // https://github.com/rust-lang/rust/issues/77404 + let mut next = 0; + + let (lower, _) = self.size_hint(); + if lower >= 2 { + let highest_selected = (0..lower) + .filter(|ix| gen_index(rng, consumed+ix+1) == 0) + .last(); + + consumed += lower; + next = lower; + + if let Some(ix) = highest_selected { + result = self.nth(ix); + next -= ix + 1; + debug_assert!(result.is_some(), "iterator shorter than size_hint().0"); + } + } + + let elem = self.nth(next); + if elem.is_none() { + return result + } + + if gen_index(rng, consumed+1) == 0 { + result = elem; + } + consumed += 1; + } + } + /// Collects values at random from the iterator into a supplied buffer /// until that buffer is filled. /// @@ -789,6 +851,103 @@ mod test { assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); } + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_stable() { + let r = &mut crate::test::rng(109); + fn test_iter + Clone>(r: &mut R, iter: Iter) { + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_stable(r).unwrap(); + chosen[picked] += 1; + } + for count in chosen.iter() { + // Samples should follow Binomial(1000, 1/9) + // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x + // Note: have seen 153, which is unlikely but not impossible. + assert!( + 72 < *count && *count < 154, + "count not close to 1000/9: {}", + count + ); + } + } + + test_iter(r, 0..9); + test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); + #[cfg(feature = "alloc")] + test_iter(r, (0..9).collect::>().into_iter()); + test_iter(r, UnhintedIterator { iter: 0..9 }); + test_iter(r, ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }); + test_iter(r, ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }); + test_iter(r, WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }); + test_iter(r, WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }); + + assert_eq!((0..0).choose(r), None); + assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_stable_stability() { + fn test_iter(iter: impl Iterator + Clone) -> [i32; 9] { + let r = &mut crate::test::rng(109); + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_stable(r).unwrap(); + chosen[picked] += 1; + } + chosen + } + + let reference = test_iter(0..9); + assert_eq!(test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference); + + #[cfg(feature = "alloc")] + assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); + assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); + assert_eq!(test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }), reference); + assert_eq!(test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }), reference); + assert_eq!(test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }), reference); + assert_eq!(test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }), reference); + } + #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_shuffle() { @@ -957,7 +1116,7 @@ mod test { assert_eq!(choose([].iter().cloned()), None); assert_eq!(choose(0..100), Some(33)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(76)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -994,6 +1153,52 @@ mod test { ); } + #[test] + fn value_stability_choose_stable() { + fn choose>(iter: I) -> Option { + let mut rng = crate::test::rng(411); + iter.choose_stable(&mut rng) + } + + assert_eq!(choose([].iter().cloned()), None); + assert_eq!(choose(0..100), Some(40)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); + assert_eq!( + choose(ChunkHintedIterator { + iter: 0..100, + chunk_size: 32, + chunk_remaining: 32, + hint_total_size: false, + }), + Some(40) + ); + assert_eq!( + choose(ChunkHintedIterator { + iter: 0..100, + chunk_size: 32, + chunk_remaining: 32, + hint_total_size: true, + }), + Some(40) + ); + assert_eq!( + choose(WindowHintedIterator { + iter: 0..100, + window_size: 32, + hint_total_size: false, + }), + Some(40) + ); + assert_eq!( + choose(WindowHintedIterator { + iter: 0..100, + window_size: 32, + hint_total_size: true, + }), + Some(40) + ); + } + #[test] fn value_stability_choose_multiple() { fn do_test>(iter: I, v: &[u32]) { diff --git a/utils/ci/script.sh b/utils/ci/script.sh index efefc2adc5e..caef0767ca9 100644 --- a/utils/ci/script.sh +++ b/utils/ci/script.sh @@ -29,6 +29,7 @@ main() { if [ "0$NIGHTLY" -ge 1 ]; then $CARGO test $TARGET --all-features $CARGO test $TARGET --benches --features=nightly + $CARGO test $TARGET --manifest-path rand_distr/Cargo.toml --benches else # all stable features: $CARGO test $TARGET --features=serde1,log,small_rng