From a9401e16b707c6554632d5d7b6af347e4ea748f9 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 15 Sep 2021 18:55:08 +0200 Subject: [PATCH 01/54] Mention that `Fill` supports floats --- src/distributions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index e3086680..05ca8060 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -158,7 +158,7 @@ use crate::Rng; /// * Tuples (up to 12 elements): each element is generated sequentially. /// * Arrays (up to 32 elements): each element is generated sequentially; /// see also [`Rng::fill`] which supports arbitrary array length for integer -/// types and tends to be faster for `u32` and smaller types. +/// and float types and tends to be faster for `u32` and smaller types. /// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support /// arrays larger than 32 elements. /// Note that [`Rng::fill`] and `Standard`'s array support are *not* equivalent: From 7f9aa2b43c05a536a29f8447efd0de470e268cca 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?= Date: Tue, 28 Dec 2021 16:31:42 +0300 Subject: [PATCH 02/54] rand_distr: fix no_std build --- rand_distr/src/binomial.rs | 2 ++ rand_distr/src/geometric.rs | 2 ++ rand_distr/src/hypergeometric.rs | 2 ++ rand_distr/src/utils.rs | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 8e7513a2..d1637b6d 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -13,6 +13,8 @@ use crate::{Distribution, Uniform}; use rand::Rng; use core::fmt; use core::cmp::Ordering; +#[allow(unused_imports)] +use num_traits::Float; /// The binomial distribution `Binomial(n, p)`. /// diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 7988261a..78ad6cce 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -3,6 +3,8 @@ use crate::Distribution; use rand::Rng; use core::fmt; +#[allow(unused_imports)] +use num_traits::Float; /// The geometric distribution `Geometric(p)` bounded to `[0, u64::MAX]`. /// diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 8ab2dca0..9a529096 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -4,6 +4,8 @@ use crate::Distribution; use rand::Rng; use rand::distributions::uniform::Uniform; use core::fmt; +#[allow(unused_imports)] +use num_traits::Float; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index f097bb45..4638e362 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -11,6 +11,7 @@ use crate::ziggurat_tables; use rand::distributions::hidden_export::IntoFloat; use rand::Rng; +use num_traits::Float; /// Calculates ln(gamma(x)) (natural logarithm of the gamma /// function) using the Lanczos approximation. @@ -25,7 +26,7 @@ use rand::Rng; /// `Ag(z)` is an infinite series with coefficients that can be calculated /// ahead of time - we use just the first 6 terms, which is good enough /// for most purposes. -pub(crate) fn log_gamma(x: F) -> F { +pub(crate) fn log_gamma(x: F) -> F { // precalculated 6 coefficients for the first 6 terms of the series let coefficients: [F; 6] = [ F::from(76.18009172947146).unwrap(), From e9f5cfccbf48864d47828e3267736c1336c5e00a 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?= Date: Tue, 28 Dec 2021 16:51:07 +0300 Subject: [PATCH 03/54] bump rand_distr to v0.4.3 --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 8a9638c8..32a5fcaf 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.4.2" +version = "0.4.3" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 9ef737ba5b814f6ab36cebafb59ad29885d68a05 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?= Date: Tue, 28 Dec 2021 16:52:13 +0300 Subject: [PATCH 04/54] update changelog --- rand_distr/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index b93933e9..6b0cda28 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ 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). +## [0.4.3] - 2021-12-30 +- Fix `no_std` build (#1208) + ## [0.4.2] - 2021-09-18 - New `Zeta` and `Zipf` distributions (#1136) - New `SkewNormal` distribution (#1149) From 73f8ffd16379390e624ac53cd6882dd679dd9a6f Mon Sep 17 00:00:00 2001 From: novacrazy Date: Sun, 6 Feb 2022 21:19:58 -0600 Subject: [PATCH 05/54] Remove unused `slice_partition_at_index` feature --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6f8665a2..6d847180 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,6 @@ #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] #![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::float_cmp, From d3ca11b0bcc1f42fe34ba4f90f99509b7eb4ff18 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 8 Feb 2022 09:33:33 +0000 Subject: [PATCH 06/54] Update to packed_simd_2 0.3.7 --- Cargo.toml | 2 +- src/distributions/uniform.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ef49d18..98ba373c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = fals [dependencies.packed_simd] # NOTE: so far no version works reliably due to dependence on unstable features package = "packed_simd_2" -version = "0.3.6" +version = "0.3.7" optional = true features = ["into_bits"] diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 482461f0..6b9e70f0 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1430,7 +1430,7 @@ mod tests { #[test] #[should_panic] fn test_float_overflow() { - Uniform::from(::core::f64::MIN..::core::f64::MAX); + let _ = Uniform::from(::core::f64::MIN..::core::f64::MAX); } #[test] From 9f20df04d88698c38515833d6db62d7eb50d8b80 Mon Sep 17 00:00:00 2001 From: Will Springer Date: Thu, 10 Feb 2022 19:30:49 -0800 Subject: [PATCH 07/54] Making distributions comparable by deriving PartialEq. Tests included --- rand_distr/src/binomial.rs | 7 +++- rand_distr/src/cauchy.rs | 7 +++- rand_distr/src/dirichlet.rs | 7 +++- rand_distr/src/exponential.rs | 7 +++- rand_distr/src/frechet.rs | 7 +++- rand_distr/src/gamma.rs | 49 +++++++++++++++++------ rand_distr/src/geometric.rs | 7 +++- rand_distr/src/gumbel.rs | 7 +++- rand_distr/src/hypergeometric.rs | 9 ++++- rand_distr/src/inverse_gaussian.rs | 7 +++- rand_distr/src/normal.rs | 14 ++++++- rand_distr/src/normal_inverse_gaussian.rs | 7 +++- rand_distr/src/pareto.rs | 7 +++- rand_distr/src/pert.rs | 7 +++- rand_distr/src/poisson.rs | 9 ++++- rand_distr/src/skew_normal.rs | 7 +++- rand_distr/src/triangular.rs | 7 +++- rand_distr/src/weibull.rs | 7 +++- rand_distr/src/zipf.rs | 14 ++++++- src/distributions/bernoulli.rs | 7 +++- src/distributions/uniform.rs | 14 +++++-- src/distributions/weighted_index.rs | 7 +++- 22 files changed, 182 insertions(+), 39 deletions(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index d1637b6d..6dbf7ab7 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -30,7 +30,7 @@ use num_traits::Float; /// let v = bin.sample(&mut rand::thread_rng()); /// println!("{} is from a binomial distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Binomial { /// Number of trials. @@ -347,4 +347,9 @@ mod test { fn test_binomial_invalid_lambda_neg() { Binomial::new(20, -10.0).unwrap(); } + + #[test] + fn binomial_distributions_can_be_compared() { + assert_eq!(Binomial::new(1, 1.0), Binomial::new(1, 1.0)); + } } diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 8c2ccdd3..9aff7e62 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -31,7 +31,7 @@ use core::fmt; /// let v = cau.sample(&mut rand::thread_rng()); /// println!("{} is from a Cauchy(2, 5) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Cauchy where F: Float + FloatConst, Standard: Distribution @@ -164,4 +164,9 @@ mod test { assert_almost_eq!(*a, *b, 1e-5); } } + + #[test] + fn cauchy_distributions_can_be_compared() { + assert_eq!(Cauchy::new(1.0, 2.0), Cauchy::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 0ffbc40a..786cbccd 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -32,7 +32,7 @@ use alloc::{boxed::Box, vec, vec::Vec}; /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Dirichlet where @@ -183,4 +183,9 @@ mod test { fn test_dirichlet_invalid_alpha() { Dirichlet::new_with_size(0.0f64, 2).unwrap(); } + + #[test] + fn dirichlet_distributions_can_be_compared() { + assert_eq!(Dirichlet::new(&[1.0, 2.0]), Dirichlet::new(&[1.0, 2.0])); + } } diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 4e33c3ca..e3d2a8d1 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -91,7 +91,7 @@ impl Distribution for Exp1 { /// let v = exp.sample(&mut rand::thread_rng()); /// println!("{} is from a Exp(2) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Exp where F: Float, Exp1: Distribution @@ -178,4 +178,9 @@ mod test { fn test_exp_invalid_lambda_nan() { Exp::new(f64::nan()).unwrap(); } + + #[test] + fn exponential_distributions_can_be_compared() { + assert_eq!(Exp::new(1.0), Exp::new(1.0)); + } } diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 0239fe83..63205b40 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -27,7 +27,7 @@ use rand::Rng; /// let val: f64 = thread_rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Frechet where @@ -182,4 +182,9 @@ mod tests { .zip(&probabilities) .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) } + + #[test] + fn frechet_distributions_can_be_compared() { + assert_eq!(Frechet::new(1.0, 2.0, 3.0), Frechet::new(1.0, 2.0, 3.0)); + } } diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 87faf11c..debad0c8 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -54,7 +54,7 @@ use serde::{Serialize, Deserialize}; /// Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 /// (September 2000), 363-372. /// DOI:[10.1145/358407.358414](https://doi.acm.org/10.1145/358407.358414) -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Gamma where @@ -91,7 +91,7 @@ impl fmt::Display for Error { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum GammaRepr where @@ -119,7 +119,7 @@ where /// /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct GammaSmallShape where @@ -135,7 +135,7 @@ where /// /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct GammaLargeShape where @@ -280,7 +280,7 @@ where /// let v = chi.sample(&mut rand::thread_rng()); /// println!("{} is from a χ²(11) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ChiSquared where @@ -314,7 +314,7 @@ impl fmt::Display for ChiSquaredError { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for ChiSquaredError {} -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum ChiSquaredRepr where @@ -385,7 +385,7 @@ where /// let v = f.sample(&mut rand::thread_rng()); /// println!("{} is from an F(2, 32) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct FisherF where @@ -472,7 +472,7 @@ where /// let v = t.sample(&mut rand::thread_rng()); /// println!("{} is from a t(11) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct StudentT where @@ -522,7 +522,7 @@ where /// 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)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum BetaAlgorithm { BB(BB), @@ -530,7 +530,7 @@ enum BetaAlgorithm { } /// Algorithm BB for `min(alpha, beta) > 1`. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct BB { alpha: N, @@ -539,7 +539,7 @@ struct BB { } /// Algorithm BC for `min(alpha, beta) <= 1`. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct BC { alpha: N, @@ -560,7 +560,7 @@ struct BC { /// let v = beta.sample(&mut rand::thread_rng()); /// println!("{} is from a Beta(2, 5) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Beta where @@ -811,4 +811,29 @@ mod test { assert!(!beta.sample(&mut rng).is_nan(), "failed at i={}", i); } } + + #[test] + fn gamma_distributions_can_be_compared() { + assert_eq!(Gamma::new(1.0, 2.0), Gamma::new(1.0, 2.0)); + } + + #[test] + fn beta_distributions_can_be_compared() { + assert_eq!(Beta::new(1.0, 2.0), Beta::new(1.0, 2.0)); + } + + #[test] + fn chi_squared_distributions_can_be_compared() { + assert_eq!(ChiSquared::new(1.0), ChiSquared::new(1.0)); + } + + #[test] + fn fisher_f_distributions_can_be_compared() { + assert_eq!(FisherF::new(1.0, 2.0), FisherF::new(1.0, 2.0)); + } + + #[test] + fn student_t_distributions_can_be_compared() { + assert_eq!(StudentT::new(1.0), StudentT::new(1.0)); + } } diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 78ad6cce..3ea8b8f3 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -27,7 +27,7 @@ use num_traits::Float; /// let v = geo.sample(&mut rand::thread_rng()); /// println!("{} is from a Geometric(0.25) distribution", v); /// ``` -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Geometric { @@ -235,4 +235,9 @@ mod test { results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; assert!((variance - expected_variance).abs() < expected_variance / 10.0); } + + #[test] + fn geometric_distributions_can_be_compared() { + assert_eq!(Geometric::new(1.0), Geometric::new(1.0)); + } } diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 2b201945..b254919f 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -27,7 +27,7 @@ use rand::Rng; /// let val: f64 = thread_rng().sample(Gumbel::new(0.0, 1.0).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Gumbel where @@ -152,4 +152,9 @@ mod tests { .zip(&probabilities) .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) } + + #[test] + fn gumbel_distributions_can_be_compared() { + assert_eq!(Gumbel::new(1.0, 2.0), Gumbel::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 9a529096..47614503 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -7,7 +7,7 @@ use core::fmt; #[allow(unused_imports)] use num_traits::Float; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] enum SamplingMethod { InverseTransform{ initial_p: f64, initial_x: i64 }, @@ -45,7 +45,7 @@ enum SamplingMethod { /// let v = hypergeo.sample(&mut rand::thread_rng()); /// println!("{} is from a hypergeometric distribution", v); /// ``` -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Hypergeometric { n1: u64, @@ -419,4 +419,9 @@ mod test { test_hypergeometric_mean_and_variance(10100, 10000, 1000, &mut rng); test_hypergeometric_mean_and_variance(100100, 100, 10000, &mut rng); } + + #[test] + fn hypergeometric_distributions_can_be_compared() { + assert_eq!(Hypergeometric::new(1, 2, 3), Hypergeometric::new(1, 2, 3)); + } } diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 58986a76..ba845fd1 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -26,7 +26,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} /// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct InverseGaussian where @@ -109,4 +109,9 @@ mod tests { assert!(InverseGaussian::new(1.0, -1.0).is_err()); assert!(InverseGaussian::new(1.0, 1.0).is_ok()); } + + #[test] + fn inverse_gaussian_distributions_can_be_compared() { + assert_eq!(InverseGaussian::new(1.0, 2.0), InverseGaussian::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 7078a894..b3b801df 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -112,7 +112,7 @@ impl Distribution for StandardNormal { /// ``` /// /// [`StandardNormal`]: crate::StandardNormal -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Normal where F: Float, StandardNormal: Distribution @@ -227,7 +227,7 @@ where F: Float, StandardNormal: Distribution /// let v = log_normal.sample(&mut rand::thread_rng()); /// println!("{} is from an ln N(2, 9) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct LogNormal where F: Float, StandardNormal: Distribution @@ -368,4 +368,14 @@ mod tests { assert!(LogNormal::from_mean_cv(0.0, 1.0).is_err()); assert!(LogNormal::from_mean_cv(1.0, -1.0).is_err()); } + + #[test] + fn normal_distributions_can_be_compared() { + assert_eq!(Normal::new(1.0, 2.0), Normal::new(1.0, 2.0)); + } + + #[test] + fn log_normal_distributions_can_be_compared() { + assert_eq!(LogNormal::new(1.0, 2.0), LogNormal::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index c4d693d0..e05d5b09 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -26,7 +26,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} /// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct NormalInverseGaussian where @@ -104,4 +104,9 @@ mod tests { assert!(NormalInverseGaussian::new(1.0, 2.0).is_err()); assert!(NormalInverseGaussian::new(2.0, 1.0).is_ok()); } + + #[test] + fn normal_inverse_gaussian_distributions_can_be_compared() { + assert_eq!(NormalInverseGaussian::new(1.0, 2.0), NormalInverseGaussian::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index cd61894c..25c8e053 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -23,7 +23,7 @@ use core::fmt; /// let val: f64 = thread_rng().sample(Pareto::new(1., 2.).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pareto where F: Float, OpenClosed01: Distribution @@ -131,4 +131,9 @@ mod tests { 105.8826669383772, ]); } + + #[test] + fn pareto_distributions_can_be_compared() { + assert_eq!(Pareto::new(1.0, 2.0), Pareto::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index 4ead1fb8..db89fff7 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -30,7 +30,7 @@ use core::fmt; /// ``` /// /// [`Triangular`]: crate::Triangular -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pert where @@ -146,4 +146,9 @@ mod test { assert!(Pert::new(min, max, mode).is_err()); } } + + #[test] + fn pert_distributions_can_be_compared() { + assert_eq!(Pert::new(1.0, 3.0, 2.0), Pert::new(1.0, 3.0, 2.0)); + } } diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index dc355258..8b9bffd0 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -28,7 +28,7 @@ use core::fmt; /// let v = poi.sample(&mut rand::thread_rng()); /// println!("{} is from a Poisson(2) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson where F: Float + FloatConst, Standard: Distribution @@ -178,4 +178,9 @@ mod test { fn test_poisson_invalid_lambda_neg() { Poisson::new(-10.0).unwrap(); } -} + + #[test] + fn poisson_distributions_can_be_compared() { + assert_eq!(Poisson::new(1.0), Poisson::new(1.0)); + } +} \ No newline at end of file diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 7d91d0bd..146b4ead 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -40,7 +40,7 @@ use rand::Rng; /// [skew normal distribution]: https://en.wikipedia.org/wiki/Skew_normal_distribution /// [`Normal`]: struct.Normal.html /// [A Method to Simulate the Skew Normal Distribution]: https://dx.doi.org/10.4236/am.2014.513201 -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct SkewNormal where @@ -253,4 +253,9 @@ mod tests { assert!(value.is_nan()); } } + + #[test] + fn skew_normal_distributions_can_be_compared() { + assert_eq!(SkewNormal::new(1.0, 2.0, 3.0), SkewNormal::new(1.0, 2.0, 3.0)); + } } diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index ba6d3644..eef7d190 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -31,7 +31,7 @@ use core::fmt; /// ``` /// /// [`Pert`]: crate::Pert -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Triangular where F: Float, Standard: Distribution @@ -130,4 +130,9 @@ mod test { assert!(Triangular::new(min, max, mode).is_err()); } } + + #[test] + fn triangular_distributions_can_be_compared() { + assert_eq!(Triangular::new(1.0, 3.0, 2.0), Triangular::new(1.0, 3.0, 2.0)); + } } diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index b390ad3f..fe45eff6 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -23,7 +23,7 @@ use core::fmt; /// let val: f64 = thread_rng().sample(Weibull::new(1., 10.).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Weibull where F: Float, OpenClosed01: Distribution @@ -129,4 +129,9 @@ mod tests { 7.877212340241561, ]); } + + #[test] + fn weibull_distributions_can_be_compared() { + assert_eq!(Weibull::new(1.0, 2.0), Weibull::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index ad322c4e..84d33c05 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -46,7 +46,7 @@ use core::fmt; /// /// [zeta distribution]: https://en.wikipedia.org/wiki/Zeta_distribution /// [Non-Uniform Random Variate Generation]: https://doi.org/10.1007/978-1-4613-8643-8 -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { @@ -142,7 +142,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution /// due to Jason Crease[1]. /// /// [1]: https://jasoncrease.medium.com/rejection-sampling-the-zipf-distribution-6b359792cffa -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Zipf where F: Float, Standard: Distribution { n: F, @@ -371,4 +371,14 @@ mod tests { 1.0, 2.0, 3.0, 2.0 ]); } + + #[test] + fn zipf_distributions_can_be_compared() { + assert_eq!(Zipf::new(1, 2.0), Zipf::new(1, 2.0)); + } + + #[test] + fn zeta_distributions_can_be_compared() { + assert_eq!(Zeta::new(1.0), Zeta::new(1.0)); + } } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index bf0d5e5e..226db79f 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -33,7 +33,7 @@ use serde::{Serialize, Deserialize}; /// This `Bernoulli` distribution uses 64 bits from the RNG (a `u64`), /// so only probabilities that are multiples of 2-64 can be /// represented. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. @@ -211,4 +211,9 @@ mod test { true, false, false, true, false, false, true, true, true, true ]); } + + #[test] + fn bernoulli_distributions_can_be_compared() { + assert_eq!(Bernoulli::new(1.0), Bernoulli::new(1.0)); + } } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 6b9e70f0..261357b2 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -172,7 +172,7 @@ use serde::{Serialize, Deserialize}; /// [`new`]: Uniform::new /// [`new_inclusive`]: Uniform::new_inclusive /// [`Rng::gen_range`]: Rng::gen_range -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] #[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] @@ -418,7 +418,7 @@ impl SampleRange for RangeInclusive { /// An alternative to using a modulus is widening multiply: After a widening /// multiply by `range`, the result is in the high word. Then comparing the low /// word against `zone` makes sure our distribution is uniform. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformInt { low: X, @@ -806,7 +806,7 @@ impl UniformSampler for UniformChar { /// [`new`]: UniformSampler::new /// [`new_inclusive`]: UniformSampler::new_inclusive /// [`Standard`]: crate::distributions::Standard -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformFloat { low: X, @@ -1647,4 +1647,12 @@ mod tests { ], ); } + + #[test] + fn uniform_distributions_can_be_compared() { + assert_eq!(Uniform::new(1.0, 2.0), Uniform::new(1.0, 2.0)); + + // To cover UniformInt + assert_eq!(Uniform::new(1 as u32, 2 as u32), Uniform::new(1 as u32, 2 as u32)); + } } diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 32da37f6..8252b172 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -75,7 +75,7 @@ use serde::{Serialize, Deserialize}; /// /// [`Uniform`]: crate::distributions::Uniform /// [`RngCore`]: crate::RngCore -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub struct WeightedIndex { @@ -418,6 +418,11 @@ mod test { 2, 2, 1, 3, 2, 1, 3, 3, 2, 1, ]); } + + #[test] + fn weighted_index_distributions_can_be_compared() { + assert_eq!(WeightedIndex::new(&[1, 2]), WeightedIndex::new(&[1, 2])); + } } /// Error type returned from `WeightedIndex::new`. From 937320cbfeebd4352a23086d9c6e68f067f74644 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Feb 2022 08:37:01 +0000 Subject: [PATCH 08/54] Update CHANGELOG for 0.8.5 (#1221) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c91fe96d..b0872af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Fix "min_const_gen" feature for `no_std` (#1173) - Check `libc::pthread_atfork` return value with panic on error (#1178) - More robust reseeding in case `ReseedingRng` is used from a fork handler (#1178) +- Fix nightly: remove unused `slice_partition_at_index` feature (#1215) +- Fix nightly + `simd_support`: update `packed_simd` (#1216) ### Rngs - `StdRng`: Switch from HC128 to ChaCha12 on emscripten (#1142). From 546acdc85101b8fa4fb0a14cda683332044700ff Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Feb 2022 08:38:55 +0000 Subject: [PATCH 09/54] README: bump rand version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44c2e4d5..d276dfb2 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -rand = "0.8.4" +rand = "0.8.5" ``` To get started using Rand, see [The Book](https://rust-random.github.io/book). From a8474f7932e2f0b9d8ee2b009f946049fecc317c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 9 Mar 2022 02:34:13 -0500 Subject: [PATCH 10/54] update Miri CI config (#1223) --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e90e380..fa1ff740 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -150,9 +150,9 @@ jobs: - uses: actions/checkout@v2 - name: Install toolchain run: | - MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) - rustup default "$MIRI_NIGHTLY" - rustup component add miri + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup - name: Test rand run: | cargo miri test --no-default-features --lib --tests From 5f0d3a10a9a28cec7831bedd2f5225e2e35a201c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 15 Sep 2021 19:12:24 +0200 Subject: [PATCH 11/54] rand_distr: Remove unused fields This breaks serialization compatibility with older versions. --- rand_distr/CHANGELOG.md | 4 ++++ rand_distr/src/gamma.rs | 3 +-- rand_distr/src/normal_inverse_gaussian.rs | 2 -- rand_distr/src/zipf.rs | 3 +-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 6b0cda28..66389d67 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,10 @@ 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). +## [0.5.0] - unreleased +- Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) + This breaks serialization compatibility with older versions. + ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index debad0c8..1a575bd6 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -544,7 +544,6 @@ struct BB { struct BC { alpha: N, beta: N, - delta: N, kappa1: N, kappa2: N, } @@ -646,7 +645,7 @@ where Ok(Beta { a, b, switched_params, algorithm: BetaAlgorithm::BC(BC { - alpha, beta, delta, kappa1, kappa2, + alpha, beta, kappa1, kappa2, }) }) } diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index e05d5b09..b1ba588a 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -34,7 +34,6 @@ where StandardNormal: Distribution, Standard: Distribution, { - alpha: F, beta: F, inverse_gaussian: InverseGaussian, } @@ -63,7 +62,6 @@ where let inverse_gaussian = InverseGaussian::new(mu, F::one()).unwrap(); Ok(Self { - alpha, beta, inverse_gaussian, }) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 84d33c05..e15b6cdd 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -145,7 +145,6 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution #[derive(Clone, Copy, Debug, PartialEq)] pub struct Zipf where F: Float, Standard: Distribution { - n: F, s: F, t: F, q: F, @@ -202,7 +201,7 @@ where F: Float, Standard: Distribution { }; debug_assert!(t > F::zero()); Ok(Zipf { - n, s, t, q + s, t, q }) } From 13193fcb8bf540ce417f5c8a39b616ca0210f4b6 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 20 Apr 2022 10:03:17 +0800 Subject: [PATCH 12/54] fix typo Signed-off-by: cuishuang --- rand_distr/tests/value_stability.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index 65c49644..d3754705 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -64,7 +64,7 @@ fn test_samples>( } #[test] -fn binominal_stability() { +fn binomial_stability() { // We have multiple code paths: np < 10, p > 0.5 test_samples(353, Binomial::new(2, 0.7).unwrap(), &[1, 1, 2, 1]); test_samples(353, Binomial::new(20, 0.3).unwrap(), &[7, 7, 5, 7]); From 3543f4b0258ecec04be570bbe9dc6e50d80bd3c1 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Thu, 19 May 2022 10:55:31 -0600 Subject: [PATCH 13/54] Make `CryptoRngCore` trait imply `CryptoRng` as well (#1230) --- rand_core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index fdbf6675..1234a566 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -226,7 +226,7 @@ pub trait CryptoRng {} /// buf /// } /// ``` -pub trait CryptoRngCore: RngCore { +pub trait CryptoRngCore: CryptoRng + RngCore { /// Upcast to an [`RngCore`] trait object. fn as_rngcore(&mut self) -> &mut dyn RngCore; } From 330efe9d42e26bc6a01dc7b6f60602f73e976035 Mon Sep 17 00:00:00 2001 From: masonk Date: Thu, 7 Jul 2022 03:20:11 -0400 Subject: [PATCH 14/54] Deterministic Rayon monte carlo example (#1236) * Deterministic Rayon monte carlo * Update deterministic mt with a batching example * discuss determinism in the context of rayon + rand * reword the discussion Co-authored-by: Mason Kramer --- Cargo.toml | 1 + examples/rayon-monte-carlo.rs | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100755 examples/rayon-monte-carlo.rs diff --git a/Cargo.toml b/Cargo.toml index 98ba373c..b0130574 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,3 +83,4 @@ libc = { version = "0.2.22", optional = true, default-features = false } rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" +rayon = "1.5.3" diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs new file mode 100755 index 00000000..041f7379 --- /dev/null +++ b/examples/rayon-monte-carlo.rs @@ -0,0 +1,79 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013-2018 The Rust Project Developers. +// +// 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. + +//! # Monte Carlo estimation of π with a chosen seed and rayon for parallelism +//! +//! Imagine that we have a square with sides of length 2 and a unit circle +//! (radius = 1), both centered at the origin. The areas are: +//! +//! ```text +//! area of circle = πr² = π * r * r = π +//! area of square = 2² = 4 +//! ``` +//! +//! The circle is entirely within the square, so if we sample many points +//! randomly from the square, roughly π / 4 of them should be inside the circle. +//! +//! We can use the above fact to estimate the value of π: pick many points in +//! the square at random, calculate the fraction that fall within the circle, +//! and multiply this fraction by 4. +//! +//! Note on determinism: +//! It's slightly tricky to build a parallel simulation using Rayon +//! which is both efficient *and* reproducible. +//! +//! Rayon's ParallelIterator api does not guarantee that the work will be +//! batched into identical batches on every run, so we can't simply use +//! map_init to construct one RNG per Rayon batch. +//! +//! Instead, we do our own batching, so that a Rayon work item becomes a +//! batch. Then we can fix our rng stream to the batched work item. +//! Batching amortizes the cost of constructing the Rng from a fixed seed +//! over BATCH_SIZE trials. Manually batching also turns out to be faster +//! for the nondeterministic version of this program as well. + +#![cfg(all(feature = "std", feature = "std_rng"))] + +use rand::distributions::{Distribution, Uniform}; +use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; +use rayon::prelude::*; + +static SEED: u64 = 0; +static BATCH_SIZE: u64 = 10_000; +static BATCHES: u64 = 1000; + +fn main() { + let range = Uniform::new(-1.0f64, 1.0); + + let in_circle = (0..BATCHES) + .into_par_iter() + .map(|i| { + let mut rng = ChaCha8Rng::seed_from_u64(SEED); + // We chose ChaCha because it's fast, has suitable statical properties for simulation, + // and because it supports this set_stream() api, which lets us chose a different stream + // per work item. ChaCha supports 2^64 independent streams. + rng.set_stream(i); + let mut count = 0; + for _ in 0..BATCH_SIZE { + let a = range.sample(&mut rng); + let b = range.sample(&mut rng); + if a * a + b * b <= 1.0 { + count += 1; + } + } + count + }) + .reduce(|| 0usize, |a, b| a + b); + + // prints something close to 3.14159... + println!( + "π is approximately {}", + 4. * (in_circle as f64) / ((BATCH_SIZE * BATCHES) as f64) + ); +} From 6112c843654ac18812e3ff4f52a7235c4a664bf1 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 7 Jul 2022 18:58:36 +0000 Subject: [PATCH 15/54] small deterministic example update --- examples/rayon-monte-carlo.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 041f7379..9b47896a 100755 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -55,7 +55,7 @@ fn main() { .into_par_iter() .map(|i| { let mut rng = ChaCha8Rng::seed_from_u64(SEED); - // We chose ChaCha because it's fast, has suitable statical properties for simulation, + // We chose ChaCha because it's fast, has suitable statistical properties for simulation, // and because it supports this set_stream() api, which lets us chose a different stream // per work item. ChaCha supports 2^64 independent streams. rng.set_stream(i); @@ -69,7 +69,7 @@ fn main() { } count }) - .reduce(|| 0usize, |a, b| a + b); + .sum::(); // prints something close to 3.14159... println!( From 1f99bb72dcfebe7a8fc190bb344fff01092b1384 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Fri, 8 Jul 2022 19:36:21 +0000 Subject: [PATCH 16/54] assert deterministic --- examples/rayon-monte-carlo.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 9b47896a..82d5a36c 100755 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -71,6 +71,9 @@ fn main() { }) .sum::(); + // assert this is deterministic + assert_eq!(in_circle, 7852263); + // prints something close to 3.14159... println!( "π is approximately {}", From 89d7e4e184f920d2e34a133318e067f42a9eaff8 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Sat, 9 Jul 2022 03:26:38 +0000 Subject: [PATCH 17/54] another typo --- examples/rayon-monte-carlo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 82d5a36c..f0e7114a 100755 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -56,7 +56,7 @@ fn main() { .map(|i| { let mut rng = ChaCha8Rng::seed_from_u64(SEED); // We chose ChaCha because it's fast, has suitable statistical properties for simulation, - // and because it supports this set_stream() api, which lets us chose a different stream + // and because it supports this set_stream() api, which lets us choose a different stream // per work item. ChaCha supports 2^64 independent streams. rng.set_stream(i); let mut count = 0; From 599d7f8f6dbead4c9f6dcf6705c38b68b5c45eff Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Wed, 6 Jul 2022 23:44:05 -0700 Subject: [PATCH 18/54] switch to std::simd, expand SIMD stuff & docs move __m128i to stable, expand documentation, add SIMD to Bernoulli, add maskNxM, add __m512i genericize simd uniform int remove some debug stuff remove bernoulli foo foo --- Cargo.toml | 11 +-- src/distributions/bernoulli.rs | 5 +- src/distributions/float.rs | 74 +++++++++--------- src/distributions/integer.rs | 116 +++++++++++++++------------- src/distributions/mod.rs | 15 +++- src/distributions/other.rs | 33 ++++++++ src/distributions/uniform.rs | 134 ++++++++++++++------------------- src/distributions/utils.rs | 133 ++++++++++++++++++-------------- src/lib.rs | 2 +- 9 files changed, 287 insertions(+), 236 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98ba373c..32c92fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,8 +41,8 @@ alloc = ["rand_core/alloc"] # Option: use getrandom package for seeding getrandom = ["rand_core/getrandom"] -# Option (requires nightly): experimental SIMD support -simd_support = ["packed_simd"] +# Option (requires nightly Rust): experimental SIMD support +simd_support = [] # Option (enabled by default): enable StdRng std_rng = ["rand_chacha"] @@ -68,13 +68,6 @@ log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = false, optional = true } -[dependencies.packed_simd] -# NOTE: so far no version works reliably due to dependence on unstable features -package = "packed_simd_2" -version = "0.3.7" -optional = true -features = ["into_bits"] - [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) libc = { version = "0.2.22", optional = true, default-features = false } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 226db79f..676b79a5 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -14,6 +14,7 @@ use core::{fmt, u64}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; + /// The Bernoulli distribution. /// /// This is a special case of the Binomial distribution where `n = 1`. @@ -147,10 +148,10 @@ mod test { use crate::Rng; #[test] - #[cfg(feature="serde1")] + #[cfg(feature = "serde1")] fn test_serializing_deserializing_bernoulli() { let coin_flip = Bernoulli::new(0.5).unwrap(); - let de_coin_flip : Bernoulli = bincode::deserialize(&bincode::serialize(&coin_flip).unwrap()).unwrap(); + let de_coin_flip: Bernoulli = bincode::deserialize(&bincode::serialize(&coin_flip).unwrap()).unwrap(); assert_eq!(coin_flip.p_int, de_coin_flip.p_int); } diff --git a/src/distributions/float.rs b/src/distributions/float.rs index ce5946f7..54aebad4 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -8,11 +8,11 @@ //! Basic floating-point number distributions -use crate::distributions::utils::FloatSIMDUtils; +use crate::distributions::utils::{IntAsSIMD, FloatAsSIMD, FloatSIMDUtils}; use crate::distributions::{Distribution, Standard}; use crate::Rng; use core::mem; -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -99,7 +99,7 @@ macro_rules! float_impls { // The exponent is encoded using an offset-binary representation let exponent_bits: $u_scalar = (($exponent_bias + exponent) as $u_scalar) << $fraction_bits; - $ty::from_bits(self | exponent_bits) + $ty::from_bits(self | $uty::splat(exponent_bits)) } } @@ -108,13 +108,13 @@ macro_rules! float_impls { // Multiply-based method; 24/53 random bits; [0, 1) interval. // We use the most significant bits because for simple RNGs // those are usually more random. - let float_size = mem::size_of::<$f_scalar>() as u32 * 8; + let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let precision = $fraction_bits + 1; let scale = 1.0 / ((1 as $u_scalar << precision) as $f_scalar); let value: $uty = rng.gen(); - let value = value >> (float_size - precision); - scale * $ty::cast_from_int(value) + let value = value >> $uty::splat(float_size - precision); + $ty::splat(scale) * $ty::cast_from_int(value) } } @@ -123,14 +123,14 @@ macro_rules! float_impls { // Multiply-based method; 24/53 random bits; (0, 1] interval. // We use the most significant bits because for simple RNGs // those are usually more random. - let float_size = mem::size_of::<$f_scalar>() as u32 * 8; + let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let precision = $fraction_bits + 1; let scale = 1.0 / ((1 as $u_scalar << precision) as $f_scalar); let value: $uty = rng.gen(); - let value = value >> (float_size - precision); + let value = value >> $uty::splat(float_size - precision); // Add 1 to shift up; will not overflow because of right-shift: - scale * $ty::cast_from_int(value + 1) + $ty::splat(scale) * $ty::cast_from_int(value + $uty::splat(1)) } } @@ -140,11 +140,11 @@ macro_rules! float_impls { // We use the most significant bits because for simple RNGs // those are usually more random. use core::$f_scalar::EPSILON; - let float_size = mem::size_of::<$f_scalar>() as u32 * 8; + let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let value: $uty = rng.gen(); - let fraction = value >> (float_size - $fraction_bits); - fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0) + let fraction = value >> $uty::splat(float_size - $fraction_bits); + fraction.into_float_with_exponent(0) - $ty::splat(1.0 - EPSILON / 2.0) } } } @@ -169,10 +169,10 @@ float_impls! { f64x4, u64x4, f64, u64, 52, 1023 } #[cfg(feature = "simd_support")] float_impls! { f64x8, u64x8, f64, u64, 52, 1023 } - #[cfg(test)] mod tests { use super::*; + use crate::distributions::utils::FloatAsSIMD; use crate::rngs::mock::StepRng; const EPSILON32: f32 = ::core::f32::EPSILON; @@ -182,29 +182,31 @@ mod tests { ($fnn:ident, $ty:ident, $ZERO:expr, $EPSILON:expr) => { #[test] fn $fnn() { + let two = $ty::splat(2.0); + // Standard let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.gen::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 8 | 1 << (8 + 32), 0); - assert_eq!(one.gen::<$ty>(), $EPSILON / 2.0); + assert_eq!(one.gen::<$ty>(), $EPSILON / two); let mut max = StepRng::new(!0, 0); - assert_eq!(max.gen::<$ty>(), 1.0 - $EPSILON / 2.0); + assert_eq!(max.gen::<$ty>(), $ty::splat(1.0) - $EPSILON / two); // OpenClosed01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 8 | 1 << (8 + 32), 0); assert_eq!(one.sample::<$ty, _>(OpenClosed01), $EPSILON); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + 1.0); + assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + $ty::splat(1.0)); // Open01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(Open01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(Open01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 9 | 1 << (9 + 32), 0); - assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / 2.0 * 3.0); + assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / two * $ty::splat(3.0)); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(Open01), 1.0 - $EPSILON / 2.0); + assert_eq!(max.sample::<$ty, _>(Open01), $ty::splat(1.0) - $EPSILON / two); } }; } @@ -222,29 +224,31 @@ mod tests { ($fnn:ident, $ty:ident, $ZERO:expr, $EPSILON:expr) => { #[test] fn $fnn() { + let two = $ty::splat(2.0); + // Standard let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.gen::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 11, 0); - assert_eq!(one.gen::<$ty>(), $EPSILON / 2.0); + assert_eq!(one.gen::<$ty>(), $EPSILON / two); let mut max = StepRng::new(!0, 0); - assert_eq!(max.gen::<$ty>(), 1.0 - $EPSILON / 2.0); + assert_eq!(max.gen::<$ty>(), $ty::splat(1.0) - $EPSILON / two); // OpenClosed01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 11, 0); assert_eq!(one.sample::<$ty, _>(OpenClosed01), $EPSILON); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + 1.0); + assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + $ty::splat(1.0)); // Open01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(Open01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(Open01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 12, 0); - assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / 2.0 * 3.0); + assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / two * $ty::splat(3.0)); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(Open01), 1.0 - $EPSILON / 2.0); + assert_eq!(max.sample::<$ty, _>(Open01), $ty::splat(1.0) - $EPSILON / two); } }; } @@ -296,16 +300,16 @@ mod tests { // 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, f32x2::from([0.0, 0.0]), &[ + f32x2::from([0.0035963655, 0.7346052]), + f32x2::from([0.09778172, 0.20298547]), + f32x2::from([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), + test_samples(&Standard, f64x2::from([0.0, 0.0]), &[ + f64x2::from([0.7346051961657583, 0.20298547462974248]), + f64x2::from([0.8166436635290655, 0.7423708925400552]), + f64x2::from([0.16387782224016323, 0.9087068770169618]), ]); } } diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 19ce7159..d905044e 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -11,12 +11,17 @@ use crate::distributions::{Distribution, Standard}; use crate::Rng; #[cfg(all(target_arch = "x86", feature = "simd_support"))] +use core::arch::x86::__m512i; +#[cfg(target_arch = "x86")] use core::arch::x86::{__m128i, __m256i}; #[cfg(all(target_arch = "x86_64", feature = "simd_support"))] +use core::arch::x86_64::__m512i; +#[cfg(target_arch = "x86_64")] use core::arch::x86_64::{__m128i, __m256i}; use core::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, NonZeroU128}; -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; +use core::mem; impl Distribution for Standard { #[inline] @@ -109,53 +114,63 @@ impl_nzint!(NonZeroU64, NonZeroU64::new); impl_nzint!(NonZeroU128, NonZeroU128::new); impl_nzint!(NonZeroUsize, NonZeroUsize::new); -#[cfg(feature = "simd_support")] -macro_rules! simd_impl { - ($(($intrinsic:ident, $vec:ty),)+) => {$( +macro_rules! intrinsic_impl { + ($($intrinsic:ident),+) => {$( + /// Available only on x86/64 platforms impl Distribution<$intrinsic> for Standard { #[inline] fn sample(&self, rng: &mut R) -> $intrinsic { - $intrinsic::from_bits(rng.gen::<$vec>()) + // On proper hardware, this should compile to SIMD instructions + // Verified on x86 Haswell with __m128i, __m256i + let mut buf = [0_u8; mem::size_of::<$intrinsic>()]; + rng.fill_bytes(&mut buf); + // x86 is little endian so no need for conversion + // SAFETY: we know [u8; N] and $intrinsic have the same size + unsafe { mem::transmute_copy(&buf) } } } )+}; - - ($bits:expr,) => {}; - ($bits:expr, $ty:ty, $($ty_more:ty,)*) => { - simd_impl!($bits, $($ty_more,)*); - - impl Distribution<$ty> for Standard { - #[inline] - fn sample(&self, rng: &mut R) -> $ty { - let mut vec: $ty = Default::default(); - unsafe { - let ptr = &mut vec; - let b_ptr = &mut *(ptr as *mut $ty as *mut [u8; $bits/8]); - rng.fill_bytes(b_ptr); - } - vec.to_le() - } - } - }; } #[cfg(feature = "simd_support")] -simd_impl!(16, u8x2, i8x2,); +macro_rules! simd_impl { + ($($ty:ty),+) => {$( + /// Requires nightly Rust and the [`simd_support`] feature + /// + /// [`simd_support`]: https://github.com/rust-random/rand#crate-features + impl Distribution<$ty> for Standard { + #[inline] + fn sample(&self, rng: &mut R) -> $ty { + // TODO: impl this generically once const generics are robust enough + let mut vec: Simd() }> = Default::default(); + rng.fill_bytes(vec.as_mut_array()); + // NOTE: replace with `to_le` if added to core::simd + #[cfg(not(target_endian = "little"))] + { + vec = vec.reverse(); + } + // SAFETY: we know u8xN and $ty have the same size + unsafe { mem::transmute_copy(&vec) } + } + } + )+}; +} + #[cfg(feature = "simd_support")] -simd_impl!(32, u8x4, i8x4, u16x2, i16x2,); -#[cfg(feature = "simd_support")] -simd_impl!(64, u8x8, i8x8, u16x4, i16x4, u32x2, i32x2,); -#[cfg(feature = "simd_support")] -simd_impl!(128, u8x16, i8x16, u16x8, i16x8, u32x4, i32x4, u64x2, i64x2,); -#[cfg(feature = "simd_support")] -simd_impl!(256, u8x32, i8x32, u16x16, i16x16, u32x8, i32x8, u64x4, i64x4,); -#[cfg(feature = "simd_support")] -simd_impl!(512, u8x64, i8x64, u16x32, i16x32, u32x16, i32x16, u64x8, i64x8,); +simd_impl!( + i8x4, i8x8, i8x16, i8x32, i8x64, i16x2, i16x4, i16x8, i16x16, i16x32, i32x2, i32x4, i32x8, + i32x16, i64x2, i64x4, i64x8, isizex2, isizex4, isizex8, u8x4, u8x8, u8x16, u8x32, u8x64, u16x2, + u16x4, u16x8, u16x16, u16x32, u32x2, u32x4, u32x8, u32x16, u64x2, u64x4, u64x8, usizex2, + usizex4, usizex8 +); + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +intrinsic_impl!(__m128i, __m256i); #[cfg(all( - feature = "simd_support", - any(target_arch = "x86", target_arch = "x86_64") + any(target_arch = "x86", target_arch = "x86_64"), + feature = "simd_support" ))] -simd_impl!((__m128i, u8x16), (__m256i, u8x32),); +intrinsic_impl!(__m512i); #[cfg(test)] mod tests { @@ -221,24 +236,19 @@ mod tests { { // 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), + u8x4::from([9, 126, 87, 132]), + u8x4::from([247, 167, 123, 153]), + u8x4::from([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), + u8x8::from([9, 126, 87, 132, 247, 167, 123, 153]), + u8x8::from([111, 149, 73, 120, 68, 171, 98, 223]), + u8x8::from([24, 121, 1, 50, 13, 46, 164, 20]), ]); test_samples(i64x8::default(), &[ - i64x8::new( + i64x8::from([ -7387126082252079607, -2350127744969763473, 1487364411147516184, @@ -247,8 +257,8 @@ mod tests { 6022086574635100741, -5080089175222015595, -4066367846667249123, - ), - i64x8::new( + ]), + i64x8::from([ 9180885022207963908, 3095981199532211089, 6586075293021332726, @@ -257,8 +267,8 @@ mod tests { 5287129228749947252, 444726432079249540, -1587028029513790706, - ), - i64x8::new( + ]), + i64x8::from([ 6075236523189346388, 1351763722368165432, -6192309979959753740, @@ -267,7 +277,7 @@ mod tests { 7522501477800909500, -1837258847956201231, -586926753024886735, - ), + ]), ]); } } diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 05ca8060..9b0a8867 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -149,8 +149,14 @@ use crate::Rng; /// * `bool`: Generates `false` or `true`, each with probability 0.5. /// * Floating point types (`f32` and `f64`): Uniformly distributed in the /// half-open range `[0, 1)`. See notes below. -/// * Wrapping integers (`Wrapping`), besides the type identical to their +/// * Wrapping integers ([`Wrapping`]), besides the type identical to their /// normal integer variants. +/// * Non-zero integers ([`NonZeroU8`]), which are like their normal integer +/// variants but cannot produce zero. +/// * SIMD types like x86's [`__m128i`], `std::simd`'s [`u32x4`]/[`f32x4`]/ +/// [`mask32x4`] (requires [`simd_support`]), where each lane is distributed +/// like their scalar `Standard` variants. See the list of `Standard` +/// implementations for more. /// /// The `Standard` distribution also supports generation of the following /// compound types where all component types are supported: @@ -213,6 +219,13 @@ use crate::Rng; /// CPUs all methods have approximately equal performance). /// /// [`Uniform`]: uniform::Uniform +/// [`Wrapping`]: std::num::Wrapping +/// [`NonZeroU8`]: std::num::NonZeroU8 +/// [`__m128i`]: https://doc.rust-lang.org/nightly/core/arch/x86/struct.__m128i.html +/// [`u32x4`]: std::simd::u32x4 +/// [`f32x4`]: std::simd::f32x4 +/// [`mask32x4`]: std::simd::mask32x4 +/// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Standard; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 03802a76..b3b5e4e0 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -22,6 +22,8 @@ use crate::Rng; use serde::{Serialize, Deserialize}; #[cfg(feature = "min_const_gen")] use core::mem::{self, MaybeUninit}; +#[cfg(feature = "simd_support")] +use core::simd::{Mask, Simd, LaneCount, SupportedLaneCount, MaskElement, SimdElement}; // ----- Sampling distributions ----- @@ -145,6 +147,37 @@ impl Distribution for Standard { } } +/// Requires nightly Rust and the [`simd_support`] feature +/// +/// Note that on some hardware like x86/64 mask operations like [`_mm_blendv_epi8`] +/// only care about a single bit. This means that you could use uniform random bits +/// directly: +/// +/// ```ignore +/// // this may be faster... +/// let x = unsafe { _mm_blendv_epi8(a.into(), b.into(), rng.gen::<__m128i>()) }; +/// +/// // ...than this +/// let x = rng.gen::().select(b, a); +/// ``` +/// +/// Since most bits are unused you could also generate only as many bits as you need. +/// +/// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ +/// [`simd_support`]: https://github.com/rust-random/rand#crate-features +#[cfg(feature = "simd_support")] +impl Distribution> for Standard +where + T: MaskElement + PartialOrd + SimdElement + Default, + LaneCount: SupportedLaneCount, + Standard: Distribution>, +{ + #[inline] + fn sample(&self, rng: &mut R) -> Mask { + rng.gen().lanes_lt(Simd::default()) + } +} + macro_rules! tuple_impl { // use variables to indicate the arity of the tuple ($($tyvar:ident),* ) => { diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 261357b2..d9197c68 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -110,15 +110,17 @@ use core::time::Duration; use core::ops::{Range, RangeInclusive}; use crate::distributions::float::IntoFloat; -use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, WideningMultiply}; +use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply}; use crate::distributions::Distribution; +#[cfg(feature = "simd_support")] +use crate::distributions::Standard; use crate::{Rng, RngCore}; #[cfg(not(feature = "std"))] #[allow(unused_imports)] // rustc doesn't detect that this is actually used use crate::distributions::utils::Float; -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -571,21 +573,30 @@ uniform_int_impl! { u128, u128, u128 } #[cfg(feature = "simd_support")] macro_rules! uniform_simd_int_impl { - ($ty:ident, $unsigned:ident, $u_scalar:ident) => { + ($ty:ident, $unsigned:ident) => { // The "pick the largest zone that can fit in an `u32`" optimization // is less useful here. Multiple lanes complicate things, we don't // know the PRNG's minimal output size, and casting to a larger vector // is generally a bad idea for SIMD performance. The user can still // implement it manually. - - // TODO: look into `Uniform::::new(0u32, 100)` functionality - // perhaps `impl SampleUniform for $u_scalar`? - impl SampleUniform for $ty { - type Sampler = UniformInt<$ty>; + impl SampleUniform for Simd<$ty, LANES> + where + LaneCount: SupportedLaneCount, + Simd<$unsigned, LANES>: + WideningMultiply, Simd<$unsigned, LANES>)>, + Standard: Distribution>, + { + type Sampler = UniformInt>; } - impl UniformSampler for UniformInt<$ty> { - type X = $ty; + impl UniformSampler for UniformInt> + where + LaneCount: SupportedLaneCount, + Simd<$unsigned, LANES>: + WideningMultiply, Simd<$unsigned, LANES>)>, + Standard: Distribution>, + { + type X = Simd<$ty, LANES>; #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. @@ -595,8 +606,8 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.lt(high).all(), "Uniform::new called with `low >= high`"); - UniformSampler::new_inclusive(low, high - 1) + assert!(low.lanes_lt(high).all(), "Uniform::new called with `low >= high`"); + UniformSampler::new_inclusive(low, high - Simd::splat(1)) } #[inline] // if the range is constant, this helps LLVM to do the @@ -607,20 +618,20 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.le(high).all(), + assert!(low.lanes_le(high).all(), "Uniform::new_inclusive called with `low > high`"); - let unsigned_max = ::core::$u_scalar::MAX; + let unsigned_max = Simd::splat(::core::$unsigned::MAX); - // NOTE: these may need to be replaced with explicitly - // wrapping operations if `packed_simd` changes - let range: $unsigned = ((high - low) + 1).cast(); + // NOTE: all `Simd` operations are inherently wrapping, + // see https://doc.rust-lang.org/std/simd/struct.Simd.html + let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); // `% 0` will panic at runtime. - let not_full_range = range.gt($unsigned::splat(0)); + let not_full_range = range.lanes_gt(Simd::splat(0)); // replacing 0 with `unsigned_max` allows a faster `select` // with bitwise OR - let modulo = not_full_range.select(range, $unsigned::splat(unsigned_max)); + let modulo = not_full_range.select(range, unsigned_max); // wrapping addition - let ints_to_reject = (unsigned_max - range + 1) % modulo; + let ints_to_reject = (unsigned_max - range + Simd::splat(1)) % modulo; // When `range` is 0, `lo` of `v.wmul(range)` will always be // zero which means only one sample is needed. let zone = unsigned_max - ints_to_reject; @@ -634,8 +645,8 @@ macro_rules! uniform_simd_int_impl { } fn sample(&self, rng: &mut R) -> Self::X { - let range: $unsigned = self.range.cast(); - let zone: $unsigned = self.z.cast(); + let range: Simd<$unsigned, LANES> = self.range.cast(); + let zone: Simd<$unsigned, LANES> = self.z.cast(); // This might seem very slow, generating a whole new // SIMD vector for every sample rejection. For most uses @@ -646,19 +657,19 @@ macro_rules! uniform_simd_int_impl { // rejection. The replacement method does however add a little // overhead. Benchmarking or calculating probabilities might // reveal contexts where this replacement method is slower. - let mut v: $unsigned = rng.gen(); + let mut v: Simd<$unsigned, LANES> = rng.gen(); loop { let (hi, lo) = v.wmul(range); - let mask = lo.le(zone); + let mask = lo.lanes_le(zone); if mask.all() { - let hi: $ty = hi.cast(); + let hi: Simd<$ty, LANES> = hi.cast(); // wrapping addition let result = self.low + hi; // `select` here compiles to a blend operation // When `range.eq(0).none()` the compare and blend // operations are avoided. - let v: $ty = v.cast(); - return range.gt($unsigned::splat(0)).select(result, v); + let v: Simd<$ty, LANES> = v.cast(); + return range.lanes_gt(Simd::splat(0)).select(result, v); } // Replace only the failing lanes v = mask.select(v, rng.gen()); @@ -668,51 +679,16 @@ macro_rules! uniform_simd_int_impl { }; // bulk implementation - ($(($unsigned:ident, $signed:ident),)+ $u_scalar:ident) => { + ($(($unsigned:ident, $signed:ident)),+) => { $( - uniform_simd_int_impl!($unsigned, $unsigned, $u_scalar); - uniform_simd_int_impl!($signed, $unsigned, $u_scalar); + uniform_simd_int_impl!($unsigned, $unsigned); + uniform_simd_int_impl!($signed, $unsigned); )+ }; } #[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u64x2, i64x2), - (u64x4, i64x4), - (u64x8, i64x8), - u64 -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u32x2, i32x2), - (u32x4, i32x4), - (u32x8, i32x8), - (u32x16, i32x16), - u32 -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u16x2, i16x2), - (u16x4, i16x4), - (u16x8, i16x8), - (u16x16, i16x16), - (u16x32, i16x32), - u16 -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u8x2, i8x2), - (u8x4, i8x4), - (u8x8, i8x8), - (u8x16, i8x16), - (u8x32, i8x32), - (u8x64, i8x64), - u8 -} +uniform_simd_int_impl! { (u8, i8), (u16, i16), (u32, i32), (u64, i64) } impl SampleUniform for char { type Sampler = UniformChar; @@ -847,7 +823,7 @@ macro_rules! uniform_float_impl { loop { let mask = (scale * max_rand + low).ge_mask(high); - if mask.none() { + if !mask.any() { break; } scale = scale.decrease_masked(mask); @@ -886,7 +862,7 @@ macro_rules! uniform_float_impl { loop { let mask = (scale * max_rand + low).gt_mask(high); - if mask.none() { + if !mask.any() { break; } scale = scale.decrease_masked(mask); @@ -899,11 +875,11 @@ macro_rules! uniform_float_impl { fn sample(&self, rng: &mut R) -> Self::X { // Generate a value in the range [1, 2) - let value1_2 = (rng.gen::<$uty>() >> $bits_to_discard).into_float_with_exponent(0); + let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) in order to avoid // overflowing into infinity when multiplying with scale - let value0_1 = value1_2 - 1.0; + let value0_1 = value1_2 - <$ty>::splat(1.0); // We don't use `f64::mul_add`, because it is not available with // `no_std`. Furthermore, it is slower for some targets (but @@ -939,11 +915,11 @@ macro_rules! uniform_float_impl { loop { // Generate a value in the range [1, 2) let value1_2 = - (rng.gen::<$uty>() >> $bits_to_discard).into_float_with_exponent(0); + (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) in order to avoid // overflowing into infinity when multiplying with scale - let value0_1 = value1_2 - 1.0; + let value0_1 = value1_2 - <$ty>::splat(1.0); // Doing multiply before addition allows some architectures // to use a single instruction. @@ -1184,7 +1160,7 @@ mod tests { _ => panic!("`UniformDurationMode` was not serialized/deserialized correctly") } } - + #[test] #[cfg(feature = "serde1")] fn test_uniform_serialization() { @@ -1289,8 +1265,8 @@ mod tests { ($ty::splat(10), $ty::splat(127)), ($ty::splat($scalar::MIN), $ty::splat($scalar::MAX)), ], - |x: $ty, y| x.le(y).all(), - |x: $ty, y| x.lt(y).all() + |x: $ty, y| x.lanes_le(y).all(), + |x: $ty, y| x.lanes_lt(y).all() );)* }}; } @@ -1298,8 +1274,8 @@ mod tests { #[cfg(feature = "simd_support")] { - t!(u8x2, u8x4, u8x8, u8x16, u8x32, u8x64 => u8); - t!(i8x2, i8x4, i8x8, i8x16, i8x32, i8x64 => i8); + t!(u8x4, u8x8, u8x16, u8x32, u8x64 => u8); + t!(i8x4, i8x8, i8x16, i8x32, i8x64 => i8); t!(u16x2, u16x4, u16x8, u16x16, u16x32 => u16); t!(i16x2, i16x4, i16x8, i16x16, i16x32 => i16); t!(u32x2, u32x4, u32x8, u32x16 => u32); @@ -1351,7 +1327,7 @@ mod tests { (-::core::$f_scalar::MAX * 0.2, ::core::$f_scalar::MAX * 0.7), ]; for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::lanes() { + for lane in 0..<$ty>::LANES { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); let my_uniform = Uniform::new(low, high); @@ -1474,7 +1450,7 @@ mod tests { (::std::$f_scalar::NEG_INFINITY, ::std::$f_scalar::INFINITY), ]; for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::lanes() { + for lane in 0..<$ty>::LANES { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); assert!(catch_unwind(|| range(low, high)).is_err()); diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 89da5fd7..689a4a1d 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -8,7 +8,7 @@ //! Math helper functions -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; pub(crate) trait WideningMultiply { @@ -45,7 +45,7 @@ macro_rules! wmul_impl { let y: $wide = self.cast(); let x: $wide = x.cast(); let tmp = y * x; - let hi: $ty = (tmp >> $shift).cast(); + let hi: $ty = (tmp >> Simd::splat($shift)).cast(); let lo: $ty = tmp.cast(); (hi, lo) } @@ -99,19 +99,20 @@ macro_rules! wmul_impl_large { #[inline(always)] fn wmul(self, b: $ty) -> Self::Output { // needs wrapping multiplication - const LOWER_MASK: $scalar = !0 >> $half; + const LOWER_MASK: $ty = <$ty>::splat(!0 >> $half); + const HALF: $ty = <$ty>::splat($half); let mut low = (self & LOWER_MASK) * (b & LOWER_MASK); - let mut t = low >> $half; + let mut t = low >> HALF; low &= LOWER_MASK; - t += (self >> $half) * (b & LOWER_MASK); - low += (t & LOWER_MASK) << $half; - let mut high = t >> $half; - t = low >> $half; + t += (self >> HALF) * (b & LOWER_MASK); + low += (t & LOWER_MASK) << HALF; + let mut high = t >> HALF; + t = low >> HALF; low &= LOWER_MASK; - t += (b >> $half) * (self & LOWER_MASK); - low += (t & LOWER_MASK) << $half; - high += t >> $half; - high += (self >> $half) * (b >> $half); + t += (b >> HALF) * (self & LOWER_MASK); + low += (t & LOWER_MASK) << HALF; + high += t >> HALF; + high += (self >> HALF) * (b >> HALF); (high, low) } @@ -148,7 +149,6 @@ mod simd_wmul { #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; wmul_impl! { - (u8x2, u16x2), (u8x4, u16x4), (u8x8, u16x8), (u8x16, u16x16), @@ -167,16 +167,14 @@ mod simd_wmul { // means `wmul` can be implemented with only two instructions. #[allow(unused_macros)] macro_rules! wmul_impl_16 { - ($ty:ident, $intrinsic:ident, $mulhi:ident, $mullo:ident) => { + ($ty:ident, $mulhi:ident, $mullo:ident) => { impl WideningMultiply for $ty { type Output = ($ty, $ty); #[inline(always)] fn wmul(self, x: $ty) -> Self::Output { - let b = $intrinsic::from_bits(x); - let a = $intrinsic::from_bits(self); - let hi = $ty::from_bits(unsafe { $mulhi(a, b) }); - let lo = $ty::from_bits(unsafe { $mullo(a, b) }); + let hi = unsafe { $mulhi(self.into(), x.into()) }.into(); + let lo = unsafe { $mullo(self.into(), x.into()) }.into(); (hi, lo) } } @@ -184,11 +182,11 @@ mod simd_wmul { } #[cfg(target_feature = "sse2")] - wmul_impl_16! { u16x8, __m128i, _mm_mulhi_epu16, _mm_mullo_epi16 } + wmul_impl_16! { u16x8, _mm_mulhi_epu16, _mm_mullo_epi16 } #[cfg(target_feature = "avx2")] - wmul_impl_16! { u16x16, __m256i, _mm256_mulhi_epu16, _mm256_mullo_epi16 } - // FIXME: there are no `__m512i` types in stdsimd yet, so `wmul::` - // cannot use the same implementation. + wmul_impl_16! { u16x16, _mm256_mulhi_epu16, _mm256_mullo_epi16 } + #[cfg(target_feature = "avx512bw")] + wmul_impl_16! { u16x32, _mm512_mulhi_epu16, _mm512_mullo_epi16 } wmul_impl! { (u32x2, u64x2), @@ -199,6 +197,7 @@ mod simd_wmul { // TODO: optimize, this seems to seriously slow things down wmul_impl_large! { (u8x64,) u8, 4 } + #[cfg(not(target_feature = "avx512bw"))] wmul_impl_large! { (u16x32,) u16, 8 } wmul_impl_large! { (u32x16,) u32, 16 } wmul_impl_large! { (u64x2, u64x4, u64x8,) u64, 32 } @@ -229,6 +228,10 @@ pub(crate) trait FloatSIMDUtils { // value, not by retaining the binary representation. type UInt; fn cast_from_int(i: Self::UInt) -> Self; + + type Scalar; + fn replace(self, index: usize, new_value: Self::Scalar) -> Self; + fn extract(self, index: usize) -> Self::Scalar; } /// Implement functions available in std builds but missing from core primitives @@ -243,26 +246,23 @@ pub(crate) trait Float: Sized { /// Implement functions on f32/f64 to give them APIs similar to SIMD types pub(crate) trait FloatAsSIMD: Sized { - #[inline(always)] - fn lanes() -> usize { - 1 - } + const LANES: usize = 1; #[inline(always)] fn splat(scalar: Self) -> Self { scalar } +} + +pub(crate) trait IntAsSIMD: Sized { #[inline(always)] - fn extract(self, index: usize) -> Self { - debug_assert_eq!(index, 0); - self - } - #[inline(always)] - fn replace(self, index: usize, new_value: Self) -> Self { - debug_assert_eq!(index, 0); - new_value + fn splat(scalar: Self) -> Self { + scalar } } +impl IntAsSIMD for u32 {} +impl IntAsSIMD for u64 {} + pub(crate) trait BoolAsSIMD: Sized { fn any(self) -> bool; fn all(self) -> bool; @@ -308,6 +308,7 @@ macro_rules! scalar_float_impl { impl FloatSIMDUtils for $ty { type Mask = bool; + type Scalar = $ty; type UInt = $uty; #[inline(always)] @@ -350,6 +351,18 @@ macro_rules! scalar_float_impl { fn cast_from_int(i: Self::UInt) -> Self { i as $ty } + + #[inline] + fn replace(self, index: usize, new_value: Self::Scalar) -> Self { + debug_assert_eq!(index, 0); + new_value + } + + #[inline] + fn extract(self, index: usize) -> Self::Scalar { + debug_assert_eq!(index, 0); + self + } } impl FloatAsSIMD for $ty {} @@ -362,42 +375,42 @@ scalar_float_impl!(f64, u64); #[cfg(feature = "simd_support")] macro_rules! simd_impl { - ($ty:ident, $f_scalar:ident, $mty:ident, $uty:ident) => { - impl FloatSIMDUtils for $ty { - type Mask = $mty; - type UInt = $uty; + ($fty:ident, $uty:ident) => { + impl FloatSIMDUtils for Simd<$fty, LANES> + where LaneCount: SupportedLaneCount + { + type Mask = Mask<<$fty as SimdElement>::Mask, LANES>; + type Scalar = $fty; + type UInt = Simd<$uty, LANES>; #[inline(always)] fn all_lt(self, other: Self) -> bool { - self.lt(other).all() + self.lanes_lt(other).all() } #[inline(always)] fn all_le(self, other: Self) -> bool { - self.le(other).all() + self.lanes_le(other).all() } #[inline(always)] fn all_finite(self) -> bool { - self.finite_mask().all() + self.is_finite().all() } #[inline(always)] fn finite_mask(self) -> Self::Mask { - // This can possibly be done faster by checking bit patterns - let neg_inf = $ty::splat(::core::$f_scalar::NEG_INFINITY); - let pos_inf = $ty::splat(::core::$f_scalar::INFINITY); - self.gt(neg_inf) & self.lt(pos_inf) + self.is_finite() } #[inline(always)] fn gt_mask(self, other: Self) -> Self::Mask { - self.gt(other) + self.lanes_gt(other) } #[inline(always)] fn ge_mask(self, other: Self) -> Self::Mask { - self.ge(other) + self.lanes_ge(other) } #[inline(always)] @@ -406,24 +419,32 @@ macro_rules! simd_impl { // true, and 0 for false. Adding that to the binary // representation of a float means subtracting one from // the binary representation, resulting in the next lower - // value representable by $ty. This works even when the + // value representable by $fty. This works even when the // current value is infinity. debug_assert!(mask.any(), "At least one lane must be set"); - <$ty>::from_bits(<$uty>::from_bits(self) + <$uty>::from_bits(mask)) + Self::from_bits(self.to_bits() + mask.to_int().cast()) } #[inline] fn cast_from_int(i: Self::UInt) -> Self { i.cast() } + + #[inline] + fn replace(mut self, index: usize, new_value: Self::Scalar) -> Self { + self.as_mut_array()[index] = new_value; + self + } + + #[inline] + fn extract(self, index: usize) -> Self::Scalar { + self.as_array()[index] + } } }; } -#[cfg(feature="simd_support")] simd_impl! { f32x2, f32, m32x2, u32x2 } -#[cfg(feature="simd_support")] simd_impl! { f32x4, f32, m32x4, u32x4 } -#[cfg(feature="simd_support")] simd_impl! { f32x8, f32, m32x8, u32x8 } -#[cfg(feature="simd_support")] simd_impl! { f32x16, f32, m32x16, u32x16 } -#[cfg(feature="simd_support")] simd_impl! { f64x2, f64, m64x2, u64x2 } -#[cfg(feature="simd_support")] simd_impl! { f64x4, f64, m64x4, u64x4 } -#[cfg(feature="simd_support")] simd_impl! { f64x8, f64, m64x8, u64x8 } +#[cfg(feature = "simd_support")] +simd_impl!(f32, u32); +#[cfg(feature = "simd_support")] +simd_impl!(f64, u64); diff --git a/src/lib.rs b/src/lib.rs index 6d847180..ef5c8a5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] -#![cfg_attr(feature = "simd_support", feature(stdsimd))] +#![cfg_attr(feature = "simd_support", feature(stdsimd, portable_simd))] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( clippy::float_cmp, From d4b8748004ee5c0fa41aa6b430ca701dd982605b Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Mon, 11 Jul 2022 11:29:13 -0700 Subject: [PATCH 19/54] fix simd ints, clarify mask behavior --- src/distributions/integer.rs | 27 +++++++++------------------ src/distributions/other.rs | 2 ++ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index d905044e..c660095b 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -138,31 +138,22 @@ macro_rules! simd_impl { /// Requires nightly Rust and the [`simd_support`] feature /// /// [`simd_support`]: https://github.com/rust-random/rand#crate-features - impl Distribution<$ty> for Standard { + impl Distribution> for Standard + where + LaneCount: SupportedLaneCount, + { #[inline] - fn sample(&self, rng: &mut R) -> $ty { - // TODO: impl this generically once const generics are robust enough - let mut vec: Simd() }> = Default::default(); - rng.fill_bytes(vec.as_mut_array()); - // NOTE: replace with `to_le` if added to core::simd - #[cfg(not(target_endian = "little"))] - { - vec = vec.reverse(); - } - // SAFETY: we know u8xN and $ty have the same size - unsafe { mem::transmute_copy(&vec) } + fn sample(&self, rng: &mut R) -> Simd<$ty, LANES> { + let mut vec = Simd::default(); + rng.fill(vec.as_mut_array().as_mut_slice()); + vec } } )+}; } #[cfg(feature = "simd_support")] -simd_impl!( - i8x4, i8x8, i8x16, i8x32, i8x64, i16x2, i16x4, i16x8, i16x16, i16x32, i32x2, i32x4, i32x8, - i32x16, i64x2, i64x4, i64x8, isizex2, isizex4, isizex8, u8x4, u8x8, u8x16, u8x32, u8x64, u16x2, - u16x4, u16x8, u16x16, u16x32, u32x2, u32x4, u32x8, u32x16, u64x2, u64x4, u64x8, usizex2, - usizex4, usizex8 -); +simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] intrinsic_impl!(__m128i, __m256i); diff --git a/src/distributions/other.rs b/src/distributions/other.rs index b3b5e4e0..b53977e1 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -174,6 +174,8 @@ where { #[inline] fn sample(&self, rng: &mut R) -> Mask { + // `MaskElement` must be a signed integer, so this is equivalent + // to the scalar `i32 < 0` method rng.gen().lanes_lt(Simd::default()) } } From f89f15fc1f846c53de4cd830c997314112dab0e4 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 4 Aug 2022 20:42:35 +0000 Subject: [PATCH 20/54] fix doc link --- src/distributions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 9b0a8867..716536d4 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -221,7 +221,7 @@ use crate::Rng; /// [`Uniform`]: uniform::Uniform /// [`Wrapping`]: std::num::Wrapping /// [`NonZeroU8`]: std::num::NonZeroU8 -/// [`__m128i`]: https://doc.rust-lang.org/nightly/core/arch/x86/struct.__m128i.html +/// [`__m128i`]: https://doc.rust-lang.org/core/arch/x86/struct.__m128i.html /// [`u32x4`]: std::simd::u32x4 /// [`f32x4`]: std::simd::f32x4 /// [`mask32x4`]: std::simd::mask32x4 From 2fab15dcd716fc568c9ca26e75465eef19a1f07e Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Sun, 7 Aug 2022 17:22:19 -0700 Subject: [PATCH 21/54] fix stdsimd, add mask opt notes --- src/distributions/integer.rs | 6 +++--- src/distributions/other.rs | 15 +++++++++++---- src/distributions/uniform.rs | 14 +++++++------- src/distributions/utils.rs | 36 ++++++++++++++++++------------------ 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index c660095b..418eea9f 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -114,7 +114,7 @@ impl_nzint!(NonZeroU64, NonZeroU64::new); impl_nzint!(NonZeroU128, NonZeroU128::new); impl_nzint!(NonZeroUsize, NonZeroUsize::new); -macro_rules! intrinsic_impl { +macro_rules! x86_intrinsic_impl { ($($intrinsic:ident),+) => {$( /// Available only on x86/64 platforms impl Distribution<$intrinsic> for Standard { @@ -156,12 +156,12 @@ macro_rules! simd_impl { simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -intrinsic_impl!(__m128i, __m256i); +x86_intrinsic_impl!(__m128i, __m256i); #[cfg(all( any(target_arch = "x86", target_arch = "x86_64"), feature = "simd_support" ))] -intrinsic_impl!(__m512i); +x86_intrinsic_impl!(__m512i); #[cfg(test)] mod tests { diff --git a/src/distributions/other.rs b/src/distributions/other.rs index b53977e1..cb1b801f 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -23,7 +23,7 @@ use serde::{Serialize, Deserialize}; #[cfg(feature = "min_const_gen")] use core::mem::{self, MaybeUninit}; #[cfg(feature = "simd_support")] -use core::simd::{Mask, Simd, LaneCount, SupportedLaneCount, MaskElement, SimdElement}; +use core::simd::*; // ----- Sampling distributions ----- @@ -161,22 +161,29 @@ impl Distribution for Standard { /// let x = rng.gen::().select(b, a); /// ``` /// -/// Since most bits are unused you could also generate only as many bits as you need. +/// Since most bits are unused you could also generate only as many bits as you need, i.e.: +/// ``` +/// let x = u16x8::splat(rng.gen:: as u16); +/// let mask = u16x8::splat(1) << u16x8::from([0, 1, 2, 3, 4, 5, 6, 7]); +/// let rand_mask = (x & mask).simd_eq(mask); +/// ``` /// /// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] impl Distribution> for Standard where - T: MaskElement + PartialOrd + SimdElement + Default, + T: MaskElement + Default, LaneCount: SupportedLaneCount, Standard: Distribution>, + Simd: SimdPartialOrd>, { #[inline] fn sample(&self, rng: &mut R) -> Mask { // `MaskElement` must be a signed integer, so this is equivalent // to the scalar `i32 < 0` method - rng.gen().lanes_lt(Simd::default()) + let var = rng.gen::>(); + var.simd_lt(Simd::default()) } } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index d9197c68..a7b4cb1a 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -606,7 +606,7 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.lanes_lt(high).all(), "Uniform::new called with `low >= high`"); + assert!(low.simd_lt(high).all(), "Uniform::new called with `low >= high`"); UniformSampler::new_inclusive(low, high - Simd::splat(1)) } @@ -618,7 +618,7 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.lanes_le(high).all(), + assert!(low.simd_le(high).all(), "Uniform::new_inclusive called with `low > high`"); let unsigned_max = Simd::splat(::core::$unsigned::MAX); @@ -626,7 +626,7 @@ macro_rules! uniform_simd_int_impl { // see https://doc.rust-lang.org/std/simd/struct.Simd.html let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); // `% 0` will panic at runtime. - let not_full_range = range.lanes_gt(Simd::splat(0)); + let not_full_range = range.simd_gt(Simd::splat(0)); // replacing 0 with `unsigned_max` allows a faster `select` // with bitwise OR let modulo = not_full_range.select(range, unsigned_max); @@ -660,7 +660,7 @@ macro_rules! uniform_simd_int_impl { let mut v: Simd<$unsigned, LANES> = rng.gen(); loop { let (hi, lo) = v.wmul(range); - let mask = lo.lanes_le(zone); + let mask = lo.simd_le(zone); if mask.all() { let hi: Simd<$ty, LANES> = hi.cast(); // wrapping addition @@ -669,7 +669,7 @@ macro_rules! uniform_simd_int_impl { // When `range.eq(0).none()` the compare and blend // operations are avoided. let v: Simd<$ty, LANES> = v.cast(); - return range.lanes_gt(Simd::splat(0)).select(result, v); + return range.simd_gt(Simd::splat(0)).select(result, v); } // Replace only the failing lanes v = mask.select(v, rng.gen()); @@ -1265,8 +1265,8 @@ mod tests { ($ty::splat(10), $ty::splat(127)), ($ty::splat($scalar::MIN), $ty::splat($scalar::MAX)), ], - |x: $ty, y| x.lanes_le(y).all(), - |x: $ty, y| x.lanes_lt(y).all() + |x: $ty, y| x.simd_le(y).all(), + |x: $ty, y| x.simd_lt(y).all() );)* }}; } diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 689a4a1d..89575646 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -99,20 +99,20 @@ macro_rules! wmul_impl_large { #[inline(always)] fn wmul(self, b: $ty) -> Self::Output { // needs wrapping multiplication - const LOWER_MASK: $ty = <$ty>::splat(!0 >> $half); - const HALF: $ty = <$ty>::splat($half); - let mut low = (self & LOWER_MASK) * (b & LOWER_MASK); - let mut t = low >> HALF; - low &= LOWER_MASK; - t += (self >> HALF) * (b & LOWER_MASK); - low += (t & LOWER_MASK) << HALF; - let mut high = t >> HALF; - t = low >> HALF; - low &= LOWER_MASK; - t += (b >> HALF) * (self & LOWER_MASK); - low += (t & LOWER_MASK) << HALF; - high += t >> HALF; - high += (self >> HALF) * (b >> HALF); + let lower_mask = <$ty>::splat(!0 >> $half); + let half = <$ty>::splat($half); + let mut low = (self & lower_mask) * (b & lower_mask); + let mut t = low >> half; + low &= lower_mask; + t += (self >> half) * (b & lower_mask); + low += (t & lower_mask) << half; + let mut high = t >> half; + t = low >> half; + low &= lower_mask; + t += (b >> half) * (self & lower_mask); + low += (t & lower_mask) << half; + high += t >> half; + high += (self >> half) * (b >> half); (high, low) } @@ -385,12 +385,12 @@ macro_rules! simd_impl { #[inline(always)] fn all_lt(self, other: Self) -> bool { - self.lanes_lt(other).all() + self.simd_lt(other).all() } #[inline(always)] fn all_le(self, other: Self) -> bool { - self.lanes_le(other).all() + self.simd_le(other).all() } #[inline(always)] @@ -405,12 +405,12 @@ macro_rules! simd_impl { #[inline(always)] fn gt_mask(self, other: Self) -> Self::Mask { - self.lanes_gt(other) + self.simd_gt(other) } #[inline(always)] fn ge_mask(self, other: Self) -> Self::Mask { - self.lanes_ge(other) + self.simd_ge(other) } #[inline(always)] From 949d70f6a5bcb74adb28c6d3183c92591122b988 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Sun, 7 Aug 2022 17:42:24 -0700 Subject: [PATCH 22/54] fix doc test --- src/distributions/other.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index cb1b801f..184fce9d 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -163,7 +163,12 @@ impl Distribution for Standard { /// /// Since most bits are unused you could also generate only as many bits as you need, i.e.: /// ``` -/// let x = u16x8::splat(rng.gen:: as u16); +/// #![feature(portable_simd)] +/// use std::simd::*; +/// use rand::prelude::*; +/// let mut rng = thread_rng(); +/// +/// let x = u16x8::splat(rng.gen::() as u16); /// let mask = u16x8::splat(1) << u16x8::from([0, 1, 2, 3, 4, 5, 6, 7]); /// let rand_mask = (x & mask).simd_eq(mask); /// ``` From d60ab387bf60eff36ec462a2519bee69109d6fec Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 11 Aug 2022 05:45:51 +0000 Subject: [PATCH 23/54] optimize simd wmul --- src/distributions/utils.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 89575646..bddb0a4a 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -31,7 +31,7 @@ macro_rules! wmul_impl { }; // simd bulk implementation - ($(($ty:ident, $wide:ident),)+, $shift:expr) => { + ($(($ty:ident, $wide:ty),)+, $shift:expr) => { $( impl WideningMultiply for $ty { type Output = ($ty, $ty); @@ -152,7 +152,8 @@ mod simd_wmul { (u8x4, u16x4), (u8x8, u16x8), (u8x16, u16x16), - (u8x32, u16x32),, + (u8x32, u16x32), + (u8x64, Simd),, 8 } @@ -162,6 +163,8 @@ mod simd_wmul { wmul_impl! { (u16x8, u32x8),, 16 } #[cfg(not(target_feature = "avx2"))] wmul_impl! { (u16x16, u32x16),, 16 } + #[cfg(not(target_feature = "avx512bw"))] + wmul_impl! { (u16x32, Simd),, 16 } // 16-bit lane widths allow use of the x86 `mulhi` instructions, which // means `wmul` can be implemented with only two instructions. @@ -191,15 +194,11 @@ mod simd_wmul { wmul_impl! { (u32x2, u64x2), (u32x4, u64x4), - (u32x8, u64x8),, + (u32x8, u64x8), + (u32x16, Simd),, 32 } - // TODO: optimize, this seems to seriously slow things down - wmul_impl_large! { (u8x64,) u8, 4 } - #[cfg(not(target_feature = "avx512bw"))] - wmul_impl_large! { (u16x32,) u16, 8 } - wmul_impl_large! { (u32x16,) u32, 16 } wmul_impl_large! { (u64x2, u64x4, u64x8,) u64, 32 } } From 2569e9d653e46c54c230366b058a8db08b6701ce Mon Sep 17 00:00:00 2001 From: Pyry Kontio Date: Sun, 14 Aug 2022 16:59:12 +0900 Subject: [PATCH 24/54] Mention disabling getrandom for wasm32-unknown-unknown in README (#1250) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d276dfb2..8c576b4e 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,13 @@ unavailable. ### WASM support -The WASM target `wasm32-unknown-unknown` is not *automatically* supported by -`rand` or `getrandom`. To solve this, either use a different target such as -`wasm32-wasi` or add a direct dependency on `getrandom` with the `js` feature -(if the target supports JavaScript). See +Seeding entropy from OS on WASM target `wasm32-unknown-unknown` is not +*automatically* supported by `rand` or `getrandom`. If you are fine with +seeding the generator manually, you can disable the `getrandom` feature +and use the methods on the `SeedableRng` trait. To enable seeding from OS, +either use a different target such as `wasm32-wasi` or add a direct +dependency on `getrandom` with the `js` feature (if the target supports +JavaScript). See [getrandom#WebAssembly support](https://docs.rs/getrandom/latest/getrandom/#webassembly-support). # License From db80c40c4b0a8e06ef72e50386af72e9ecacc786 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 3 Aug 2022 15:14:37 +0100 Subject: [PATCH 25/54] Bump MSRV to 1.38.0: fixes crossbeam-utils dev-dependency --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa1ff740..fc2b991c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,7 +47,7 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.36.0 # MSRV + toolchain: 1.38.0 # MSRV - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -85,13 +85,13 @@ jobs: cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --examples - name: Test rand (all stable features, non-MSRV) - if: ${{ matrix.toolchain != '1.36.0' }} + if: ${{ matrix.toolchain != '1.38.0' }} run: | cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen - name: Test rand (all stable features, MSRV) - if: ${{ matrix.toolchain == '1.36.0' }} + if: ${{ matrix.toolchain == '1.38.0' }} run: | - # const generics are not stable on 1.36.0 + # const generics are not stable on 1.38.0 cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng - name: Test rand_core run: | From 6951164ec62a126b1b9d24727fa511026ee63383 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 3 Aug 2022 15:22:57 +0100 Subject: [PATCH 26/54] Bump MSRV to 1.51.0: support const generics by default --- .github/workflows/test.yml | 14 ++++---------- Cargo.toml | 4 ---- README.md | 2 -- src/distributions/mod.rs | 4 +--- src/distributions/other.rs | 27 --------------------------- src/rng.rs | 32 +------------------------------- 6 files changed, 6 insertions(+), 77 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc2b991c..1086f094 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: env: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) - run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng,min_const_gen + run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng test: runs-on: ${{ matrix.os }} @@ -47,7 +47,7 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.38.0 # MSRV + toolchain: 1.51.0 # MSRV - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -77,21 +77,15 @@ jobs: cargo test --target ${{ matrix.target }} --all-features cargo test --target ${{ matrix.target }} --benches --features=nightly cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches - cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features min_const_gen + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand run: | cargo test --target ${{ matrix.target }} --lib --tests --no-default-features cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --examples - - name: Test rand (all stable features, non-MSRV) - if: ${{ matrix.toolchain != '1.38.0' }} + - name: Test rand (all stable features) run: | - cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen - - name: Test rand (all stable features, MSRV) - if: ${{ matrix.toolchain == '1.38.0' }} - run: | - # const generics are not stable on 1.38.0 cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng - name: Test rand_core run: | diff --git a/Cargo.toml b/Cargo.toml index d207177a..91c731e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,10 +50,6 @@ std_rng = ["rand_chacha"] # Option: enable SmallRng small_rng = [] -# Option: for rustc ≥ 1.51, enable generating random arrays of any size -# using min-const-generics -min_const_gen = [] - [workspace] members = [ "rand_core", diff --git a/README.md b/README.md index 8c576b4e..4459d755 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,6 @@ Additionally, these features configure Rand: - `nightly` enables some optimizations requiring nightly Rust - `simd_support` (experimental) enables sampling of SIMD values (uniformly random SIMD integers and floats), requiring nightly Rust -- `min_const_gen` enables generating random arrays of - any size using min-const-generics, requiring Rust ≥ 1.51. 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 diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 716536d4..a923f879 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -162,11 +162,9 @@ use crate::Rng; /// compound types where all component types are supported: /// /// * Tuples (up to 12 elements): each element is generated sequentially. -/// * Arrays (up to 32 elements): each element is generated sequentially; +/// * Arrays: each element is generated sequentially; /// see also [`Rng::fill`] which supports arbitrary array length for integer /// and float types and tends to be faster for `u32` and smaller types. -/// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support -/// arrays larger than 32 elements. /// Note that [`Rng::fill`] and `Standard`'s array support are *not* equivalent: /// the former is optimised for integer types (using fewer RNG calls for /// element types smaller than the RNG word size), while the latter supports diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 184fce9d..4cb31086 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -20,7 +20,6 @@ use crate::Rng; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; -#[cfg(feature = "min_const_gen")] use core::mem::{self, MaybeUninit}; #[cfg(feature = "simd_support")] use core::simd::*; @@ -236,8 +235,6 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} -#[cfg(feature = "min_const_gen")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] impl Distribution<[T; N]> for Standard where Standard: Distribution { @@ -253,30 +250,6 @@ where Standard: Distribution } } -#[cfg(not(feature = "min_const_gen"))] -macro_rules! array_impl { - // recursive, given at least one type parameter: - {$n:expr, $t:ident, $($ts:ident,)*} => { - array_impl!{($n - 1), $($ts,)*} - - impl Distribution<[T; $n]> for Standard where Standard: Distribution { - #[inline] - fn sample(&self, _rng: &mut R) -> [T; $n] { - [_rng.gen::<$t>(), $(_rng.gen::<$ts>()),*] - } - } - }; - // empty case: - {$n:expr,} => { - impl Distribution<[T; $n]> for Standard { - fn sample(&self, _rng: &mut R) -> [T; $n] { [] } - } - }; -} - -#[cfg(not(feature = "min_const_gen"))] -array_impl! {32, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,} - impl Distribution> for Standard where Standard: Distribution { diff --git a/src/rng.rs b/src/rng.rs index 79a9fbff..e82f1885 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -68,11 +68,9 @@ pub trait Rng: RngCore { /// /// # Arrays and tuples /// - /// The `rng.gen()` method is able to generate arrays (up to 32 elements) + /// The `rng.gen()` method is able to generate arrays /// and tuples (up to 12 elements), so long as all element types can be /// generated. - /// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support - /// arrays larger than 32 elements. /// /// For arrays of integers, especially for those with small element types /// (< 64 bit), it will likely be faster to instead use [`Rng::fill`]. @@ -392,8 +390,6 @@ macro_rules! impl_fill { impl_fill!(u16, u32, u64, usize, u128,); impl_fill!(i8, i16, i32, i64, isize, i128,); -#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] -#[cfg(feature = "min_const_gen")] impl Fill for [T; N] where [T]: Fill { @@ -402,32 +398,6 @@ where [T]: Fill } } -#[cfg(not(feature = "min_const_gen"))] -macro_rules! impl_fill_arrays { - ($n:expr,) => {}; - ($n:expr, $N:ident) => { - impl Fill for [T; $n] where [T]: Fill { - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { - self[..].try_fill(rng) - } - } - }; - ($n:expr, $N:ident, $($NN:ident,)*) => { - impl_fill_arrays!($n, $N); - impl_fill_arrays!($n - 1, $($NN,)*); - }; - (!div $n:expr,) => {}; - (!div $n:expr, $N:ident, $($NN:ident,)*) => { - impl_fill_arrays!($n, $N); - impl_fill_arrays!(!div $n / 2, $($NN,)*); - }; -} -#[cfg(not(feature = "min_const_gen"))] -#[rustfmt::skip] -impl_fill_arrays!(32, N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,); -#[cfg(not(feature = "min_const_gen"))] -impl_fill_arrays!(!div 4096, N,N,N,N,N,N,N,); - #[cfg(test)] mod test { use super::*; From fbf06ff9198d5da9a5f2964c8947b889022673b5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 5 Aug 2022 09:30:05 +0100 Subject: [PATCH 27/54] Optimized path of sample_efraimidis_spirakis is stable This no longer requires nightly --- Cargo.toml | 2 +- README.md | 2 +- src/seq/index.rs | 78 ++++++++++++++---------------------------------- 3 files changed, 24 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91c731e3..bc39334d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ features = ["small_rng", "serde1"] [features] # Meta-features: default = ["std", "std_rng"] -nightly = [] # enables performance optimizations requiring nightly rust +nightly = [] # some additions requiring nightly Rust serde1 = ["serde", "rand_core/serde1"] # Option (enabled by default): without "std" rand uses libcore; this option diff --git a/README.md b/README.md index 4459d755..40d13534 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Optionally, the following dependencies can be enabled: Additionally, these features configure Rand: - `small_rng` enables inclusion of the `SmallRng` PRNG -- `nightly` enables some optimizations requiring nightly Rust +- `nightly` includes some additions requiring nightly Rust - `simd_support` (experimental) enables sampling of SIMD values (uniformly random SIMD integers and floats), requiring nightly Rust diff --git a/src/seq/index.rs b/src/seq/index.rs index b38e4649..7682facd 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -267,9 +267,7 @@ where R: Rng + ?Sized { /// sometimes be useful to have the indices themselves so this is provided as /// an alternative. /// -/// This implementation uses `O(length + amount)` space and `O(length)` time -/// if the "nightly" feature is enabled, or `O(length)` space and -/// `O(length + amount * log length)` time otherwise. +/// This implementation uses `O(length + amount)` space and `O(length)` time. /// /// Panics if `amount > length`. #[cfg(feature = "std")] @@ -300,9 +298,7 @@ where /// /// This implementation uses the algorithm described by Efraimidis and Spirakis /// in this paper: https://doi.org/10.1016/j.ipl.2005.11.003 -/// It uses `O(length + amount)` space and `O(length)` time if the -/// "nightly" feature is enabled, or `O(length)` space and `O(length -/// + amount * log length)` time otherwise. +/// It uses `O(length + amount)` space and `O(length)` time. /// /// Panics if `amount > length`. #[cfg(feature = "std")] @@ -347,63 +343,33 @@ where } impl Eq for Element {} - #[cfg(feature = "nightly")] - { - let mut candidates = Vec::with_capacity(length.as_usize()); - let mut index = N::zero(); - while index < length { - let weight = weight(index.as_usize()).into(); - if !(weight >= 0.) { - return Err(WeightedError::InvalidWeight); - } - - let key = rng.gen::().powf(1.0 / weight); - candidates.push(Element { index, key }); - - index += N::one(); + let mut candidates = Vec::with_capacity(length.as_usize()); + let mut index = N::zero(); + while index < length { + let weight = weight(index.as_usize()).into(); + if !(weight >= 0.) { + return Err(WeightedError::InvalidWeight); } - // Partially sort the array to find the `amount` elements with the greatest - // keys. Do this by using `select_nth_unstable` to put the elements with - // the *smallest* keys at the beginning of the list in `O(n)` time, which - // provides equivalent information about the elements with the *greatest* keys. - let (_, mid, greater) - = candidates.select_nth_unstable(length.as_usize() - amount.as_usize()); + let key = rng.gen::().powf(1.0 / weight); + candidates.push(Element { index, key }); - let mut result: Vec = Vec::with_capacity(amount.as_usize()); - result.push(mid.index); - for element in greater { - result.push(element.index); - } - Ok(IndexVec::from(result)) + index += N::one(); } - #[cfg(not(feature = "nightly"))] - { - use alloc::collections::BinaryHeap; + // Partially sort the array to find the `amount` elements with the greatest + // keys. Do this by using `select_nth_unstable` to put the elements with + // the *smallest* keys at the beginning of the list in `O(n)` time, which + // provides equivalent information about the elements with the *greatest* keys. + let (_, mid, greater) + = candidates.select_nth_unstable(length.as_usize() - amount.as_usize()); - // Partially sort the array such that the `amount` elements with the largest - // keys are first using a binary max heap. - let mut candidates = BinaryHeap::with_capacity(length.as_usize()); - let mut index = N::zero(); - while index < length { - let weight = weight(index.as_usize()).into(); - if !(weight >= 0.) { - return Err(WeightedError::InvalidWeight); - } - - let key = rng.gen::().powf(1.0 / weight); - candidates.push(Element { index, key }); - - index += N::one(); - } - - let mut result: Vec = Vec::with_capacity(amount.as_usize()); - while result.len() < amount.as_usize() { - result.push(candidates.pop().unwrap().index); - } - Ok(IndexVec::from(result)) + let mut result: Vec = Vec::with_capacity(amount.as_usize()); + result.push(mid.index); + for element in greater { + result.push(element.index); } + Ok(IndexVec::from(result)) } /// Randomly sample exactly `amount` indices from `0..length`, using Floyd's From 89a1336b934c68ddce548127c6f8afd910b35a18 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 15 Sep 2022 14:23:08 +0100 Subject: [PATCH 28/54] rand_core: update CHANGELOG for 0.6.4 (#1253) --- rand_core/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 17482d40..75fcbc66 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,9 +4,10 @@ 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). -## [0.6.4] - 2021-08-20 -### Fixed +## [0.6.4] - 2022-09-15 - Fix unsoundness in `::next_u32` (#1160) +- Reduce use of `unsafe` and improve gen_bytes performance (#1180) +- Add `CryptoRngCore` trait (#1187, #1230) ## [0.6.3] - 2021-06-15 ### Changed From 2b4f00add718c844d7b25b784d055a8efa628314 Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Thu, 15 Sep 2022 23:41:28 -0700 Subject: [PATCH 29/54] Update listed rand_core version number (#1254) --- rand_core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/README.md b/rand_core/README.md index d32dd685..14bd7d55 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -43,7 +43,7 @@ The traits and error types are also available via `rand`. The current version is: ``` -rand_core = "0.6.0" +rand_core = "0.6.4" ``` Rand libs have inter-dependencies and make use of the From 766c7eccd73a1f2768f7ce2a4469005a65f7f9a2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 30 Sep 2022 09:48:10 +0100 Subject: [PATCH 30/54] Doc: improve random, thread_rng, ThreadRng docs (#1257) * Use a custom Debug impl for ThreadRng * Adjust documentation of random, thread_rng, ThreadRng * Fix no-std build * Compatibility with older rustc --- rand_core/src/os.rs | 5 ++-- src/lib.rs | 33 +++-------------------- src/rng.rs | 2 +- src/rngs/thread.rs | 64 +++++++++++++++++++++++++++++++++------------ 4 files changed, 55 insertions(+), 49 deletions(-) diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 6cd1b9cf..b43c9fda 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -19,8 +19,9 @@ use getrandom::getrandom; /// The implementation is provided by the [getrandom] crate. Refer to /// [getrandom] documentation for details. /// -/// This struct is only available when specifying the crate feature `getrandom` -/// or `std`. When using the `rand` lib, it is also available as `rand::rngs::OsRng`. +/// This struct is available as `rand_core::OsRng` and as `rand::rngs::OsRng`. +/// In both cases, this requires the crate feature `getrandom` or `std` +/// (enabled by default in `rand` but not in `rand_core`). /// /// # Blocking and error handling /// diff --git a/src/lib.rs b/src/lib.rs index ef5c8a5a..755b5ba6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,36 +110,10 @@ use crate::distributions::{Distribution, Standard}; /// Generates a random value using the thread-local random number generator. /// -/// This is simply a shortcut for `thread_rng().gen()`. See [`thread_rng`] for -/// documentation of the entropy source and [`Standard`] for documentation of -/// distributions and type-specific generation. +/// This function is simply a shortcut for `thread_rng().gen()`: /// -/// # Provided implementations -/// -/// The following types have provided implementations that -/// generate values with the following ranges and distributions: -/// -/// * Integers (`i32`, `u32`, `isize`, `usize`, etc.): Uniformly distributed -/// over all values of the type. -/// * `char`: Uniformly distributed over all Unicode scalar values, i.e. all -/// code points in the range `0...0x10_FFFF`, except for the range -/// `0xD800...0xDFFF` (the surrogate code points). This includes -/// unassigned/reserved code points. -/// * `bool`: Generates `false` or `true`, each with probability 0.5. -/// * Floating point types (`f32` and `f64`): Uniformly distributed in the -/// half-open range `[0, 1)`. See notes below. -/// * Wrapping integers (`Wrapping`), besides the type identical to their -/// normal integer variants. -/// -/// Also supported is the generation of the following -/// compound types where all component types are supported: -/// -/// * Tuples (up to 12 elements): each element is generated sequentially. -/// * Arrays (up to 32 elements): each element is generated sequentially; -/// see also [`Rng::fill`] which supports arbitrary array length for integer -/// types and tends to be faster for `u32` and smaller types. -/// * `Option` first generates a `bool`, and if true generates and returns -/// `Some(value)` where `value: T`, otherwise returning `None`. +/// - See [`ThreadRng`] for documentation of the generator and security +/// - See [`Standard`] for documentation of supported types and distributions /// /// # Examples /// @@ -177,6 +151,7 @@ use crate::distributions::{Distribution, Standard}; /// ``` /// /// [`Standard`]: distributions::Standard +/// [`ThreadRng`]: rngs::ThreadRng #[cfg(all(feature = "std", feature = "std_rng"))] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] #[inline] diff --git a/src/rng.rs b/src/rng.rs index e82f1885..c9f3a5f7 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -53,7 +53,7 @@ use core::{mem, slice}; /// # let v = foo(&mut thread_rng()); /// ``` pub trait Rng: RngCore { - /// Return a random value supporting the [`Standard`] distribution. + /// Return a random value via the [`Standard`] distribution. /// /// # Example /// diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index baebb1d9..673c9a87 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -11,6 +11,7 @@ use core::cell::UnsafeCell; use std::rc::Rc; use std::thread_local; +use std::fmt; use super::std::Core; use crate::rngs::adapter::ReseedingRng; @@ -39,31 +40,43 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// +/// This type is a reference to a lazily-initialized thread-local generator. /// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`. /// This handle is safe to use everywhere (including thread-local destructors), /// though it is recommended not to use inside a fork handler. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// -/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance -/// and is automatically seeded from [`OsRng`]. +/// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with +/// [`StdRng`], the algorithm may be changed, subject to reasonable expectations +/// of security and performance. /// -/// 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. +/// `ThreadRng` is automatically seeded from [`OsRng`] with periodic reseeding +/// (every 64 kiB, as well as "soon" after a fork on Unix — see [`ReseedingRng`] +/// documentation for details). +/// +/// Security must be considered relative to a thread model and validation +/// requirements. `ThreadRng` attempts to meet basic security considerations +/// for producing unpredictable random numbers: use a CSPRNG, use a +/// recommended platform-specific seed ([`OsRng`]), and avoid +/// leaking internal secrets e.g. via [`Debug`] implementation or serialization. +/// Memory is not zeroized on drop. /// /// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync rng: Rc>>, } +/// Debug implementation does not leak internal state +impl fmt::Debug for ThreadRng { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "ThreadRng {{ .. }}") + } +} + thread_local!( // We require Rc<..> to avoid premature freeing when thread_rng is used // within thread-local destructors. See #968. @@ -77,13 +90,23 @@ thread_local!( } ); -/// Retrieve the lazily-initialized thread-local random number generator, -/// seeded by the system. Intended to be used in method chaining style, -/// e.g. `thread_rng().gen::()`, or cached locally, e.g. -/// `let mut rng = thread_rng();`. Invoked by the `Default` trait, making -/// `ThreadRng::default()` equivalent. +/// Access the thread-local generator /// -/// For more information see [`ThreadRng`]. +/// Returns a reference to the local [`ThreadRng`], initializing the generator +/// on the first call on each thread. +/// +/// Example usage: +/// ``` +/// use rand::Rng; +/// +/// # fn main() { +/// // rand::random() may be used instead of rand::thread_rng().gen(): +/// println!("A random boolean: {}", rand::random::()); +/// +/// let mut rng = rand::thread_rng(); +/// println!("A simulated die roll: {}", rng.gen_range(1..=6)); +/// # } +/// ``` #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] pub fn thread_rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); @@ -92,7 +115,7 @@ pub fn thread_rng() -> ThreadRng { impl Default for ThreadRng { fn default() -> ThreadRng { - crate::prelude::thread_rng() + thread_rng() } } @@ -140,4 +163,11 @@ mod test { r.gen::(); assert_eq!(r.gen_range(0..1), 0); } + + #[test] + fn test_debug_output() { + // We don't care about the exact output here, but it must not include + // private CSPRNG state or the cache stored by BlockRng! + assert_eq!(std::format!("{:?}", crate::thread_rng()), "ThreadRng { .. }"); + } } From 8d70f5017f870b2925309f91c8117106c2c1bdd2 Mon Sep 17 00:00:00 2001 From: ISibboI Date: Mon, 10 Oct 2022 15:40:37 +0200 Subject: [PATCH 31/54] Clarify documentation of `choose_weighted(_mut)` mentioning accurate behavior with floats (#1245) * Fix the documentation for `choose_weighted(_mut)` as discussed in #1243. * Mention that elements of zero weight are handled as expected by `WeightedIndex` as discussed in #1243. Additionally fix some minor issues. * Let the second example of `WeightedIndex` use floats to stress that they are handled correctly for the zero case. * Manually indent doc comments. --- src/distributions/weighted_index.rs | 12 +++++++----- src/seq/mod.rs | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 8252b172..2af2446d 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -25,8 +25,10 @@ use serde::{Serialize, Deserialize}; /// Sampling a `WeightedIndex` distribution returns the index of a randomly /// selected element from the iterator used when the `WeightedIndex` was /// created. The chance of a given element being picked is proportional to the -/// value of the element. The weights can use any type `X` for which an -/// implementation of [`Uniform`] exists. +/// weight of the element. The weights can use any type `X` for which an +/// implementation of [`Uniform`] exists. The implementation guarantees that +/// elements with zero weight are never picked, even when the weights are +/// floating point numbers. /// /// # Performance /// @@ -42,8 +44,8 @@ use serde::{Serialize, Deserialize}; /// weights of type `X`, where `N` is the number of weights. However, since /// `Vec` doesn't guarantee a particular growth strategy, additional memory /// might be allocated but not used. Since the `WeightedIndex` object also -/// contains, this might cause additional allocations, though for primitive -/// types, [`Uniform`] doesn't allocate any memory. +/// contains an instance of `X::Sampler`, this might cause additional allocations, +/// though for primitive types, [`Uniform`] doesn't allocate any memory. /// /// Sampling from `WeightedIndex` will result in a single call to /// `Uniform::sample` (method of the [`Distribution`] trait), which typically @@ -65,7 +67,7 @@ use serde::{Serialize, Deserialize}; /// println!("{}", choices[dist.sample(&mut rng)]); /// } /// -/// let items = [('a', 0), ('b', 3), ('c', 7)]; +/// let items = [('a', 0.0), ('b', 3.0), ('c', 7.0)]; /// let dist2 = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap(); /// for _ in 0..100 { /// // 0% chance to print 'a', 30% chance to print 'b', 70% chance to print 'c' diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 069e9e6b..a03d8241 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -123,21 +123,25 @@ pub trait SliceRandom { /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. /// /// For slices of length `n`, complexity is `O(n)`. - /// See also [`choose_weighted_mut`], [`distributions::weighted`]. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted_mut`]. /// /// # Example /// /// ``` /// use rand::prelude::*; /// - /// let choices = [('a', 2), ('b', 1), ('c', 1)]; + /// let choices = [('a', 2), ('b', 1), ('c', 1), ('d', 0)]; /// let mut rng = thread_rng(); - /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c' + /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c', + /// // and 'd' will never be printed /// println!("{:?}", choices.choose_weighted(&mut rng, |item| item.1).unwrap().0); /// ``` /// [`choose`]: SliceRandom::choose /// [`choose_weighted_mut`]: SliceRandom::choose_weighted_mut - /// [`distributions::weighted`]: crate::distributions::weighted + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted( @@ -161,11 +165,14 @@ pub trait SliceRandom { /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. /// /// For slices of length `n`, complexity is `O(n)`. - /// See also [`choose_weighted`], [`distributions::weighted`]. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted`]. /// /// [`choose_mut`]: SliceRandom::choose_mut /// [`choose_weighted`]: SliceRandom::choose_weighted - /// [`distributions::weighted`]: crate::distributions::weighted + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted_mut( From 23f8b2986e2e7a74cf07ff77122a051ad51bd722 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 11 Oct 2022 10:07:15 +0200 Subject: [PATCH 32/54] clarify shuffle docs (#1259) --- src/seq/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index a03d8241..edff51fe 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -235,6 +235,7 @@ pub trait SliceRandom { /// Shuffle a mutable slice in place. /// /// For slices of length `n`, complexity is `O(n)`. + /// The resulting permutation is picked uniformly from the set of all possible permutations. /// /// # Example /// From 387dd644a24dd7a675f0452f27aedddf9e65396a Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Wed, 9 Nov 2022 06:50:09 -0800 Subject: [PATCH 33/54] fix outdated choose_multiple_weighted docs (#1237) --- src/seq/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index edff51fe..420ef253 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -199,10 +199,9 @@ pub trait SliceRandom { /// If all of the weights are equal, even if they are all zero, each element has /// an equal likelihood of being selected. /// - /// The complexity of this method depends on the feature `partition_at_index`. - /// If the feature is enabled, then for slices of length `n`, the complexity - /// is `O(n)` space and `O(n)` time. Otherwise, the complexity is `O(n)` space and - /// `O(n * log amount)` time. + /// This implementation uses `O(length + amount)` space and `O(length)` time + /// if the "nightly" feature is enabled, or `O(length)` space and + /// `O(length + amount * log length)` time otherwise. /// /// # Example /// From 9720f110a69659f05a9f84a38346b570e63a9c56 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 10 Nov 2022 15:36:40 +0000 Subject: [PATCH 34/54] Update GitHub Actions (#1263) * examples/rayon-monte-carlo.rs: remove execute bit of file metadata * Document in README that MSRV is 1.51.0 This was changed in #1246 * Update GitHub Actions --- .github/workflows/gh-pages.yml | 20 ++++++++-------- .github/workflows/test.yml | 44 +++++++++++----------------------- README.md | 14 +++-------- examples/rayon-monte-carlo.rs | 0 rand_chacha/README.md | 2 +- rand_core/README.md | 2 +- rand_distr/README.md | 2 +- rand_pcg/README.md | 2 +- 8 files changed, 31 insertions(+), 55 deletions(-) mode change 100755 => 100644 examples/rayon-monte-carlo.rs diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 80c0ec3d..a86271a2 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -10,13 +10,9 @@ jobs: name: GH-pages documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@nightly - name: doc (rand) env: RUSTDOCFLAGS: --cfg doc_cfg @@ -24,8 +20,12 @@ jobs: run: | cargo doc --all --features nightly,serde1,getrandom,small_rng cp utils/redirect.html target/doc/index.html - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + - name: Setup Pages + uses: actions/configure-pages@v2 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc + path: './target/doc' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1086f094..7d64a535 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,13 +11,9 @@ jobs: name: Check doc runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@nightly - run: cargo install cargo-deadlinks - name: doc (rand) env: @@ -58,14 +54,12 @@ jobs: variant: minimal_versions steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal target: ${{ matrix.target }} toolchain: ${{ matrix.toolchain }} - override: true - run: ${{ matrix.deps }} - name: Maybe minimal versions if: ${{ matrix.variant == 'minimal_versions' }} @@ -113,16 +107,14 @@ jobs: toolchain: stable steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal target: ${{ matrix.target }} toolchain: ${{ matrix.toolchain }} - override: true - name: Cache cargo plugins - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.cargo/bin/ key: ${{ runner.os }}-cargo-plugins @@ -141,7 +133,7 @@ jobs: test-miri: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain run: | rustup toolchain install nightly --component miri @@ -161,41 +153,33 @@ jobs: test-no-std: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly target: thumbv6m-none-eabi - override: true - name: Build top-level only run: cargo build --target=thumbv6m-none-eabi --no-default-features test-avr: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: nightly-2021-01-07 # Pinned compiler version due to https://github.com/rust-lang/compiler-builtins/issues/400 components: rust-src - override: true - name: Build top-level only run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features test-ios: runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly target: aarch64-apple-ios - override: true - name: Build top-level only run: cargo build --target=aarch64-apple-ios diff --git a/README.md b/README.md index 40d13534..0952d8c6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -95,17 +95,9 @@ Some versions of Rand crates have been yanked ("unreleased"). Where this occurs, the crate's CHANGELOG *should* be updated with a rationale, and a search on the issue tracker with the keyword `yank` *should* uncover the motivation. -### Rust version requirements +### Rust version requirements (MSRV) -Since version 0.8, Rand requires **Rustc version 1.36 or greater**. -Rand 0.7 requires Rustc 1.32 or greater while versions 0.5 require Rustc 1.22 or -greater, and 0.4 and 0.3 (since approx. June 2017) require Rustc version 1.15 or -greater. Subsets of the Rand code may work with older Rust versions, but this is -not supported. - -Continuous Integration (CI) will always test the minimum supported Rustc version -(the MSRV). The current policy is that this can be updated in any -Rand release if required, but the change must be noted in the changelog. +This version of Rand requires Rustc >= 1.51.0. ## Crate Features diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs old mode 100755 new mode 100644 diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 1a6920d9..df52ab44 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/README.md b/rand_core/README.md index 14bd7d55..a7362d19 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_distr/README.md b/rand_distr/README.md index 3fc2ea62..9096e918 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_pcg/README.md b/rand_pcg/README.md index 736a7890..d2c9259f 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. From 8339afc7ee3fdd06adb92b6e77e4507111e814a8 Mon Sep 17 00:00:00 2001 From: Yaron Sheffer Date: Mon, 14 Nov 2022 11:05:54 +0000 Subject: [PATCH 35/54] Fix typo (#1264) --- src/rngs/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 673c9a87..78cecde5 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -54,7 +54,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// (every 64 kiB, as well as "soon" after a fork on Unix — see [`ReseedingRng`] /// documentation for details). /// -/// Security must be considered relative to a thread model and validation +/// Security must be considered relative to a threat model and validation /// requirements. `ThreadRng` attempts to meet basic security considerations /// for producing unpredictable random numbers: use a CSPRNG, use a /// recommended platform-specific seed ([`OsRng`]), and avoid From 21131af61d51cab98da583cf46903f38041670cc Mon Sep 17 00:00:00 2001 From: Frank Steffahn Date: Mon, 14 Nov 2022 20:37:49 +0900 Subject: [PATCH 36/54] Remove redundant AsRef/AsMut bounds (#1207) --- rand_core/src/block.rs | 10 ++-------- src/rngs/adapter/reseeding.rs | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index d311b68c..a527dda2 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -178,10 +178,7 @@ impl BlockRng { } } -impl> RngCore for BlockRng -where - ::Results: AsRef<[u32]> + AsMut<[u32]>, -{ +impl> RngCore for BlockRng { #[inline] fn next_u32(&mut self) -> u32 { if self.index >= self.results.as_ref().len() { @@ -346,10 +343,7 @@ impl BlockRng64 { } } -impl> RngCore for BlockRng64 -where - ::Results: AsRef<[u64]> + AsMut<[u64]>, -{ +impl> RngCore for BlockRng64 { #[inline] fn next_u32(&mut self) -> u32 { let mut index = self.index - self.half_used as usize; diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index ae3fcbb2..5ab453c9 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -113,7 +113,6 @@ where impl RngCore for ReseedingRng where R: BlockRngCore + SeedableRng, - ::Results: AsRef<[u32]> + AsMut<[u32]>, { #[inline(always)] fn next_u32(&mut self) -> u32 { From 0aca9028f20fbc7db8fe1ecd57ca472fd1a5dcb3 Mon Sep 17 00:00:00 2001 From: ironhaven Date: Mon, 14 Nov 2022 06:08:30 -0600 Subject: [PATCH 37/54] SmallRng uses wrong seed_from_u64 implementation (#1203) * Forward inner seed_from_u64 implmentation for SmallRng * increase tolerance of sparkline tests --- rand_distr/tests/pdf.rs | 8 ++++---- src/rngs/small.rs | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index eb766142..14db1815 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -89,8 +89,8 @@ fn normal() { .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { - // Difference larger than 3 standard deviations or cutoff - let tol = (3. * e).max(1e-4); + // Difference larger than 4 standard deviations or cutoff + let tol = (4. * e).max(1e-4); assert!(d <= tol, "Difference = {} * tol", d / tol); } } @@ -172,8 +172,8 @@ fn skew_normal() { .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { - // Difference larger than 3 standard deviations or cutoff - let tol = (3. * e).max(1e-4); + // Difference larger than 4 standard deviations or cutoff + let tol = (4. * e).max(1e-4); assert!(d <= tol, "Difference = {} * tol", d / tol); } } diff --git a/src/rngs/small.rs b/src/rngs/small.rs index fb0e0d11..a3261757 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -114,4 +114,9 @@ impl SeedableRng for SmallRng { fn from_rng(rng: R) -> Result { Rng::from_rng(rng).map(SmallRng) } + + #[inline(always)] + fn seed_from_u64(state: u64) -> Self { + SmallRng(Rng::seed_from_u64(state)) + } } From 7aa25d577e2df84a5156f824077bb7f6bdf28d97 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Nov 2022 16:41:49 +0000 Subject: [PATCH 38/54] gh-pages action: add id-token write permission (#1265) * gh-pages action: add id-token write permission * gh-pages action: add environment --- .github/workflows/gh-pages.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index a86271a2..f751f8c5 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,5 +1,10 @@ name: gh-pages +permissions: + contents: read + pages: write + id-token: write + on: push: branches: @@ -9,23 +14,33 @@ jobs: deploy: name: GH-pages documentation runs-on: ubuntu-latest + environment: + name: github-pages + url: https://rust-random.github.io/rand/ + steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 + - name: Install toolchain uses: dtolnay/rust-toolchain@nightly - - name: doc (rand) + + - name: Build docs env: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) run: | cargo doc --all --features nightly,serde1,getrandom,small_rng cp utils/redirect.html target/doc/index.html + - name: Setup Pages uses: actions/configure-pages@v2 + - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: './target/doc' + - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 From 19169cbce9931eea5ccb4f2cbf174fc9d3e8759d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 6 Dec 2022 19:01:10 +0000 Subject: [PATCH 39/54] Bump MSRV to 1.56 (Edition 2021) (#1269) * Bump MSRV to 1.56 (Edition 2021) * Apply Clippy suggestions * Bump edition and add rust-version field to Cargo.toml * CI AVR test: unpin nightly rust version * Disable AVR test * Bump crate version numbers for a breaking release --- .github/workflows/test.yml | 24 ++++++++++++------------ Cargo.toml | 11 ++++++----- README.md | 7 ++++--- rand_chacha/Cargo.toml | 7 ++++--- rand_chacha/README.md | 2 +- rand_core/Cargo.toml | 5 +++-- rand_core/README.md | 2 +- rand_core/src/block.rs | 4 ++-- rand_distr/Cargo.toml | 11 ++++++----- rand_distr/README.md | 2 +- rand_distr/benches/Cargo.toml | 5 +++-- rand_pcg/Cargo.toml | 7 ++++--- rand_pcg/README.md | 2 +- rustfmt.toml | 2 +- src/distributions/uniform.rs | 2 +- src/rngs/adapter/reseeding.rs | 4 ++-- src/seq/index.rs | 4 ++-- src/seq/mod.rs | 2 +- 18 files changed, 55 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d64a535..50dd5e86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.51.0 # MSRV + toolchain: 1.56.0 # MSRV - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -161,17 +161,17 @@ jobs: - name: Build top-level only run: cargo build --target=thumbv6m-none-eabi --no-default-features - test-avr: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2021-01-07 # Pinned compiler version due to https://github.com/rust-lang/compiler-builtins/issues/400 - components: rust-src - - name: Build top-level only - run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features + # Disabled due to lack of known working compiler versions (not older than our MSRV) + # test-avr: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - name: Install toolchain + # uses: dtolnay/rust-toolchain@nightly + # with: + # components: rust-src + # - name: Build top-level only + # run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features test-ios: runs-on: macos-latest diff --git a/Cargo.toml b/Cargo.toml index bc39334d..71b430f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.5" +version = "0.9.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -13,7 +13,8 @@ Random number generators and other randomness functionality. keywords = ["random", "rng"] categories = ["algorithms", "no-std"] autobenches = true -edition = "2018" +edition = "2021" +rust-version = "1.56" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] @@ -59,17 +60,17 @@ members = [ ] [dependencies] -rand_core = { path = "rand_core", version = "0.6.0" } +rand_core = { path = "rand_core", version = "0.7.0" } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "0.4.0", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) libc = { version = "0.2.22", optional = true, default-features = false } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "0.3.0" } +rand_pcg = { path = "rand_pcg", version = "0.4.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" diff --git a/README.md b/README.md index 0952d8c6..c4704f3d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -95,9 +95,10 @@ Some versions of Rand crates have been yanked ("unreleased"). Where this occurs, the crate's CHANGELOG *should* be updated with a rationale, and a search on the issue tracker with the keyword `yank` *should* uncover the motivation. -### Rust version requirements (MSRV) +### Rust version requirements -This version of Rand requires Rustc >= 1.51.0. +The Minimum Supported Rust Version (MSRV) is `rustc >= 1.56.0`. +Older releases may work (depending on feature configuration) but are untested. ## Crate Features diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index c4f5c113..7584b788 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.3.1" +version = "0.4.0" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,10 +12,11 @@ ChaCha random number generator """ keywords = ["random", "rng", "chacha"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" [dependencies] -rand_core = { path = "../rand_core", version = "0.6.0" } +rand_core = { path = "../rand_core", version = "0.7.0" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/rand_chacha/README.md b/rand_chacha/README.md index df52ab44..851490e2 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index bfaa029b..a3640068 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.6.4" +version = "0.7.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,7 +12,8 @@ Core random number generator traits and tools for implementation. """ keywords = ["random", "rng"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" [package.metadata.docs.rs] # To build locally: diff --git a/rand_core/README.md b/rand_core/README.md index a7362d19..4174ff19 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index a527dda2..5a986895 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -381,13 +381,13 @@ impl> RngCore for BlockRng64 { let mut read_len = 0; self.half_used = false; while read_len < dest.len() { - if self.index as usize >= self.results.as_ref().len() { + if self.index >= self.results.as_ref().len() { self.core.generate(&mut self.results); self.index = 0; } let (consumed_u64, filled_u8) = fill_via_u64_chunks( - &self.results.as_ref()[self.index as usize..], + &self.results.as_ref()[self.index..], &mut dest[read_len..], ); diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 32a5fcaf..ff1400d6 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.4.3" +version = "0.5.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,7 +12,8 @@ Sampling from random number distributions """ keywords = ["random", "rng", "distribution", "probability"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [features] @@ -23,14 +24,14 @@ std_math = ["num-traits/std"] serde1 = ["serde", "rand/serde1"] [dependencies] -rand = { path = "..", version = "0.8.0", default-features = false } +rand = { path = "..", version = "0.9.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } [dev-dependencies] -rand_pcg = { version = "0.3.0", path = "../rand_pcg" } +rand_pcg = { version = "0.4.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std", "small_rng"] } +rand = { path = "..", version = "0.9.0", default-features = false, features = ["std_rng", "std", "small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_distr/README.md b/rand_distr/README.md index 9096e918..d11a3744 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_distr/benches/Cargo.toml b/rand_distr/benches/Cargo.toml index 093286d5..aeba667b 100644 --- a/rand_distr/benches/Cargo.toml +++ b/rand_distr/benches/Cargo.toml @@ -4,7 +4,8 @@ version = "0.0.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "Criterion benchmarks of the rand_distr crate" -edition = "2018" +edition = "2021" +rust-version = "1.56" publish = false [workspace] @@ -19,4 +20,4 @@ rand_pcg = { path = "../../rand_pcg/" } [[bench]] name = "distributions" path = "src/distributions.rs" -harness = false \ No newline at end of file +harness = false diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 8ef7a3b5..80099769 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.3.1" +version = "0.4.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,13 +12,14 @@ Selected PCG random number generators """ keywords = ["random", "rng", "pcg"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" [features] serde1 = ["serde"] [dependencies] -rand_core = { path = "../rand_core", version = "0.6.0" } +rand_core = { path = "../rand_core", version = "0.7.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] diff --git a/rand_pcg/README.md b/rand_pcg/README.md index d2c9259f..ce6d1f37 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. diff --git a/rustfmt.toml b/rustfmt.toml index 6a2d9d48..ded1e781 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -19,7 +19,7 @@ where_single_line = true # struct_field_align_threshold = 20 # Compatibility: -edition = "2018" # we require compatibility back to 1.32.0 +edition = "2021" # Misc: inline_attribute_width = 80 diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index a7b4cb1a..49703b84 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -340,7 +340,7 @@ where Borrowed: SampleUniform { #[inline(always)] fn borrow(&self) -> &Borrowed { - *self + self } } diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 5ab453c9..b78b850a 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -208,8 +208,8 @@ where ReseedingCore { inner: rng, reseeder, - threshold: threshold as i64, - bytes_until_reseed: threshold as i64, + threshold, + bytes_until_reseed: threshold, fork_counter: 0, } } diff --git a/src/seq/index.rs b/src/seq/index.rs index 7682facd..ecd7d9f7 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -238,7 +238,7 @@ where R: Rng + ?Sized { if amount < 163 { const C: [[f32; 2]; 2] = [[1.6, 8.0 / 45.0], [10.0, 70.0 / 9.0]]; - let j = if length < 500_000 { 0 } else { 1 }; + let j = usize::from(length >= 500_000); let amount_fp = amount as f32; let m4 = C[0][j] * amount_fp; // Short-cut: when amount < 12, floyd's is always faster @@ -249,7 +249,7 @@ where R: Rng + ?Sized { } } else { const C: [f32; 2] = [270.0, 330.0 / 9.0]; - let j = if length < 500_000 { 0 } else { 1 }; + let j = usize::from(length >= 500_000); if (length as f32) < C[j] * (amount as f32) { sample_inplace(rng, length, amount) } else { diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 420ef253..24c65bc9 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -641,7 +641,7 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceCho fn next(&mut self) -> Option { // TODO: investigate using SliceIndex::get_unchecked when stable - self.indices.next().map(|i| &self.slice[i as usize]) + self.indices.next().map(|i| &self.slice[i]) } fn size_hint(&self) -> (usize, Option) { From 50b9a447410860af8d6db9a208c3576886955874 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 7 Dec 2022 09:47:45 +0000 Subject: [PATCH 40/54] fill_via_chunks: mutate src on BE (small optimisation) (#1182) * fill_via_chunks: mutate src on BE (small optimisation) * Add doc to fill_via_chunks --- rand_core/src/block.rs | 4 +-- rand_core/src/impls.rs | 77 ++++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 5a986895..f813784f 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -223,7 +223,7 @@ impl> RngCore for BlockRng { self.generate_and_set(0); } let (consumed_u32, filled_u8) = - fill_via_u32_chunks(&self.results.as_ref()[self.index..], &mut dest[read_len..]); + fill_via_u32_chunks(&mut self.results.as_mut()[self.index..], &mut dest[read_len..]); self.index += consumed_u32; read_len += filled_u8; @@ -387,7 +387,7 @@ impl> RngCore for BlockRng64 { } let (consumed_u64, filled_u8) = fill_via_u64_chunks( - &self.results.as_ref()[self.index..], + &mut self.results.as_mut()[self.index..], &mut dest[read_len..], ); diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 4b7688c5..8f99ef81 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -53,16 +53,14 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } trait Observable: Copy { - type Bytes: AsRef<[u8]>; - fn to_le_bytes(self) -> Self::Bytes; + fn to_le(self) -> Self; // Contract: observing self is memory-safe (implies no uninitialised padding) fn as_byte_slice(x: &[Self]) -> &[u8]; } impl Observable for u32 { - type Bytes = [u8; 4]; - fn to_le_bytes(self) -> Self::Bytes { - self.to_le_bytes() + fn to_le(self) -> Self { + self.to_le() } fn as_byte_slice(x: &[Self]) -> &[u8] { let ptr = x.as_ptr() as *const u8; @@ -71,9 +69,8 @@ impl Observable for u32 { } } impl Observable for u64 { - type Bytes = [u8; 8]; - fn to_le_bytes(self) -> Self::Bytes { - self.to_le_bytes() + fn to_le(self) -> Self { + self.to_le() } fn as_byte_slice(x: &[Self]) -> &[u8] { let ptr = x.as_ptr() as *const u8; @@ -82,28 +79,27 @@ impl Observable for u64 { } } -fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { +/// Fill dest from src +/// +/// Returns `(n, byte_len)`. `src[..n]` is consumed (and possibly mutated), +/// `dest[..byte_len]` is filled. `src[n..]` and `dest[byte_len..]` are left +/// unaltered. +fn fill_via_chunks(src: &mut [T], dest: &mut [u8]) -> (usize, usize) { let size = core::mem::size_of::(); let byte_len = min(src.len() * size, dest.len()); let num_chunks = (byte_len + size - 1) / size; - if cfg!(target_endian = "little") { - // On LE we can do a simple copy, which is 25-50% faster: - dest[..byte_len].copy_from_slice(&T::as_byte_slice(&src[..num_chunks])[..byte_len]); - } else { - // This code is valid on all arches, but slower than the above: - let mut i = 0; - let mut iter = dest[..byte_len].chunks_exact_mut(size); - for chunk in &mut iter { - chunk.copy_from_slice(src[i].to_le_bytes().as_ref()); - i += 1; - } - let chunk = iter.into_remainder(); - if !chunk.is_empty() { - chunk.copy_from_slice(&src[i].to_le_bytes().as_ref()[..chunk.len()]); + // Byte-swap for portability of results. This must happen before copying + // since the size of dest is not guaranteed to be a multiple of T or to be + // sufficiently aligned. + if cfg!(target_endian = "big") { + for x in &mut src[..num_chunks] { + *x = x.to_le(); } } + dest[..byte_len].copy_from_slice(&T::as_byte_slice(&src[..num_chunks])[..byte_len]); + (num_chunks, byte_len) } @@ -112,6 +108,9 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) /// /// The return values are `(consumed_u32, filled_u8)`. /// +/// On big-endian systems, endianness of `src[..consumed_u32]` values is +/// swapped. No other adjustments to `src` are made. +/// /// `filled_u8` is the number of filled bytes in `dest`, which may be less than /// the length of `dest`. /// `consumed_u32` is the number of words consumed from `src`, which is the same @@ -137,7 +136,7 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) /// } /// } /// ``` -pub fn fill_via_u32_chunks(src: &[u32], dest: &mut [u8]) -> (usize, usize) { +pub fn fill_via_u32_chunks(src: &mut [u32], dest: &mut [u8]) -> (usize, usize) { fill_via_chunks(src, dest) } @@ -145,13 +144,17 @@ pub fn fill_via_u32_chunks(src: &[u32], dest: &mut [u8]) -> (usize, usize) { /// based RNG. /// /// The return values are `(consumed_u64, filled_u8)`. +/// +/// On big-endian systems, endianness of `src[..consumed_u64]` values is +/// swapped. No other adjustments to `src` are made. +/// /// `filled_u8` is the number of filled bytes in `dest`, which may be less than /// the length of `dest`. /// `consumed_u64` is the number of words consumed from `src`, which is the same /// as `filled_u8 / 8` rounded up. /// /// See `fill_via_u32_chunks` for an example. -pub fn fill_via_u64_chunks(src: &[u64], dest: &mut [u8]) -> (usize, usize) { +pub fn fill_via_u64_chunks(src: &mut [u64], dest: &mut [u8]) -> (usize, usize) { fill_via_chunks(src, dest) } @@ -175,33 +178,41 @@ mod test { #[test] fn test_fill_via_u32_chunks() { - let src = [1, 2, 3]; + let src_orig = [1, 2, 3]; + + let mut src = src_orig; let mut dst = [0u8; 11]; - assert_eq!(fill_via_u32_chunks(&src, &mut dst), (3, 11)); + assert_eq!(fill_via_u32_chunks(&mut src, &mut dst), (3, 11)); assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 13]; - assert_eq!(fill_via_u32_chunks(&src, &mut dst), (3, 12)); + assert_eq!(fill_via_u32_chunks(&mut src, &mut dst), (3, 12)); assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 5]; - assert_eq!(fill_via_u32_chunks(&src, &mut dst), (2, 5)); + assert_eq!(fill_via_u32_chunks(&mut src, &mut dst), (2, 5)); assert_eq!(dst, [1, 0, 0, 0, 2]); } #[test] fn test_fill_via_u64_chunks() { - let src = [1, 2]; + let src_orig = [1, 2]; + + let mut src = src_orig; let mut dst = [0u8; 11]; - assert_eq!(fill_via_u64_chunks(&src, &mut dst), (2, 11)); + assert_eq!(fill_via_u64_chunks(&mut src, &mut dst), (2, 11)); assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 17]; - assert_eq!(fill_via_u64_chunks(&src, &mut dst), (2, 16)); + assert_eq!(fill_via_u64_chunks(&mut src, &mut dst), (2, 16)); assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 5]; - assert_eq!(fill_via_u64_chunks(&src, &mut dst), (1, 5)); + assert_eq!(fill_via_u64_chunks(&mut src, &mut dst), (1, 5)); assert_eq!(dst, [1, 0, 0, 0, 0]); } } From 0dddc2c5593fae7dc473b9f1d5774f1caa5e61c2 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 7 Dec 2022 02:18:04 -0800 Subject: [PATCH 41/54] Add read_adapter to avoid dynamic dispatch (#1267) * Add read_adapter to avoid dynamic dispatch * Get rid of the dyn Read impl since we can't deprecate it --- rand_core/src/lib.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 1234a566..70baf78d 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -41,8 +41,8 @@ use core::convert::AsMut; use core::default::Default; -#[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] use alloc::boxed::Box; pub use error::Error; @@ -182,6 +182,13 @@ pub trait RngCore { /// `fill_bytes` may be implemented with /// `self.try_fill_bytes(dest).unwrap()` or more specific error handling. fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error>; + + /// Convert an [`RngCore`] to a [`RngReadAdapter`]. + #[cfg(feature = "std")] + fn read_adapter(&mut self) -> RngReadAdapter<'_, Self> + where Self: Sized { + RngReadAdapter { inner: self } + } } /// A marker trait used to indicate that an [`RngCore`] or [`BlockRngCore`] @@ -469,14 +476,37 @@ impl RngCore for Box { } } +/// Adapter that enables reading through a [`io::Read`](std::io::Read) from a [`RngCore`]. +/// +/// # Examples +/// +/// ```rust +/// # use std::{io, io::Read}; +/// # use std::fs::File; +/// # use rand_core::{OsRng, RngCore}; +/// +/// io::copy(&mut OsRng.read_adapter().take(100), &mut File::create("/tmp/random.bytes").unwrap()).unwrap(); +/// ``` #[cfg(feature = "std")] -impl std::io::Read for dyn RngCore { +pub struct RngReadAdapter<'a, R: RngCore + ?Sized> { + inner: &'a mut R, +} + +#[cfg(feature = "std")] +impl std::io::Read for RngReadAdapter<'_, R> { fn read(&mut self, buf: &mut [u8]) -> Result { - self.try_fill_bytes(buf)?; + self.inner.try_fill_bytes(buf)?; Ok(buf.len()) } } +#[cfg(feature = "std")] +impl std::fmt::Debug for RngReadAdapter<'_, R> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ReadAdapter").finish() + } +} + // Implement `CryptoRng` for references to a `CryptoRng`. impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} From 81d7dc726443e9a057161356bcef0e6a770f9180 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 9 Dec 2022 11:50:09 +0200 Subject: [PATCH 42/54] build: harden test.yml permissions (#1274) Signed-off-by: Alex Signed-off-by: Alex --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50dd5e86..4039cb3a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master, '0.[0-9]+' ] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: check-doc: name: Check doc From fbd95860b461b04a2a0c02965b485192498189b5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 11 Dec 2022 17:47:51 +0000 Subject: [PATCH 43/54] Add Criterion as dev-dependency, fix CI for MSRV and minimal-versions (#1275) * Add criterion as dev-dependency * CI[minimal-versions]: require regex 1.5.1 --- .github/workflows/test.yml | 11 +- Cargo.lock.msrv | 707 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 3 files changed, 717 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock.msrv diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4039cb3a..a35e82db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,8 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.56.0 # MSRV + variant: MSRV + toolchain: 1.56.0 - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -58,6 +59,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: MSRV + if: ${{ matrix.variant == 'MSRV' }} + run: cp Cargo.lock.msrv Cargo.lock - name: Install toolchain uses: dtolnay/rust-toolchain@master with: @@ -66,7 +70,10 @@ jobs: - run: ${{ matrix.deps }} - name: Maybe minimal versions if: ${{ matrix.variant == 'minimal_versions' }} - run: cargo generate-lockfile -Z minimal-versions + run: | + cargo generate-lockfile -Z minimal-versions + # Overrides for dependencies with incorrect requirements (may need periodic updating) + cargo update -p regex --precise 1.5.1 - name: Maybe nightly if: ${{ matrix.toolchain == 'nightly' }} run: | diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv new file mode 100644 index 00000000..b173eb6d --- /dev/null +++ b/Cargo.lock.msrv @@ -0,0 +1,707 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "average" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843ec791d3f24503bbf72bbd5e49a3ab4dbb4bcd0a8ef6b0c908efa73caa27b1" +dependencies = [ + "easy-cast", + "float-ord", + "num-traits", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53da17d37dba964b9b3ecb5c5a1f193a2762c700e6829201e645b9381c99dc7" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "easy-cast" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd102ee8c418348759919b83b81cdbdc933ffe29740b903df448b4bafaa348e" +dependencies = [ + "libm", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +dependencies = [ + "bincode", + "criterion", + "libc", + "log", + "rand_chacha", + "rand_core", + "rand_pcg", + "rayon", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.4.0" +dependencies = [ + "ppv-lite86", + "rand_core", + "serde", + "serde_json", +] + +[[package]] +name = "rand_core" +version = "0.7.0" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "rand_distr" +version = "0.5.0" +dependencies = [ + "average", + "num-traits", + "rand", + "rand_pcg", + "serde", + "special", +] + +[[package]] +name = "rand_pcg" +version = "0.4.0" +dependencies = [ + "bincode", + "rand_core", + "serde", +] + +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "special" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" +dependencies = [ + "libc", +] + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 71b430f5..da251903 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,4 @@ rand_pcg = { path = "rand_pcg", version = "0.4.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" +criterion = { version = "0.4" } From b9e7d84c3bf14705b4907c92d024e07972032e12 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 22 Dec 2022 02:54:41 -0800 Subject: [PATCH 44/54] use partition_point in WeightedIndex (#1276) * use partition_point in WeightedIndex * fix partition_point * fix --- src/distributions/weighted_index.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 2af2446d..b1b2071a 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -229,15 +229,7 @@ where X: SampleUniform + PartialOrd use ::core::cmp::Ordering; let chosen_weight = self.weight_distribution.sample(rng); // Find the first item which has a weight *higher* than the chosen weight. - self.cumulative_weights - .binary_search_by(|w| { - if *w <= chosen_weight { - Ordering::Less - } else { - Ordering::Greater - } - }) - .unwrap_err() + self.cumulative_weights.partition_point(|w| w <= &chosen_weight) } } From e97b5b68877adc204e7193db40257f6e346e528b Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Sun, 1 Jan 2023 01:49:32 -0800 Subject: [PATCH 45/54] Simpler and faster implementation of Floyd's F2 (#1277) The previous implementation used either `Vec::insert` or a second F-Y shuffling phase to achieve fair random order. Instead, use the random numbers already drawn to achieve a fair shuffle. --- CHANGELOG.md | 7 +++++++ src/seq/index.rs | 29 +++++++---------------------- src/seq/mod.rs | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0872af6..b27a5edb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [Unreleased API changing release] + +### Other +- Simpler and faster implementation of Floyd's F2 (#1277). This + changes some outputs from `rand::seq::index::sample` and + `rand::seq::SliceRandom::choose_multiple`. + ## [0.8.5] - 2021-08-20 ### Fixes - Fix build on non-32/64-bit architectures (#1144) diff --git a/src/seq/index.rs b/src/seq/index.rs index ecd7d9f7..b7bc6a9b 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -380,33 +380,18 @@ where /// This implementation uses `O(amount)` memory and `O(amount^2)` time. fn sample_floyd(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized { - // For small amount we use Floyd's fully-shuffled variant. For larger - // amounts this is slow due to Vec::insert performance, so we shuffle - // afterwards. Benchmarks show little overhead from extra logic. - let floyd_shuffle = amount < 50; - + // Note that the values returned by `rng.gen_range()` can be + // inferred from the returned vector by working backwards from + // the last entry. This bijection proves the algorithm fair. debug_assert!(amount <= length); let mut indices = Vec::with_capacity(amount as usize); for j in length - amount..length { let t = rng.gen_range(0..=j); - if floyd_shuffle { - if let Some(pos) = indices.iter().position(|&x| x == t) { - indices.insert(pos, j); - continue; - } - } else if indices.contains(&t) { - indices.push(j); - continue; + if let Some(pos) = indices.iter().position(|&x| x == t) { + indices[pos] = j; } indices.push(t); } - if !floyd_shuffle { - // Reimplement SliceRandom::shuffle with smaller indices - for i in (1..amount).rev() { - // invariant: elements with index > i have been locked in place. - indices.swap(i as usize, rng.gen_range(0..=i) as usize); - } - } IndexVec::from(indices) } @@ -628,8 +613,8 @@ mod test { ); }; - do_test(10, 6, &[8, 0, 3, 5, 9, 6]); // floyd - do_test(25, 10, &[18, 15, 14, 9, 0, 13, 5, 24]); // floyd + do_test(10, 6, &[8, 3, 5, 9, 0, 6]); // floyd + do_test(25, 10, &[18, 14, 9, 15, 0, 13, 5, 24]); // floyd do_test(300, 8, &[30, 283, 150, 1, 73, 13, 285, 35]); // floyd do_test(300, 80, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace do_test(300, 180, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 24c65bc9..a61e5169 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -725,7 +725,7 @@ mod test { .choose_multiple(&mut r, 8) .cloned() .collect::>(), - &['d', 'm', 'b', 'n', 'c', 'k', 'h', 'e'] + &['d', 'm', 'n', 'k', 'h', 'e', 'b', 'c'] ); #[cfg(feature = "alloc")] From 3107a54aea87ee857441027c2f33c4608e2a40dc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Jan 2023 19:49:17 +0200 Subject: [PATCH 46/54] Relax `Sized` bound on `Distribution for &D` (#1278) --- src/distributions/distribution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index c5cf6a60..4fcb4c32 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -112,7 +112,7 @@ pub trait Distribution { } } -impl<'a, T, D: Distribution> Distribution for &'a D { +impl<'a, T, D: Distribution + ?Sized> Distribution for &'a D { fn sample(&self, rng: &mut R) -> T { (*self).sample(rng) } From 1e96eb4593717181d435f5895ba5ed63a58a5efa Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 5 Jan 2023 10:49:33 +0000 Subject: [PATCH 47/54] Added new versions of choose and choose_stable (#1268) * Added new versions of choose and choose_stable * Removed coin_flipper tests which were unnecessary and not building on ci * Performance optimizations in coin_flipper * Clippy fixes and more documentation * Added a correctness fix for coin_flipper * Update benches/seq.rs Co-authored-by: Vinzent Steinberg * Update benches/seq.rs Co-authored-by: Vinzent Steinberg * Removed old version of choose and choose stable and updated value stability tests * Moved sequence choose benchmarks to their own file * Reworked coin_flipper * Use criterion for seq_choose benches * Removed an old comment * Change how c is estimated in coin_flipper * Revert "Use criterion for seq_choose benches" This reverts commit 23395391370ab95694558be90686eb16494e590a. * Added seq_choose benches for smaller numbers * Removed some unneeded lines from seq_choose * Improvements in coin_flipper.rs * Small refactor of coin_flipper * Tidied comments in coin_flipper * Use criterion for seq_choose benchmarks * Made choose not generate a random number if len=1 * small change to IteratorRandom::choose * Made it easier to change seq_choose benchmarks RNG * Added Pcg64 benchmarks for seq_choose * Added TODO to coin_flipper * Changed criterion settings in seq_choose Co-authored-by: Vinzent Steinberg --- Cargo.toml | 5 + benches/seq.rs | 72 +--------- benches/seq_choose.rs | 111 +++++++++++++++ src/seq/coin_flipper.rs | 152 ++++++++++++++++++++ src/seq/mod.rs | 305 +++++++++++++++++++++++++--------------- 5 files changed, 460 insertions(+), 185 deletions(-) create mode 100644 benches/seq_choose.rs create mode 100644 src/seq/coin_flipper.rs diff --git a/Cargo.toml b/Cargo.toml index da251903..19f75738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,3 +75,8 @@ rand_pcg = { path = "rand_pcg", version = "0.4.0" } bincode = "1.2.1" rayon = "1.5.3" criterion = { version = "0.4" } + +[[bench]] +name = "seq_choose" +path = "benches/seq_choose.rs" +harness = false \ No newline at end of file diff --git a/benches/seq.rs b/benches/seq.rs index 5b3a846f..3d57d487 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -13,9 +13,9 @@ extern crate test; use test::Bencher; +use core::mem::size_of; use rand::prelude::*; use rand::seq::*; -use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. @@ -74,76 +74,6 @@ seq_slice_choose_multiple!(seq_slice_choose_multiple_950_of_1000, 950, 1000); seq_slice_choose_multiple!(seq_slice_choose_multiple_10_of_100, 10, 100); seq_slice_choose_multiple!(seq_slice_choose_multiple_90_of_100, 90, 100); -#[bench] -fn seq_iter_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &mut [usize] = &mut [1; 1000]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.iter().choose(&mut rng).unwrap(); - } - s - }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; -} - -#[derive(Clone)] -struct UnhintedIterator { - iter: I, -} -impl Iterator for UnhintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -#[derive(Clone)] -struct WindowHintedIterator { - iter: I, - window_size: usize, -} -impl Iterator for WindowHintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - (core::cmp::min(self.iter.len(), self.window_size), None) - } -} - -#[bench] -fn seq_iter_unhinted_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; 1000]; - b.iter(|| { - UnhintedIterator { iter: x.iter() } - .choose(&mut rng) - .unwrap() - }) -} - -#[bench] -fn seq_iter_window_hinted_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; 1000]; - b.iter(|| { - WindowHintedIterator { - iter: x.iter(), - window_size: 7, - } - .choose(&mut rng) - }) -} - #[bench] fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs new file mode 100644 index 00000000..44b4bdf9 --- /dev/null +++ b/benches/seq_choose.rs @@ -0,0 +1,111 @@ +// Copyright 2018-2022 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. +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::prelude::*; +use rand::SeedableRng; + +criterion_group!( +name = benches; +config = Criterion::default(); +targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + bench_rng::(c, "ChaCha20"); + bench_rng::(c, "Pcg32"); + bench_rng::(c, "Pcg64"); +} + +fn bench_rng(c: &mut Criterion, rng_name: &'static str) { + for length in [1, 2, 3, 10, 100, 1000].map(|x| black_box(x)) { + c.bench_function( + format!("choose_size-hinted_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_size_hinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_stable_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_unhinted_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_windowed_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }, + ); + } +} + +fn choose_size_hinted(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose(rng) +} + +fn choose_stable(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose_stable(rng) +} + +fn choose_unhinted(max: usize, rng: &mut R) -> Option { + let iterator = UnhintedIterator { iter: (0..max) }; + iterator.choose(rng) +} + +fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { + let iterator = WindowHintedIterator { + iter: (0..max), + window_size, + }; + iterator.choose(rng) +} + +#[derive(Clone)] +struct UnhintedIterator { + iter: I, +} +impl Iterator for UnhintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +#[derive(Clone)] +struct WindowHintedIterator { + iter: I, + window_size: usize, +} +impl Iterator for WindowHintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + (core::cmp::min(self.iter.len(), self.window_size), None) + } +} diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs new file mode 100644 index 00000000..77c18ded --- /dev/null +++ b/src/seq/coin_flipper.rs @@ -0,0 +1,152 @@ +use crate::RngCore; + +pub(crate) struct CoinFlipper { + pub rng: R, + chunk: u32, //TODO(opt): this should depend on RNG word size + chunk_remaining: u32, +} + +impl CoinFlipper { + pub fn new(rng: R) -> Self { + Self { + rng, + chunk: 0, + chunk_remaining: 0, + } + } + + #[inline] + /// Returns true with a probability of 1 / d + /// Uses an expected two bits of randomness + /// Panics if d == 0 + pub fn gen_ratio_one_over(&mut self, d: usize) -> bool { + debug_assert_ne!(d, 0); + // This uses the same logic as `gen_ratio` but is optimized for the case that + // the starting numerator is one (which it always is for `Sequence::Choose()`) + + // In this case (but not `gen_ratio`), this way of calculating c is always accurate + let c = (usize::BITS - 1 - d.leading_zeros()).min(32); + + if self.flip_c_heads(c) { + let numerator = 1 << c; + return self.gen_ratio(numerator, d); + } else { + return false; + } + } + + #[inline] + /// Returns true with a probability of n / d + /// Uses an expected two bits of randomness + fn gen_ratio(&mut self, mut n: usize, d: usize) -> bool { + // Explanation: + // We are trying to return true with a probability of n / d + // If n >= d, we can just return true + // Otherwise there are two possibilities 2n < d and 2n >= d + // In either case we flip a coin. + // If 2n < d + // If it comes up tails, return false + // If it comes up heads, double n and start again + // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d + // (if 2n was greater than d we would effectively round it down to 1 + // by returning true) + // If 2n >= d + // If it comes up tails, set n to 2n - d and start again + // If it comes up heads, return true + // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d + // Note that if 2n = d and the coin comes up tails, n will be set to 0 + // before restarting which is equivalent to returning false. + + // As a performance optimization we can flip multiple coins at once + // This is efficient because we can use the `lzcnt` intrinsic + // We can check up to 32 flips at once but we only receive one bit of information + // - all heads or at least one tail. + + // Let c be the number of coins to flip. 1 <= c <= 32 + // If 2n < d, n * 2^c < d + // If the result is all heads, then set n to n * 2^c + // If there was at least one tail, return false + // If 2n >= d, the order of results matters so we flip one coin at a time so c = 1 + // Ideally, c will be as high as possible within these constraints + + while n < d { + // Find a good value for c by counting leading zeros + // This will either give the highest possible c, or 1 less than that + let c = n + .leading_zeros() + .saturating_sub(d.leading_zeros() + 1) + .clamp(1, 32); + + if self.flip_c_heads(c) { + // All heads + // Set n to n * 2^c + // If 2n >= d, the while loop will exit and we will return `true` + // If n * 2^c > `usize::MAX` we always return `true` anyway + n = n.saturating_mul(2_usize.pow(c)); + } else { + //At least one tail + if c == 1 { + // Calculate 2n - d. + // We need to use wrapping as 2n might be greater than `usize::MAX` + let next_n = n.wrapping_add(n).wrapping_sub(d); + if next_n == 0 || next_n > n { + // This will happen if 2n < d + return false; + } + n = next_n; + } else { + // c > 1 so 2n < d so we can return false + return false; + } + } + } + true + } + + /// If the next `c` bits of randomness all represent heads, consume them, return true + /// Otherwise return false and consume the number of heads plus one. + /// Generates new bits of randomness when necessary (in 32 bit chunks) + /// Has a 1 in 2 to the `c` chance of returning true + /// `c` must be less than or equal to 32 + fn flip_c_heads(&mut self, mut c: u32) -> bool { + debug_assert!(c <= 32); + // Note that zeros on the left of the chunk represent heads. + // It needs to be this way round because zeros are filled in when left shifting + loop { + let zeros = self.chunk.leading_zeros(); + + if zeros < c { + // The happy path - we found a 1 and can return false + // Note that because a 1 bit was detected, + // We cannot have run out of random bits so we don't need to check + + // First consume all of the bits read + // Using shl seems to give worse performance for size-hinted iterators + self.chunk = self.chunk.wrapping_shl(zeros + 1); + + self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); + return false; + } else { + // The number of zeros is larger than `c` + // There are two possibilities + if let Some(new_remaining) = self.chunk_remaining.checked_sub(c) { + // Those zeroes were all part of our random chunk, + // throw away `c` bits of randomness and return true + self.chunk_remaining = new_remaining; + self.chunk <<= c; + return true; + } else { + // Some of those zeroes were part of the random chunk + // and some were part of the space behind it + // We need to take into account only the zeroes that were random + c -= self.chunk_remaining; + + // Generate a new chunk + self.chunk = self.rng.next_u32(); + self.chunk_remaining = 32; + // Go back to start of loop + } + } + } + } +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index a61e5169..e1286105 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -24,20 +24,25 @@ //! `usize` indices are sampled as a `u32` where possible (also providing a //! small performance boost in some cases). - +mod coin_flipper; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; -#[cfg(feature = "alloc")] use core::ops::Index; +#[cfg(feature = "alloc")] +use core::ops::Index; -#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; #[cfg(feature = "alloc")] use crate::distributions::uniform::{SampleBorrow, SampleUniform}; -#[cfg(feature = "alloc")] use crate::distributions::WeightedError; +#[cfg(feature = "alloc")] +use crate::distributions::WeightedError; use crate::Rng; +use self::coin_flipper::CoinFlipper; + /// Extension trait on slices, providing random mutation and sampling methods. /// /// This trait is implemented on all `[T]` slice types, providing several @@ -77,14 +82,16 @@ pub trait SliceRandom { /// assert_eq!(choices[..0].choose(&mut rng), None); /// ``` fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Returns a mutable reference to one random element of the slice, or /// `None` if the slice is empty. /// /// For slices, complexity is `O(1)`. fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Chooses `amount` elements from the slice at random, without repetition, /// and in random order. The returned iterator is appropriate both for @@ -113,7 +120,8 @@ pub trait SliceRandom { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Similar to [`choose`], but where the likelihood of each outcome may be /// specified. @@ -249,7 +257,8 @@ pub trait SliceRandom { /// println!("Shuffled: {:?}", y); /// ``` fn shuffle(&mut self, rng: &mut R) - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Shuffle a slice in place, but exit early. /// @@ -271,7 +280,8 @@ pub trait SliceRandom { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, ) -> (&mut [Self::Item], &mut [Self::Item]) - where R: Rng + ?Sized; + where + R: Rng + ?Sized; } /// Extension trait on iterators, providing random sampling methods. @@ -309,26 +319,30 @@ pub trait IteratorRandom: Iterator + Sized { /// `choose` returning different elements. If you want consistent results /// and RNG usage consider using [`IteratorRandom::choose_stable`]. fn choose(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { + 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 - } else { - self.nth(gen_index(rng, lower)) + return match lower { + 0 => None, + 1 => self.next(), + _ => self.nth(gen_index(rng, lower)), }; } + let mut coin_flipper = coin_flipper::CoinFlipper::new(rng); + let mut consumed = 0; + // Continue until the iterator is exhausted loop { if lower > 1 { - let ix = gen_index(rng, lower + consumed); + let ix = gen_index(coin_flipper.rng, lower + consumed); let skip = if ix < lower { result = self.nth(ix); lower - (ix + 1) @@ -348,7 +362,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } consumed += 1; - if gen_index(rng, consumed) == 0 { + if coin_flipper.gen_ratio_one_over(consumed) { result = elem; } } @@ -378,9 +392,12 @@ pub trait IteratorRandom: Iterator + Sized { /// /// [`choose`]: IteratorRandom::choose fn choose_stable(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mut consumed = 0; let mut result = None; + let mut coin_flipper = CoinFlipper::new(rng); loop { // Currently the only way to skip elements is `nth()`. So we need to @@ -392,7 +409,7 @@ pub trait IteratorRandom: Iterator + Sized { let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) - .filter(|ix| gen_index(rng, consumed+ix+1) == 0) + .filter(|ix| coin_flipper.gen_ratio_one_over(consumed + ix + 1)) .last(); consumed += lower; @@ -407,10 +424,10 @@ pub trait IteratorRandom: Iterator + Sized { let elem = self.nth(next); if elem.is_none() { - return result + return result; } - if gen_index(rng, consumed+1) == 0 { + if coin_flipper.gen_ratio_one_over(consumed + 1) { result = elem; } consumed += 1; @@ -431,7 +448,9 @@ pub trait IteratorRandom: Iterator + Sized { /// Complexity is `O(n)` where `n` is the length of the iterator. /// For slices, prefer [`SliceRandom::choose_multiple`]. fn choose_multiple_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let amount = buf.len(); let mut len = 0; while len < amount { @@ -471,7 +490,9 @@ pub trait IteratorRandom: Iterator + Sized { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mut reservoir = Vec::with_capacity(amount); reservoir.extend(self.by_ref().take(amount)); @@ -495,12 +516,13 @@ pub trait IteratorRandom: Iterator + Sized { } } - impl SliceRandom for [T] { type Item = T; fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { if self.is_empty() { None } else { @@ -509,7 +531,9 @@ impl SliceRandom for [T] { } fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { if self.is_empty() { None } else { @@ -520,7 +544,9 @@ impl SliceRandom for [T] { #[cfg(feature = "alloc")] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let amount = ::core::cmp::min(amount, self.len()); SliceChooseIter { slice: self, @@ -591,7 +617,9 @@ impl SliceRandom for [T] { } fn shuffle(&mut self, rng: &mut R) - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { for i in (1..self.len()).rev() { // invariant: elements with index > i have been locked in place. self.swap(i, gen_index(rng, i + 1)); @@ -601,7 +629,9 @@ impl SliceRandom for [T] { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, ) -> (&mut [Self::Item], &mut [Self::Item]) - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { // This applies Durstenfeld's algorithm for the // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) // for an unbiased permutation, but exits early after choosing `amount` @@ -621,7 +651,6 @@ impl SliceRandom for [T] { impl IteratorRandom for I where I: Iterator + Sized {} - /// An iterator over multiple slice elements. /// /// This struct is created by @@ -658,12 +687,12 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator } } - // Sample a number uniformly between 0 and `ubound`. Uses 32-bit sampling where // possible, primarily in order to produce the same output on 32-bit and 64-bit // platforms. #[inline] fn gen_index(rng: &mut R, ubound: usize) -> usize { + if ubound <= (core::u32::MAX as usize) { rng.gen_range(0..ubound as u32) as usize } else { @@ -671,12 +700,13 @@ fn gen_index(rng: &mut R, ubound: usize) -> usize { } } - #[cfg(test)] mod test { use super::*; - #[cfg(feature = "alloc")] use crate::Rng; - #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; + #[cfg(feature = "alloc")] + use crate::Rng; + #[cfg(all(feature = "alloc", not(feature = "std")))] + use alloc::vec::Vec; #[test] fn test_slice_choose() { @@ -837,28 +867,40 @@ mod test { #[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, - }); + 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); @@ -891,28 +933,40 @@ mod test { #[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, - }); + 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); @@ -932,33 +986,48 @@ mod test { } let reference = test_iter(0..9); - assert_eq!(test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference); + 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); + 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] @@ -1129,7 +1198,7 @@ mod test { assert_eq!(choose([].iter().cloned()), None); assert_eq!(choose(0..100), Some(33)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -1174,8 +1243,8 @@ mod test { } 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(0..100), Some(27)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -1183,7 +1252,7 @@ mod test { chunk_remaining: 32, hint_total_size: false, }), - Some(40) + Some(27) ); assert_eq!( choose(ChunkHintedIterator { @@ -1192,7 +1261,7 @@ mod test { chunk_remaining: 32, hint_total_size: true, }), - Some(40) + Some(27) ); assert_eq!( choose(WindowHintedIterator { @@ -1200,7 +1269,7 @@ mod test { window_size: 32, hint_total_size: false, }), - Some(40) + Some(27) ); assert_eq!( choose(WindowHintedIterator { @@ -1208,7 +1277,7 @@ mod test { window_size: 32, hint_total_size: true, }), - Some(40) + Some(27) ); } @@ -1260,9 +1329,13 @@ mod test { // Case 2: All of the weights are 0 let choices = [('a', 0), ('b', 0), ('c', 0)]; - assert_eq!(choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap().count(), 2); + assert_eq!( + choices + .choose_multiple_weighted(&mut rng, 2, |item| item.1) + .unwrap() + .count(), + 2 + ); // Case 3: Negative weights let choices = [('a', -1), ('b', 1), ('c', 1)]; @@ -1275,9 +1348,13 @@ mod test { // Case 4: Empty list let choices = []; - assert_eq!(choices - .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) - .unwrap().count(), 0); + assert_eq!( + choices + .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) + .unwrap() + .count(), + 0 + ); // Case 5: NaN weights let choices = [('a', core::f64::NAN), ('b', 1.0), ('c', 1.0)]; From 4bde8a0adb517ec956fcec91665922f6360f974b Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Sun, 8 Jan 2023 16:14:26 +0000 Subject: [PATCH 48/54] Performance improvements for `shuffle` and `partial_shuffle` (#1272) * Made shuffle and partial_shuffle faster * Use criterion benchmarks for shuffle * Added a note about RNG word size * Tidied comments * Added a debug_assert * Added a comment re possible further optimization * Added and updated copyright notices * Revert cfg mistake * Reverted change to mod.rs * Removed ChaCha20 benches from shuffle * moved debug_assert out of a const fn --- Cargo.toml | 5 ++ benches/seq_choose.rs | 2 +- benches/shuffle.rs | 50 ++++++++++++++++ src/seq/coin_flipper.rs | 8 +++ src/seq/increasing_uniform.rs | 108 ++++++++++++++++++++++++++++++++++ src/seq/mod.rs | 51 ++++++++++------ 6 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 benches/shuffle.rs create mode 100644 src/seq/increasing_uniform.rs diff --git a/Cargo.toml b/Cargo.toml index 19f75738..ec0e4d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,4 +79,9 @@ criterion = { version = "0.4" } [[bench]] name = "seq_choose" path = "benches/seq_choose.rs" +harness = false + +[[bench]] +name = "shuffle" +path = "benches/shuffle.rs" harness = false \ No newline at end of file diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 44b4bdf9..2c34d77c 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2022 Developers of the Rand project. +// Copyright 2018-2023 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/benches/shuffle.rs b/benches/shuffle.rs new file mode 100644 index 00000000..3d687821 --- /dev/null +++ b/benches/shuffle.rs @@ -0,0 +1,50 @@ +// Copyright 2018-2023 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. +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::prelude::*; +use rand::SeedableRng; + +criterion_group!( +name = benches; +config = Criterion::default(); +targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + bench_rng::(c, "ChaCha12"); + bench_rng::(c, "Pcg32"); + bench_rng::(c, "Pcg64"); +} + +fn bench_rng(c: &mut Criterion, rng_name: &'static str) { + for length in [1, 2, 3, 10, 100, 1000, 10000].map(|x| black_box(x)) { + c.bench_function(format!("shuffle_{length}_{rng_name}").as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + let mut vec: Vec = (0..length).collect(); + b.iter(|| { + vec.shuffle(&mut rng); + vec[0] + }) + }); + + if length >= 10 { + c.bench_function( + format!("partial_shuffle_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + let mut vec: Vec = (0..length).collect(); + b.iter(|| { + vec.partial_shuffle(&mut rng, length / 2); + vec[0] + }) + }, + ); + } + } +} diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 77c18ded..05f18d71 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -1,3 +1,11 @@ +// Copyright 2018-2023 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. + use crate::RngCore; pub(crate) struct CoinFlipper { diff --git a/src/seq/increasing_uniform.rs b/src/seq/increasing_uniform.rs new file mode 100644 index 00000000..3208c656 --- /dev/null +++ b/src/seq/increasing_uniform.rs @@ -0,0 +1,108 @@ +// Copyright 2018-2023 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. + +use crate::{Rng, RngCore}; + +/// Similar to a Uniform distribution, +/// but after returning a number in the range [0,n], n is increased by 1. +pub(crate) struct IncreasingUniform { + pub rng: R, + n: u32, + // Chunk is a random number in [0, (n + 1) * (n + 2) *..* (n + chunk_remaining) ) + chunk: u32, + chunk_remaining: u8, +} + +impl IncreasingUniform { + /// Create a dice roller. + /// The next item returned will be a random number in the range [0,n] + pub fn new(rng: R, n: u32) -> Self { + // If n = 0, the first number returned will always be 0 + // so we don't need to generate a random number + let chunk_remaining = if n == 0 { 1 } else { 0 }; + Self { + rng, + n, + chunk: 0, + chunk_remaining, + } + } + + /// Returns a number in [0,n] and increments n by 1. + /// Generates new random bits as needed + /// Panics if `n >= u32::MAX` + #[inline] + pub fn next_index(&mut self) -> usize { + let next_n = self.n + 1; + + // There's room for further optimisation here: + // gen_range uses rejection sampling (or other method; see #1196) to avoid bias. + // When the initial sample is biased for range 0..bound + // it may still be viable to use for a smaller bound + // (especially if small biases are considered acceptable). + + let next_chunk_remaining = self.chunk_remaining.checked_sub(1).unwrap_or_else(|| { + // If the chunk is empty, generate a new chunk + let (bound, remaining) = calculate_bound_u32(next_n); + // bound = (n + 1) * (n + 2) *..* (n + remaining) + self.chunk = self.rng.gen_range(0..bound); + // Chunk is a random number in + // [0, (n + 1) * (n + 2) *..* (n + remaining) ) + + remaining - 1 + }); + + let result = if next_chunk_remaining == 0 { + // `chunk` is a random number in the range [0..n+1) + // Because `chunk_remaining` is about to be set to zero + // we do not need to clear the chunk here + self.chunk as usize + } else { + // `chunk` is a random number in a range that is a multiple of n+1 + // so r will be a random number in [0..n+1) + let r = self.chunk % next_n; + self.chunk /= next_n; + r as usize + }; + + self.chunk_remaining = next_chunk_remaining; + self.n = next_n; + result + } +} + +#[inline] +/// Calculates `bound`, `count` such that bound (m)*(m+1)*..*(m + remaining - 1) +fn calculate_bound_u32(m: u32) -> (u32, u8) { + debug_assert!(m > 0); + #[inline] + const fn inner(m: u32) -> (u32, u8) { + let mut product = m; + let mut current = m + 1; + + loop { + if let Some(p) = u32::checked_mul(product, current) { + product = p; + current += 1; + } else { + // Count has a maximum value of 13 for when min is 1 or 2 + let count = (current - m) as u8; + return (product, count); + } + } + } + + const RESULT2: (u32, u8) = inner(2); + if m == 2 { + // Making this value a constant instead of recalculating it + // gives a significant (~50%) performance boost for small shuffles + return RESULT2; + } + + inner(m) +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index e1286105..d9b38e92 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Developers of the Rand project. +// Copyright 2018-2023 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -29,6 +29,8 @@ mod coin_flipper; #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; +mod increasing_uniform; + #[cfg(feature = "alloc")] use core::ops::Index; @@ -42,6 +44,7 @@ use crate::distributions::WeightedError; use crate::Rng; use self::coin_flipper::CoinFlipper; +use self::increasing_uniform::IncreasingUniform; /// Extension trait on slices, providing random mutation and sampling methods. /// @@ -620,10 +623,11 @@ impl SliceRandom for [T] { where R: Rng + ?Sized, { - for i in (1..self.len()).rev() { - // invariant: elements with index > i have been locked in place. - self.swap(i, gen_index(rng, i + 1)); + if self.len() <= 1 { + // There is no need to shuffle an empty or single element slice + return; } + self.partial_shuffle(rng, self.len()); } fn partial_shuffle( @@ -632,19 +636,30 @@ impl SliceRandom for [T] { where R: Rng + ?Sized, { - // This applies Durstenfeld's algorithm for the + let m = self.len().saturating_sub(amount); + + // The algorithm below is based on Durstenfeld's algorithm for the // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) - // for an unbiased permutation, but exits early after choosing `amount` - // elements. - - let len = self.len(); - let end = if amount >= len { 0 } else { len - amount }; - - for i in (end..len).rev() { - // invariant: elements with index > i have been locked in place. - self.swap(i, gen_index(rng, i + 1)); + // for an unbiased permutation. + // It ensures that the last `amount` elements of the slice + // are randomly selected from the whole slice. + + //`IncreasingUniform::next_index()` is faster than `gen_index` + //but only works for 32 bit integers + //So we must use the slow method if the slice is longer than that. + if self.len() < (u32::MAX as usize) { + let mut chooser = IncreasingUniform::new(rng, m as u32); + for i in m..self.len() { + let index = chooser.next_index(); + self.swap(i, index); + } + } else { + for i in m..self.len() { + let index = gen_index(rng, i + 1); + self.swap(i, index); + } } - let r = self.split_at_mut(end); + let r = self.split_at_mut(m); (r.1, r.0) } } @@ -765,11 +780,11 @@ mod test { let mut r = crate::test::rng(414); nums.shuffle(&mut r); - assert_eq!(nums, [9, 5, 3, 10, 7, 12, 8, 11, 6, 4, 0, 2, 1]); + assert_eq!(nums, [5, 11, 0, 8, 7, 12, 6, 4, 9, 3, 1, 2, 10]); nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; let res = nums.partial_shuffle(&mut r, 6); - assert_eq!(res.0, &mut [7, 4, 8, 6, 9, 3]); - assert_eq!(res.1, &mut [0, 1, 2, 12, 11, 5, 10]); + assert_eq!(res.0, &mut [7, 12, 6, 8, 1, 9]); + assert_eq!(res.1, &mut [0, 11, 2, 3, 4, 5, 10]); } #[derive(Clone)] From ae4b48ece870c70cee5143888656711803c87749 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 2 Feb 2023 04:30:08 -0500 Subject: [PATCH 49/54] Add note about floating point weights in update_weights docs (#1280) --- src/distributions/weighted_index.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index b1b2071a..0131ed15 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -141,6 +141,10 @@ impl WeightedIndex { /// allocation internally. /// /// In case of error, `self` is not modified. + /// + /// Note: Updating floating-point weights may cause slight inaccuracies in the total weight. + /// This method may not return `WeightedError::AllWeightsZero` when all weights + /// are zero if using floating-point weights. pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightedError> where X: for<'a> ::core::ops::AddAssign<&'a X> + for<'a> ::core::ops::SubAssign<&'a X> From 7d73990096890960dbc086e5ad93c453e4435b25 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 6 Feb 2023 11:55:22 +0100 Subject: [PATCH 50/54] Make `Uniform` constructors return a result (#1229) * Forbid unsafe code in crates without unsafe code This helps tools like `cargo geiger`. * Make `Uniform` constructors return a result - This is a breaking change. - The new error type had to be made public, otherwise `Uniform` could not be extended for user-defined types by implementing `UniformSampler`. - `rand_distr` was updated accordingly. - Also forbid unsafe code for crates where none is used. Fixes #1195, #1211. * Address review feedback * Make `sample_single` return a `Result` * Fix benchmarks * Small fixes * Update src/distributions/uniform.rs --- CHANGELOG.md | 5 +- benches/distributions.rs | 38 +-- examples/monte-carlo.rs | 2 +- examples/monty-hall.rs | 2 +- examples/rayon-monte-carlo.rs | 2 +- rand_chacha/src/lib.rs | 1 + rand_distr/CHANGELOG.md | 1 + rand_distr/src/binomial.rs | 4 +- rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/lib.rs | 1 + rand_distr/src/unit_ball.rs | 2 +- rand_distr/src/unit_circle.rs | 2 +- rand_distr/src/unit_disc.rs | 2 +- rand_distr/src/unit_sphere.rs | 2 +- rand_distr/src/weighted_alias.rs | 6 +- rand_pcg/src/lib.rs | 1 + src/distributions/distribution.rs | 7 +- src/distributions/other.rs | 4 +- src/distributions/slice.rs | 2 +- src/distributions/uniform.rs | 388 +++++++++++++++------------- src/distributions/weighted_index.rs | 5 +- src/rng.rs | 8 +- src/seq/index.rs | 2 +- 23 files changed, 261 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b27a5edb..083b7dbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [Unreleased API changing release] +## [0.9.0] - unreleased +### Distributions +- `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) +- `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) ### Other - Simpler and faster implementation of Floyd's F2 (#1277). This diff --git a/benches/distributions.rs b/benches/distributions.rs index 76d5d258..f637fe4a 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -131,36 +131,36 @@ macro_rules! distr { } // uniform -distr_int!(distr_uniform_i8, i8, Uniform::new(20i8, 100)); -distr_int!(distr_uniform_i16, i16, Uniform::new(-500i16, 2000)); -distr_int!(distr_uniform_i32, i32, Uniform::new(-200_000_000i32, 800_000_000)); -distr_int!(distr_uniform_i64, i64, Uniform::new(3i64, 123_456_789_123)); -distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_789_123_456_789)); -distr_int!(distr_uniform_usize16, usize, Uniform::new(0usize, 0xb9d7)); -distr_int!(distr_uniform_usize32, usize, Uniform::new(0usize, 0x548c0f43)); +distr_int!(distr_uniform_i8, i8, Uniform::new(20i8, 100).unwrap()); +distr_int!(distr_uniform_i16, i16, Uniform::new(-500i16, 2000).unwrap()); +distr_int!(distr_uniform_i32, i32, Uniform::new(-200_000_000i32, 800_000_000).unwrap()); +distr_int!(distr_uniform_i64, i64, Uniform::new(3i64, 123_456_789_123).unwrap()); +distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_789_123_456_789).unwrap()); +distr_int!(distr_uniform_usize16, usize, Uniform::new(0usize, 0xb9d7).unwrap()); +distr_int!(distr_uniform_usize32, usize, Uniform::new(0usize, 0x548c0f43).unwrap()); #[cfg(target_pointer_width = "64")] -distr_int!(distr_uniform_usize64, usize, Uniform::new(0usize, 0x3a42714f2bf927a8)); -distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057)); +distr_int!(distr_uniform_usize64, usize, Uniform::new(0usize, 0x3a42714f2bf927a8).unwrap()); +distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057).unwrap()); -distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319)); -distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319)); +distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319).unwrap()); +distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319).unwrap()); const LARGE_SEC: u64 = u64::max_value() / 1000; distr_duration!(distr_uniform_duration_largest, - Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::max_value(), 999_999_999)) + Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::max_value(), 999_999_999)).unwrap() ); distr_duration!(distr_uniform_duration_large, - Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)) + Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)).unwrap() ); distr_duration!(distr_uniform_duration_one, - Uniform::new(Duration::new(0, 0), Duration::new(1, 0)) + Uniform::new(Duration::new(0, 0), Duration::new(1, 0)).unwrap() ); distr_duration!(distr_uniform_duration_variety, - Uniform::new(Duration::new(10000, 423423), Duration::new(200000, 6969954)) + Uniform::new(Duration::new(10000, 423423), Duration::new(200000, 6969954)).unwrap() ); distr_duration!(distr_uniform_duration_edge, - Uniform::new_inclusive(Duration::new(LARGE_SEC, 999_999_999), Duration::new(LARGE_SEC + 1, 1)) + Uniform::new_inclusive(Duration::new(LARGE_SEC, 999_999_999), Duration::new(LARGE_SEC + 1, 1)).unwrap() ); // standard @@ -272,7 +272,7 @@ macro_rules! uniform_sample { let high = black_box($high); b.iter(|| { for _ in 0..10 { - let dist = UniformInt::<$type>::new(low, high); + let dist = UniformInt::<$type>::new(low, high).unwrap(); for _ in 0..$count { black_box(dist.sample(&mut rng)); } @@ -291,7 +291,7 @@ macro_rules! uniform_inclusive { let high = black_box($high); b.iter(|| { for _ in 0..10 { - let dist = UniformInt::<$type>::new_inclusive(low, high); + let dist = UniformInt::<$type>::new_inclusive(low, high).unwrap(); for _ in 0..$count { black_box(dist.sample(&mut rng)); } @@ -311,7 +311,7 @@ macro_rules! uniform_single { let high = black_box($high); b.iter(|| { for _ in 0..(10 * $count) { - black_box(UniformInt::<$type>::sample_single(low, high, &mut rng)); + black_box(UniformInt::<$type>::sample_single(low, high, &mut rng).unwrap()); } }); } diff --git a/examples/monte-carlo.rs b/examples/monte-carlo.rs index 6cc9f4e1..a72cc1e9 100644 --- a/examples/monte-carlo.rs +++ b/examples/monte-carlo.rs @@ -29,7 +29,7 @@ use rand::distributions::{Distribution, Uniform}; fn main() { - let range = Uniform::new(-1.0f64, 1.0); + let range = Uniform::new(-1.0f64, 1.0).unwrap(); let mut rng = rand::thread_rng(); let total = 1_000_000; diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index 2a3b63d8..23e11178 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -80,7 +80,7 @@ fn main() { let num_simulations = 10000; let mut rng = rand::thread_rng(); - let random_door = Uniform::new(0u32, 3); + let random_door = Uniform::new(0u32, 3).unwrap(); let (mut switch_wins, mut switch_losses) = (0, 0); let (mut keep_wins, mut keep_losses) = (0, 0); diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index f0e7114a..7e703c01 100644 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -49,7 +49,7 @@ static BATCH_SIZE: u64 = 10_000; static BATCHES: u64 = 1000; fn main() { - let range = Uniform::new(-1.0f64, 1.0); + let range = Uniform::new(-1.0f64, 1.0).unwrap(); let in_circle = (0..BATCHES) .into_par_iter() diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index 24125b45..f4b526b8 100644 --- a/rand_chacha/src/lib.rs +++ b/rand_chacha/src/lib.rs @@ -13,6 +13,7 @@ html_favicon_url = "https://www.rust-lang.org/favicon.ico", html_root_url = "https://rust-random.github.io/rand/" )] +#![forbid(unsafe_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 66389d67..13dac1c8 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.0] - unreleased - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. +- Upgrade Rand ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 6dbf7ab7..0f49806a 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -163,8 +163,8 @@ impl Distribution for Binomial { // return value let mut y: i64; - let gen_u = Uniform::new(0., p4); - let gen_v = Uniform::new(0., 1.); + let gen_u = Uniform::new(0., p4).unwrap(); + let gen_v = Uniform::new(0., 1.).unwrap(); loop { // Step 1: Generate `u` for selecting the region. If region 1 is diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 47614503..a42a572d 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -251,7 +251,7 @@ impl Distribution for Hypergeometric { x }, RejectionAcceptance { m, a, lambda_l, lambda_r, x_l, x_r, p1, p2, p3 } => { - let distr_region_select = Uniform::new(0.0, p3); + let distr_region_select = Uniform::new(0.0, p3).unwrap(); loop { let (y, v) = loop { let u = distr_region_select.sample(rng); diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 6d8d81bd..c8fd2981 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -11,6 +11,7 @@ html_favicon_url = "https://www.rust-lang.org/favicon.ico", html_root_url = "https://rust-random.github.io/rand/" )] +#![forbid(unsafe_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![allow( diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index 8a4b4fbf..4d296125 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -31,7 +31,7 @@ pub struct UnitBall; impl Distribution<[F; 3]> for UnitBall { #[inline] fn sample(&self, rng: &mut R) -> [F; 3] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); let mut x1; let mut x2; let mut x3; diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index 24a06f3f..f3dbe757 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -35,7 +35,7 @@ pub struct UnitCircle; impl Distribution<[F; 2]> for UnitCircle { #[inline] fn sample(&self, rng: &mut R) -> [F; 2] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); let mut x1; let mut x2; let mut sum; diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index 937c1d01..5004217d 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -30,7 +30,7 @@ pub struct UnitDisc; impl Distribution<[F; 2]> for UnitDisc { #[inline] fn sample(&self, rng: &mut R) -> [F; 2] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); let mut x1; let mut x2; loop { diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index 2b299239..632275e3 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -34,7 +34,7 @@ pub struct UnitSphere; impl Distribution<[F; 3]> for UnitSphere { #[inline] fn sample(&self, rng: &mut R) -> [F; 3] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); loop { let (x1, x2) = (uniform.sample(rng), uniform.sample(rng)); let sum = x1 * x1 + x2 * x2; diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 582a4dd9..170de80c 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -221,8 +221,8 @@ impl WeightedAliasIndex { // Prepare distributions for sampling. Creating them beforehand improves // sampling performance. - let uniform_index = Uniform::new(0, n); - let uniform_within_weight_sum = Uniform::new(W::ZERO, weight_sum); + let uniform_index = Uniform::new(0, n).unwrap(); + let uniform_within_weight_sum = Uniform::new(W::ZERO, weight_sum).unwrap(); Ok(Self { aliases: aliases.aliases, @@ -458,7 +458,7 @@ mod test { let random_weight_distribution = Uniform::new_inclusive( W::ZERO, W::MAX / W::try_from_u32_lossy(NUM_WEIGHTS).unwrap(), - ); + ).unwrap(); for _ in 0..NUM_WEIGHTS { weights.push(rng.sample(&random_weight_distribution)); } diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 9d0209d1..34131395 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -33,6 +33,7 @@ html_favicon_url = "https://www.rust-lang.org/favicon.ico", html_root_url = "https://rust-random.github.io/rand/" )] +#![forbid(unsafe_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![no_std] diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 4fcb4c32..c6eaf5ef 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -64,7 +64,7 @@ pub trait Distribution { /// .collect(); /// /// // Dice-rolling: - /// let die_range = Uniform::new_inclusive(1, 6); + /// let die_range = Uniform::new_inclusive(1, 6).unwrap(); /// let mut roll_die = die_range.sample_iter(&mut rng); /// while roll_die.next().unwrap() != 6 { /// println!("Not a 6; rolling again!"); @@ -93,7 +93,7 @@ pub trait Distribution { /// /// let mut rng = thread_rng(); /// - /// let die = Uniform::new_inclusive(1, 6); + /// let die = Uniform::new_inclusive(1, 6).unwrap(); /// let even_number = die.map(|num| num % 2 == 0); /// while !even_number.sample(&mut rng) { /// println!("Still odd; rolling again!"); @@ -227,7 +227,7 @@ mod tests { #[test] fn test_distributions_map() { - let dist = Uniform::new_inclusive(0, 5).map(|val| val + 15); + let dist = Uniform::new_inclusive(0, 5).unwrap().map(|val| val + 15); let mut rng = crate::test::rng(212); let val = dist.sample(&mut rng); @@ -240,6 +240,7 @@ mod tests { rng: &mut R, ) -> impl Iterator + '_ { Uniform::new_inclusive(1, 6) + .unwrap() .sample_iter(rng) .filter(|x| *x != 5) .take(10) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 4cb31086..9ddce763 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -81,9 +81,9 @@ impl Distribution for Standard { // reserved for surrogates. This is the size of that gap. const GAP_SIZE: u32 = 0xDFFF - 0xD800 + 1; - // Uniform::new(0, 0x11_0000 - GAP_SIZE) can also be used but it + // Uniform::new(0, 0x11_0000 - GAP_SIZE) can also be used, but it // seemed slower. - let range = Uniform::new(GAP_SIZE, 0x11_0000); + let range = Uniform::new(GAP_SIZE, 0x11_0000).unwrap(); let mut n = range.sample(rng); if n <= 0xDFFF { diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 3302deb2..398cad18 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -75,7 +75,7 @@ impl<'a, T> Slice<'a, T> { 0 => Err(EmptySlice), len => Ok(Self { slice, - range: Uniform::new(0, len), + range: Uniform::new(0, len).unwrap(), }), } } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 49703b84..b4856ff6 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -10,9 +10,8 @@ //! A distribution uniformly sampling numbers within a given range. //! //! [`Uniform`] is the standard distribution to sample uniformly from a range; -//! e.g. `Uniform::new_inclusive(1, 6)` can sample integers from 1 to 6, like a -//! standard die. [`Rng::gen_range`] supports any type supported by -//! [`Uniform`]. +//! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a +//! standard die. [`Rng::gen_range`] supports any type supported by [`Uniform`]. //! //! This distribution is provided with support for several primitive types //! (all integer and floating-point types) as well as [`std::time::Duration`], @@ -31,7 +30,7 @@ //! use rand::distributions::Uniform; //! //! let mut rng = thread_rng(); -//! let side = Uniform::new(-10.0, 10.0); +//! let side = Uniform::new(-10.0, 10.0).unwrap(); //! //! // sample between 1 and 10 points //! for _ in 0..rng.gen_range(1..=10) { @@ -49,11 +48,11 @@ //! //! At a minimum, the back-end needs to store any parameters needed for sampling //! (e.g. the target range) and implement `new`, `new_inclusive` and `sample`. -//! Those methods should include an assert to check the range is valid (i.e. +//! Those methods should include an assertion to check the range is valid (i.e. //! `low < high`). The example below merely wraps another back-end. //! //! The `new`, `new_inclusive` and `sample_single` functions use arguments of -//! type SampleBorrow in order to support passing in values by reference or +//! type SampleBorrow to support passing in values by reference or //! by value. In the implementation of these functions, you can choose to //! simply use the reference returned by [`SampleBorrow::borrow`], or you can choose //! to copy or clone the value, whatever is appropriate for your type. @@ -61,7 +60,7 @@ //! ``` //! use rand::prelude::*; //! use rand::distributions::uniform::{Uniform, SampleUniform, -//! UniformSampler, UniformFloat, SampleBorrow}; +//! UniformSampler, UniformFloat, SampleBorrow, Error}; //! //! struct MyF32(f32); //! @@ -70,20 +69,18 @@ //! //! impl UniformSampler for UniformMyF32 { //! type X = MyF32; -//! fn new(low: B1, high: B2) -> Self +//! +//! fn new(low: B1, high: B2) -> Result //! where B1: SampleBorrow + Sized, //! B2: SampleBorrow + Sized //! { -//! UniformMyF32(UniformFloat::::new(low.borrow().0, high.borrow().0)) +//! UniformFloat::::new(low.borrow().0, high.borrow().0).map(UniformMyF32) //! } -//! fn new_inclusive(low: B1, high: B2) -> Self +//! fn new_inclusive(low: B1, high: B2) -> Result //! where B1: SampleBorrow + Sized, //! B2: SampleBorrow + Sized //! { -//! UniformMyF32(UniformFloat::::new_inclusive( -//! low.borrow().0, -//! high.borrow().0, -//! )) +//! UniformFloat::::new_inclusive(low.borrow().0, high.borrow().0).map(UniformMyF32) //! } //! fn sample(&self, rng: &mut R) -> Self::X { //! MyF32(self.0.sample(rng)) @@ -95,7 +92,7 @@ //! } //! //! let (low, high) = (MyF32(17.0f32), MyF32(22.0f32)); -//! let uniform = Uniform::new(low, high); +//! let uniform = Uniform::new(low, high).unwrap(); //! let x = uniform.sample(&mut thread_rng()); //! ``` //! @@ -106,8 +103,10 @@ //! [`UniformDuration`]: crate::distributions::uniform::UniformDuration //! [`SampleBorrow::borrow`]: crate::distributions::uniform::SampleBorrow::borrow +use core::fmt; use core::time::Duration; use core::ops::{Range, RangeInclusive}; +use core::convert::TryFrom; use crate::distributions::float::IntoFloat; use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply}; @@ -122,6 +121,28 @@ use crate::distributions::utils::Float; #[cfg(feature = "simd_support")] use core::simd::*; +/// Error type returned from [`Uniform::new`] and `new_inclusive`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// `low > high`, or equal in case of exclusive range. + EmptyRange, + /// Input or range `high - low` is non-finite. Not relevant to integer types. + NonFinite, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::EmptyRange => "low > high (or equal if exclusive) in uniform distribution", + Error::NonFinite => "Non-finite range in uniform distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -134,10 +155,10 @@ use serde::{Serialize, Deserialize}; /// /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and -/// the full range of integer types this should have comparable performance to +/// the full range of integer types, this should have comparable performance to /// the `Standard` distribution. /// -/// Steps are taken to avoid bias which might be present in naive +/// Steps are taken to avoid bias, which might be present in naive /// implementations; for example `rng.gen::() % 170` samples from the range /// `[0, 169]` but is twice as likely to select numbers less than 85 than other /// values. Further, the implementations here give more weight to the high-bits @@ -153,7 +174,7 @@ use serde::{Serialize, Deserialize}; /// ``` /// use rand::distributions::{Distribution, Uniform}; /// -/// let between = Uniform::from(10..10000); +/// let between = Uniform::try_from(10..10000).unwrap(); /// let mut rng = rand::thread_rng(); /// let mut sum = 0; /// for _ in 0..1000 { @@ -181,24 +202,30 @@ use serde::{Serialize, Deserialize}; pub struct Uniform(X::Sampler); impl Uniform { - /// Create a new `Uniform` instance which samples uniformly from the half - /// open range `[low, high)` (excluding `high`). Panics if `low >= high`. - pub fn new(low: B1, high: B2) -> Uniform + /// Create a new `Uniform` instance, which samples uniformly from the half + /// open range `[low, high)` (excluding `high`). + /// + /// Fails if `low >= high`, or if `low`, `high` or the range `high - low` is + /// non-finite. In release mode, only the range is checked. + pub fn new(low: B1, high: B2) -> Result, Error> where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - Uniform(X::Sampler::new(low, high)) + X::Sampler::new(low, high).map(Uniform) } - /// Create a new `Uniform` instance which samples uniformly from the closed - /// range `[low, high]` (inclusive). Panics if `low > high`. - pub fn new_inclusive(low: B1, high: B2) -> Uniform + /// Create a new `Uniform` instance, which samples uniformly from the closed + /// range `[low, high]` (inclusive). + /// + /// Fails if `low > high`, or if `low`, `high` or the range `high - low` is + /// non-finite. In release mode, only the range is checked. + pub fn new_inclusive(low: B1, high: B2) -> Result, Error> where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - Uniform(X::Sampler::new_inclusive(low, high)) + X::Sampler::new_inclusive(low, high).map(Uniform) } } @@ -234,22 +261,20 @@ pub trait UniformSampler: Sized { /// The type sampled by this implementation. type X; - /// Construct self, with inclusive lower bound and exclusive upper bound - /// `[low, high)`. + /// Construct self, with inclusive lower bound and exclusive upper bound `[low, high)`. /// - /// Usually users should not call this directly but instead use - /// `Uniform::new`, which asserts that `low < high` before calling this. - fn new(low: B1, high: B2) -> Self + /// Usually users should not call this directly but prefer to use + /// [`Uniform::new`]. + fn new(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized; /// Construct self, with inclusive bounds `[low, high]`. /// - /// Usually users should not call this directly but instead use - /// `Uniform::new_inclusive`, which asserts that `low <= high` before - /// calling this. - fn new_inclusive(low: B1, high: B2) -> Self + /// Usually users should not call this directly but prefer to use + /// [`Uniform::new_inclusive`]. + fn new_inclusive(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized; @@ -273,16 +298,16 @@ pub trait UniformSampler: Sized { /// # #[allow(unused)] /// fn sample_from_range(lb: T, ub: T) -> T { /// let mut rng = thread_rng(); - /// ::Sampler::sample_single(lb, ub, &mut rng) + /// ::Sampler::sample_single(lb, ub, &mut rng).unwrap() /// } /// ``` - fn sample_single(low: B1, high: B2, rng: &mut R) -> Self::X + fn sample_single(low: B1, high: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - let uniform: Self = UniformSampler::new(low, high); - uniform.sample(rng) + let uniform: Self = UniformSampler::new(low, high)?; + Ok(uniform.sample(rng)) } /// Sample a single value uniformly from a range with inclusive lower bound @@ -294,23 +319,27 @@ pub trait UniformSampler: Sized { /// via this method. /// Results may not be identical. fn sample_single_inclusive(low: B1, high: B2, rng: &mut R) - -> Self::X + -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { - let uniform: Self = UniformSampler::new_inclusive(low, high); - uniform.sample(rng) + let uniform: Self = UniformSampler::new_inclusive(low, high)?; + Ok(uniform.sample(rng)) } } -impl From> for Uniform { - fn from(r: ::core::ops::Range) -> Uniform { +impl TryFrom> for Uniform { + type Error = Error; + + fn try_from(r: ::core::ops::Range) -> Result, Error> { Uniform::new(r.start, r.end) } } -impl From> for Uniform { - fn from(r: ::core::ops::RangeInclusive) -> Uniform { +impl TryFrom> for Uniform { + type Error = Error; + + fn try_from(r: ::core::ops::RangeInclusive) -> Result, Error> { Uniform::new_inclusive(r.start(), r.end()) } } @@ -350,7 +379,7 @@ where Borrowed: SampleUniform /// for `Rng::gen_range`. pub trait SampleRange { /// Generate a sample from the given range. - fn sample_single(self, rng: &mut R) -> T; + fn sample_single(self, rng: &mut R) -> Result; /// Check whether the range is empty. fn is_empty(&self) -> bool; @@ -358,7 +387,7 @@ pub trait SampleRange { impl SampleRange for Range { #[inline] - fn sample_single(self, rng: &mut R) -> T { + fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single(self.start, self.end, rng) } @@ -370,7 +399,7 @@ impl SampleRange for Range { impl SampleRange for RangeInclusive { #[inline] - fn sample_single(self, rng: &mut R) -> T { + fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single_inclusive(self.start(), self.end(), rng) } @@ -444,30 +473,31 @@ macro_rules! uniform_int_impl { #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low < high, "Uniform::new called with `low >= high`"); + if !(low < high) { + return Err(Error::EmptyRange); + } UniformSampler::new_inclusive(low, high - 1) } #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!( - low <= high, - "Uniform::new_inclusive called with `low > high`" - ); + if !(low <= high) { + return Err(Error::EmptyRange); + } let unsigned_max = ::core::$u_large::MAX; let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned; @@ -478,12 +508,12 @@ macro_rules! uniform_int_impl { 0 }; - UniformInt { + Ok(UniformInt { low, // These are really $unsigned values, but store as $ty: range: range as $ty, z: ints_to_reject as $unsigned as $ty, - } + }) } #[inline] @@ -506,31 +536,35 @@ macro_rules! uniform_int_impl { } #[inline] - fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low < high, "UniformSampler::sample_single: low >= high"); + if !(low < high) { + return Err(Error::EmptyRange); + } Self::sample_single_inclusive(low, high - 1, rng) } #[inline] - fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low <= high, "UniformSampler::sample_single_inclusive: low > high"); + if !(low <= high) { + return Err(Error::EmptyRange); + } let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned as $u_large; // If the above resulted in wrap-around to 0, the range is $ty::MIN..=$ty::MAX, // and any integer will do. if range == 0 { - return rng.gen(); + return Ok(rng.gen()); } let zone = if ::core::$unsigned::MAX <= ::core::u16::MAX as $unsigned { @@ -550,7 +584,7 @@ macro_rules! uniform_int_impl { let v: $u_large = rng.gen(); let (hi, lo) = v.wmul(range); if lo <= zone { - return low.wrapping_add(hi as $ty); + return Ok(low.wrapping_add(hi as $ty)); } } } @@ -600,26 +634,29 @@ macro_rules! uniform_simd_int_impl { #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.simd_lt(high).all(), "Uniform::new called with `low >= high`"); + if !(low.simd_lt(high).all()) { + return Err(Error::EmptyRange); + } UniformSampler::new_inclusive(low, high - Simd::splat(1)) } #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.simd_le(high).all(), - "Uniform::new_inclusive called with `low > high`"); + if !(low.simd_le(high).all()) { + return Err(Error::EmptyRange); + } let unsigned_max = Simd::splat(::core::$unsigned::MAX); // NOTE: all `Simd` operations are inherently wrapping, @@ -636,12 +673,12 @@ macro_rules! uniform_simd_int_impl { // zero which means only one sample is needed. let zone = unsigned_max - ints_to_reject; - UniformInt { + Ok(UniformInt { low, // These are really $unsigned values, but store as $ty: range: range.cast(), z: zone.cast(), - } + }) } fn sample(&self, rng: &mut R) -> Self::X { @@ -727,7 +764,7 @@ impl UniformSampler for UniformChar { #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -735,12 +772,12 @@ impl UniformSampler for UniformChar { let low = char_to_comp_u32(*low_b.borrow()); let high = char_to_comp_u32(*high_b.borrow()); let sampler = UniformInt::::new(low, high); - UniformChar { sampler } + sampler.map(|sampler| UniformChar { sampler }) } #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -748,7 +785,7 @@ impl UniformSampler for UniformChar { let low = char_to_comp_u32(*low_b.borrow()); let high = char_to_comp_u32(*high_b.borrow()); let sampler = UniformInt::::new_inclusive(low, high); - UniformChar { sampler } + sampler.map(|sampler| UniformChar { sampler }) } fn sample(&self, rng: &mut R) -> Self::X { @@ -798,28 +835,28 @@ macro_rules! uniform_float_impl { impl UniformSampler for UniformFloat<$ty> { type X = $ty; - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - debug_assert!( - low.all_finite(), - "Uniform::new called with `low` non-finite." - ); - debug_assert!( - high.all_finite(), - "Uniform::new called with `high` non-finite." - ); - assert!(low.all_lt(high), "Uniform::new called with `low >= high`"); + #[cfg(debug_assertions)] + if !(low.all_finite()) || !(high.all_finite()) { + return Err(Error::NonFinite); + } + if !(low.all_lt(high)) { + return Err(Error::EmptyRange); + } let max_rand = <$ty>::splat( (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = high - low; - assert!(scale.all_finite(), "Uniform::new: range overflow"); + if !(scale.all_finite()) { + return Err(Error::NonFinite); + } loop { let mask = (scale * max_rand + low).ge_mask(high); @@ -831,34 +868,31 @@ macro_rules! uniform_float_impl { debug_assert!(<$ty>::splat(0.0).all_le(scale)); - UniformFloat { low, scale } + Ok(UniformFloat { low, scale }) } - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - debug_assert!( - low.all_finite(), - "Uniform::new_inclusive called with `low` non-finite." - ); - debug_assert!( - high.all_finite(), - "Uniform::new_inclusive called with `high` non-finite." - ); - assert!( - low.all_le(high), - "Uniform::new_inclusive called with `low > high`" - ); + #[cfg(debug_assertions)] + if !(low.all_finite()) || !(high.all_finite()) { + return Err(Error::NonFinite); + } + if !low.all_le(high) { + return Err(Error::EmptyRange); + } let max_rand = <$ty>::splat( (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = (high - low) / max_rand; - assert!(scale.all_finite(), "Uniform::new_inclusive: range overflow"); + if !scale.all_finite() { + return Err(Error::NonFinite); + } loop { let mask = (scale * max_rand + low).gt_mask(high); @@ -870,15 +904,14 @@ macro_rules! uniform_float_impl { debug_assert!(<$ty>::splat(0.0).all_le(scale)); - UniformFloat { low, scale } + Ok(UniformFloat { low, scale }) } fn sample(&self, rng: &mut R) -> Self::X { // Generate a value in the range [1, 2) let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - // Get a value in the range [0, 1) in order to avoid - // overflowing into infinity when multiplying with scale + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); // We don't use `f64::mul_add`, because it is not available with @@ -890,35 +923,31 @@ macro_rules! uniform_float_impl { } #[inline] - fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - debug_assert!( - low.all_finite(), - "UniformSampler::sample_single called with `low` non-finite." - ); - debug_assert!( - high.all_finite(), - "UniformSampler::sample_single called with `high` non-finite." - ); - assert!( - low.all_lt(high), - "UniformSampler::sample_single: low >= high" - ); + #[cfg(debug_assertions)] + if !low.all_finite() || !high.all_finite() { + return Err(Error::NonFinite); + } + if !low.all_lt(high) { + return Err(Error::EmptyRange); + } let mut scale = high - low; - assert!(scale.all_finite(), "UniformSampler::sample_single: range overflow"); + if !scale.all_finite() { + return Err(Error::NonFinite); + } loop { // Generate a value in the range [1, 2) let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - // Get a value in the range [0, 1) in order to avoid - // overflowing into infinity when multiplying with scale + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); // Doing multiply before addition allows some architectures @@ -927,7 +956,7 @@ macro_rules! uniform_float_impl { debug_assert!(low.all_le(res) || !scale.all_finite()); if res.all_lt(high) { - return res; + return Ok(res); } // This handles a number of edge cases. @@ -955,14 +984,13 @@ macro_rules! uniform_float_impl { // `high` are non-finite we'll end up here and can do the // appropriate checks. // - // Likewise `high - low` overflowing to infinity is also + // Likewise, `high - low` overflowing to infinity is also // rare, so handle it here after the common case. let mask = !scale.finite_mask(); if mask.any() { - assert!( - low.all_finite() && high.all_finite(), - "Uniform::sample_single: low and high must be finite" - ); + if !(low.all_finite() && high.all_finite()) { + return Err(Error::NonFinite); + } scale = scale.decrease_masked(mask); } } @@ -1027,29 +1055,30 @@ impl UniformSampler for UniformDuration { type X = Duration; #[inline] - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low < high, "Uniform::new called with `low >= high`"); + if !(low < high) { + return Err(Error::EmptyRange); + } UniformDuration::new_inclusive(low, high - Duration::new(0, 1)) } #[inline] - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!( - low <= high, - "Uniform::new_inclusive called with `low > high`" - ); + if !(low <= high) { + return Err(Error::EmptyRange); + } let low_s = low.as_secs(); let low_n = low.subsec_nanos(); @@ -1064,7 +1093,7 @@ impl UniformSampler for UniformDuration { let mode = if low_s == high_s { UniformDurationMode::Small { secs: low_s, - nanos: Uniform::new_inclusive(low_n, high_n), + nanos: Uniform::new_inclusive(low_n, high_n)?, } } else { let max = high_s @@ -1074,7 +1103,7 @@ impl UniformSampler for UniformDuration { if let Some(higher_bound) = max { let lower_bound = low_s * 1_000_000_000 + u64::from(low_n); UniformDurationMode::Medium { - nanos: Uniform::new_inclusive(lower_bound, higher_bound), + nanos: Uniform::new_inclusive(lower_bound, higher_bound)?, } } else { // An offset is applied to simplify generation of nanoseconds @@ -1082,14 +1111,14 @@ impl UniformSampler for UniformDuration { UniformDurationMode::Large { max_secs: high_s, max_nanos, - secs: Uniform::new_inclusive(low_s, high_s), + secs: Uniform::new_inclusive(low_s, high_s)?, } } }; - UniformDuration { + Ok(UniformDuration { mode, offset: low_n, - } + }) } #[inline] @@ -1109,7 +1138,7 @@ impl UniformSampler for UniformDuration { secs, } => { // constant folding means this is at least as fast as `Rng::sample(Range)` - let nano_range = Uniform::new(0, 1_000_000_000); + let nano_range = Uniform::new(0, 1_000_000_000).unwrap(); loop { let s = secs.sample(rng); let n = nano_range.sample(rng); @@ -1131,7 +1160,7 @@ mod tests { #[test] #[cfg(feature = "serde1")] fn test_serialization_uniform_duration() { - let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)); + let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); let de_distr: UniformDuration = bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); assert_eq!( distr.offset, de_distr.offset @@ -1164,39 +1193,37 @@ mod tests { #[test] #[cfg(feature = "serde1")] fn test_uniform_serialization() { - let unit_box: Uniform = Uniform::new(-1, 1); + let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); assert_eq!(unit_box.0.low, de_unit_box.0.low); assert_eq!(unit_box.0.range, de_unit_box.0.range); assert_eq!(unit_box.0.z, de_unit_box.0.z); - let unit_box: Uniform = Uniform::new(-1., 1.); + let unit_box: Uniform = Uniform::new(-1., 1.).unwrap(); let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); assert_eq!(unit_box.0.low, de_unit_box.0.low); assert_eq!(unit_box.0.scale, de_unit_box.0.scale); } - #[should_panic] #[test] fn test_uniform_bad_limits_equal_int() { - Uniform::new(10, 10); + assert_eq!(Uniform::new(10, 10), Err(Error::EmptyRange)); } #[test] fn test_uniform_good_limits_equal_int() { let mut rng = crate::test::rng(804); - let dist = Uniform::new_inclusive(10, 10); + let dist = Uniform::new_inclusive(10, 10).unwrap(); for _ in 0..20 { assert_eq!(rng.sample(dist), 10); } } - #[should_panic] #[test] fn test_uniform_bad_limits_flipped_int() { - Uniform::new(10, 5); + assert_eq!(Uniform::new(10, 5), Err(Error::EmptyRange)); } #[test] @@ -1210,37 +1237,37 @@ mod tests { macro_rules! t { ($ty:ident, $v:expr, $le:expr, $lt:expr) => {{ for &(low, high) in $v.iter() { - let my_uniform = Uniform::new(low, high); + let my_uniform = Uniform::new(low, high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $lt(v, high)); } - let my_uniform = Uniform::new_inclusive(low, high); + let my_uniform = Uniform::new_inclusive(low, high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $le(v, high)); } - let my_uniform = Uniform::new(&low, high); + let my_uniform = Uniform::new(&low, high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $lt(v, high)); } - let my_uniform = Uniform::new_inclusive(&low, &high); + let my_uniform = Uniform::new_inclusive(&low, &high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $le(v, high)); } for _ in 0..1000 { - let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng); + let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng).unwrap(); assert!($le(low, v) && $lt(v, high)); } for _ in 0..1000 { - let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng); + let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng).unwrap(); assert!($le(low, v) && $le(v, high)); } } @@ -1299,7 +1326,7 @@ mod tests { let d = Uniform::new( core::char::from_u32(0xD7F0).unwrap(), core::char::from_u32(0xE010).unwrap(), - ); + ).unwrap(); for _ in 0..100 { let c = d.sample(&mut rng); assert!((c as u32) < 0xD800 || (c as u32) > 0xDFFF); @@ -1330,27 +1357,27 @@ mod tests { for lane in 0..<$ty>::LANES { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); - let my_uniform = Uniform::new(low, high); - let my_incl_uniform = Uniform::new_inclusive(low, high); + let my_uniform = Uniform::new(low, high).unwrap(); + let my_incl_uniform = Uniform::new_inclusive(low, high).unwrap(); for _ in 0..100 { let v = rng.sample(my_uniform).extract(lane); assert!(low_scalar <= v && v < high_scalar); let v = rng.sample(my_incl_uniform).extract(lane); assert!(low_scalar <= v && v <= high_scalar); let v = <$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut rng).extract(lane); + ::sample_single(low, high, &mut rng).unwrap().extract(lane); assert!(low_scalar <= v && v < high_scalar); } assert_eq!( - rng.sample(Uniform::new_inclusive(low, low)).extract(lane), + rng.sample(Uniform::new_inclusive(low, low).unwrap()).extract(lane), low_scalar ); assert_eq!(zero_rng.sample(my_uniform).extract(lane), low_scalar); assert_eq!(zero_rng.sample(my_incl_uniform).extract(lane), low_scalar); assert_eq!(<$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut zero_rng) + ::sample_single(low, high, &mut zero_rng).unwrap() .extract(lane), low_scalar); assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar); assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); @@ -1365,7 +1392,7 @@ mod tests { ); assert!( <$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut lowering_max_rng) + ::sample_single(low, high, &mut lowering_max_rng).unwrap() .extract(lane) < high_scalar ); } @@ -1376,14 +1403,14 @@ mod tests { rng.sample(Uniform::new_inclusive( ::core::$f_scalar::MAX, ::core::$f_scalar::MAX - )), + ).unwrap()), ::core::$f_scalar::MAX ); assert_eq!( rng.sample(Uniform::new_inclusive( -::core::$f_scalar::MAX, -::core::$f_scalar::MAX - )), + ).unwrap()), -::core::$f_scalar::MAX ); }}; @@ -1404,9 +1431,8 @@ mod tests { } #[test] - #[should_panic] fn test_float_overflow() { - let _ = Uniform::from(::core::f64::MIN..::core::f64::MAX); + assert_eq!(Uniform::try_from(::core::f64::MIN..::core::f64::MAX), Err(Error::NonFinite)); } #[test] @@ -1427,7 +1453,7 @@ mod tests { use std::panic::catch_unwind; fn range(low: T, high: T) { let mut rng = crate::test::rng(253); - T::Sampler::sample_single(low, high, &mut rng); + T::Sampler::sample_single(low, high, &mut rng).unwrap(); } macro_rules! t { @@ -1454,10 +1480,10 @@ mod tests { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); assert!(catch_unwind(|| range(low, high)).is_err()); - assert!(catch_unwind(|| Uniform::new(low, high)).is_err()); - assert!(catch_unwind(|| Uniform::new_inclusive(low, high)).is_err()); + assert!(Uniform::new(low, high).is_err()); + assert!(Uniform::new_inclusive(low, high).is_err()); assert!(catch_unwind(|| range(low, low)).is_err()); - assert!(catch_unwind(|| Uniform::new(low, low)).is_err()); + assert!(Uniform::new(low, low).is_err()); } } }}; @@ -1492,7 +1518,7 @@ mod tests { ), ]; for &(low, high) in v.iter() { - let my_uniform = Uniform::new(low, high); + let my_uniform = Uniform::new(low, high).unwrap(); for _ in 0..1000 { let v = rng.sample(my_uniform); assert!(low <= v && v < high); @@ -1514,15 +1540,15 @@ mod tests { impl UniformSampler for UniformMyF32 { type X = MyF32; - fn new(low: B1, high: B2) -> Self + fn new(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - UniformMyF32(UniformFloat::::new(low.borrow().x, high.borrow().x)) + UniformFloat::::new(low.borrow().x, high.borrow().x).map(UniformMyF32) } - fn new_inclusive(low: B1, high: B2) -> Self + fn new_inclusive(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -1541,7 +1567,7 @@ mod tests { } let (low, high) = (MyF32 { x: 17.0f32 }, MyF32 { x: 22.0f32 }); - let uniform = Uniform::new(low, high); + let uniform = Uniform::new(low, high).unwrap(); let mut rng = crate::test::rng(804); for _ in 0..100 { let x: MyF32 = rng.sample(uniform); @@ -1551,20 +1577,20 @@ mod tests { #[test] fn test_uniform_from_std_range() { - let r = Uniform::from(2u32..7); + let r = Uniform::try_from(2u32..7).unwrap(); assert_eq!(r.0.low, 2); assert_eq!(r.0.range, 5); - let r = Uniform::from(2.0f64..7.0); + let r = Uniform::try_from(2.0f64..7.0).unwrap(); 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); + let r = Uniform::try_from(2u32..=6).unwrap(); assert_eq!(r.0.low, 2); assert_eq!(r.0.range, 5); - let r = Uniform::from(2.0f64..=7.0); + let r = Uniform::try_from(2.0f64..=7.0).unwrap(); assert_eq!(r.0.low, 2.0); assert!(r.0.scale > 5.0); assert!(r.0.scale < 5.0 + 1e-14); @@ -1579,11 +1605,11 @@ mod tests { let mut buf = [lb; 3]; for x in &mut buf { - *x = T::Sampler::sample_single(lb, ub, &mut rng); + *x = T::Sampler::sample_single(lb, ub, &mut rng).unwrap(); } assert_eq!(&buf, expected_single); - let distr = Uniform::new(lb, ub); + let distr = Uniform::new(lb, ub).unwrap(); for x in &mut buf { *x = rng.sample(&distr); } @@ -1626,9 +1652,9 @@ mod tests { #[test] fn uniform_distributions_can_be_compared() { - assert_eq!(Uniform::new(1.0, 2.0), Uniform::new(1.0, 2.0)); + assert_eq!(Uniform::new(1.0, 2.0).unwrap(), Uniform::new(1.0, 2.0).unwrap()); // To cover UniformInt - assert_eq!(Uniform::new(1 as u32, 2 as u32), Uniform::new(1 as u32, 2 as u32)); + assert_eq!(Uniform::new(1 as u32, 2 as u32).unwrap(), Uniform::new(1 as u32, 2 as u32).unwrap()); } } diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 0131ed15..4c57edc5 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -123,7 +123,7 @@ impl WeightedIndex { if total_weight == zero { return Err(WeightedError::AllWeightsZero); } - let distr = X::Sampler::new(zero, total_weight.clone()); + let distr = X::Sampler::new(zero, total_weight.clone()).unwrap(); Ok(WeightedIndex { cumulative_weights: weights, @@ -220,7 +220,7 @@ impl WeightedIndex { } self.total_weight = total_weight; - self.weight_distribution = X::Sampler::new(zero, self.total_weight.clone()); + self.weight_distribution = X::Sampler::new(zero, self.total_weight.clone()).unwrap(); Ok(()) } @@ -230,7 +230,6 @@ impl Distribution for WeightedIndex where X: SampleUniform + PartialOrd { fn sample(&self, rng: &mut R) -> usize { - use ::core::cmp::Ordering; let chosen_weight = self.weight_distribution.sample(rng); // Find the first item which has a weight *higher* than the chosen weight. self.cumulative_weights.partition_point(|w| w <= &chosen_weight) diff --git a/src/rng.rs b/src/rng.rs index c9f3a5f7..1b53298d 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -130,7 +130,7 @@ pub trait Rng: RngCore { R: SampleRange { assert!(!range.is_empty(), "cannot sample empty range"); - range.sample_single(self) + range.sample_single(self).unwrap() } /// Sample a new value, using the given distribution. @@ -142,10 +142,10 @@ pub trait Rng: RngCore { /// use rand::distributions::Uniform; /// /// let mut rng = thread_rng(); - /// let x = rng.sample(Uniform::new(10u32, 15)); + /// let x = rng.sample(Uniform::new(10u32, 15).unwrap()); /// // Type annotation requires two types, the type and distribution; the /// // distribution can be inferred. - /// let y = rng.sample::(Uniform::new(10, 15)); + /// let y = rng.sample::(Uniform::new(10, 15).unwrap()); /// ``` fn sample>(&mut self, distr: D) -> T { distr.sample(self) @@ -181,7 +181,7 @@ pub trait Rng: RngCore { /// .collect::>()); /// /// // Dice-rolling: - /// let die_range = Uniform::new_inclusive(1, 6); + /// let die_range = Uniform::new_inclusive(1, 6).unwrap(); /// let mut roll_die = (&mut rng).sample_iter(die_range); /// while roll_die.next().unwrap() != 6 { /// println!("Not a 6; rolling again!"); diff --git a/src/seq/index.rs b/src/seq/index.rs index b7bc6a9b..50523cc4 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -479,7 +479,7 @@ where let mut cache = HashSet::with_capacity(amount.as_usize()); #[cfg(not(feature = "std"))] let mut cache = BTreeSet::new(); - let distr = Uniform::new(X::zero(), length); + let distr = Uniform::new(X::zero(), length).unwrap(); let mut indices = Vec::with_capacity(amount.as_usize()); for _ in 0..amount.as_usize() { let mut pos = distr.sample(rng); From f2e21db9713413d390106d8d9a718ec0fc9d20a9 Mon Sep 17 00:00:00 2001 From: Oliver Tearne Date: Mon, 13 Feb 2023 09:08:01 +0000 Subject: [PATCH 51/54] Poisson returns -1 for small lambda (#1284) * Correct Knuth's method since not using do-while --- rand_distr/CHANGELOG.md | 1 + rand_distr/src/poisson.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 13dac1c8..50cd913b 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. - Upgrade Rand +- Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 8b9bffd0..b0b3d15a 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -89,8 +89,8 @@ where F: Float + FloatConst, Standard: Distribution // for low expected values use the Knuth method if self.lambda < F::from(12.0).unwrap() { - let mut result = F::zero(); - let mut p = F::one(); + let mut result = F::one(); + let mut p = rng.gen::(); while p > self.exp_lambda { p = p*rng.gen::(); result = result + F::one(); @@ -161,10 +161,15 @@ mod test { #[test] fn test_poisson_avg() { - test_poisson_avg_gen::(10.0, 0.5); - test_poisson_avg_gen::(15.0, 0.5); - test_poisson_avg_gen::(10.0, 0.5); - test_poisson_avg_gen::(15.0, 0.5); + test_poisson_avg_gen::(10.0, 0.1); + test_poisson_avg_gen::(15.0, 0.1); + + test_poisson_avg_gen::(10.0, 0.1); + test_poisson_avg_gen::(15.0, 0.1); + + //Small lambda will use Knuth's method with exp_lambda == 1.0 + test_poisson_avg_gen::(0.00000000000000005, 0.1); + test_poisson_avg_gen::(0.00000000000000005, 0.1); } #[test] From 7c1e649ea1b8074046712e07272d18a3d85bcc71 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Mon, 20 Feb 2023 10:28:23 +0000 Subject: [PATCH 52/54] Rework CryptoRng (#1273) --- rand_chacha/src/chacha.rs | 16 ++++++++-------- rand_core/src/block.rs | 14 +++++++++++--- rand_core/src/lib.rs | 35 +++-------------------------------- src/rngs/adapter/reseeding.rs | 12 ++++++------ 4 files changed, 28 insertions(+), 49 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index ad74b35f..ebc28a8a 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -13,7 +13,7 @@ use self::core::fmt; use crate::guts::ChaCha; -use rand_core::block::{BlockRng, BlockRngCore}; +use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize, Serializer, Deserializer}; @@ -99,7 +99,7 @@ macro_rules! chacha_impl { } } - impl CryptoRng for $ChaChaXCore {} + impl CryptoBlockRng for $ChaChaXCore {} /// A cryptographically secure random number generator that uses the ChaCha algorithm. /// @@ -626,12 +626,12 @@ mod test { #[test] fn test_trait_objects() { - use rand_core::CryptoRngCore; + use rand_core::CryptoRng; - let rng = &mut ChaChaRng::from_seed(Default::default()) as &mut dyn CryptoRngCore; - let r1 = rng.next_u64(); - let rng: &mut dyn RngCore = rng.as_rngcore(); - let r2 = rng.next_u64(); - assert_ne!(r1, r2); + let mut rng1 = ChaChaRng::from_seed(Default::default()); + let rng2 = &mut rng1.clone() as &mut dyn CryptoRng; + for _ in 0..1000 { + assert_eq!(rng1.next_u64(), rng2.next_u64()); + } } } diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index f813784f..5ad459d0 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -43,7 +43,7 @@ //! } //! } //! -//! // optionally, also implement CryptoRng for MyRngCore +//! // optionally, also implement CryptoBlockRng for MyRngCore //! //! // Final RNG. //! let mut rng = BlockRng::::seed_from_u64(0); @@ -54,7 +54,7 @@ //! [`fill_bytes`]: RngCore::fill_bytes use crate::impls::{fill_via_u32_chunks, fill_via_u64_chunks}; -use crate::{CryptoRng, Error, RngCore, SeedableRng}; +use crate::{Error, CryptoRng, RngCore, SeedableRng}; use core::convert::AsRef; use core::fmt; #[cfg(feature = "serde1")] @@ -77,6 +77,12 @@ pub trait BlockRngCore { fn generate(&mut self, results: &mut Self::Results); } +/// A marker trait used to indicate that an [`RngCore`] implementation is +/// supposed to be cryptographically secure. +/// +/// See [`CryptoRng`][crate::CryptoRng] docs for more information. +pub trait CryptoBlockRng: BlockRngCore { } + /// A wrapper type implementing [`RngCore`] for some type implementing /// [`BlockRngCore`] with `u32` array buffer; i.e. this can be used to implement /// a full RNG from just a `generate` function. @@ -256,6 +262,8 @@ impl SeedableRng for BlockRng { } } +impl> CryptoRng for BlockRng {} + /// A wrapper type implementing [`RngCore`] for some type implementing /// [`BlockRngCore`] with `u64` array buffer; i.e. this can be used to implement /// a full RNG from just a `generate` function. @@ -422,7 +430,7 @@ impl SeedableRng for BlockRng64 { } } -impl CryptoRng for BlockRng {} +impl> CryptoRng for BlockRng64 {} #[cfg(test)] mod test { diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 70baf78d..9a6c0baa 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -191,8 +191,8 @@ pub trait RngCore { } } -/// A marker trait used to indicate that an [`RngCore`] or [`BlockRngCore`] -/// implementation is supposed to be cryptographically secure. +/// A marker trait used to indicate that an [`RngCore`] implementation is +/// supposed to be cryptographically secure. /// /// *Cryptographically secure generators*, also known as *CSPRNGs*, should /// satisfy an additional properties over other generators: given the first @@ -213,36 +213,7 @@ pub trait RngCore { /// weaknesses such as seeding from a weak entropy source or leaking state. /// /// [`BlockRngCore`]: block::BlockRngCore -pub trait CryptoRng {} - -/// An extension trait that is automatically implemented for any type -/// implementing [`RngCore`] and [`CryptoRng`]. -/// -/// It may be used as a trait object, and supports upcasting to [`RngCore`] via -/// the [`CryptoRngCore::as_rngcore`] method. -/// -/// # Example -/// -/// ``` -/// use rand_core::CryptoRngCore; -/// -/// #[allow(unused)] -/// fn make_token(rng: &mut dyn CryptoRngCore) -> [u8; 32] { -/// let mut buf = [0u8; 32]; -/// rng.fill_bytes(&mut buf); -/// buf -/// } -/// ``` -pub trait CryptoRngCore: CryptoRng + RngCore { - /// Upcast to an [`RngCore`] trait object. - fn as_rngcore(&mut self) -> &mut dyn RngCore; -} - -impl CryptoRngCore for T { - fn as_rngcore(&mut self) -> &mut dyn RngCore { - self - } -} +pub trait CryptoRng: RngCore {} /// A random number generator that can be explicitly seeded. /// diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index b78b850a..a47ab7c7 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -12,7 +12,7 @@ use core::mem::size_of; -use rand_core::block::{BlockRng, BlockRngCore}; +use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the @@ -147,8 +147,8 @@ where impl CryptoRng for ReseedingRng where - R: BlockRngCore + SeedableRng + CryptoRng, - Rsdr: RngCore + CryptoRng, + R: BlockRngCore + SeedableRng + CryptoBlockRng, + Rsdr: CryptoRng, { } @@ -276,10 +276,10 @@ where } } -impl CryptoRng for ReseedingCore +impl CryptoBlockRng for ReseedingCore where - R: BlockRngCore + SeedableRng + CryptoRng, - Rsdr: RngCore + CryptoRng, + R: BlockRngCore + SeedableRng + CryptoBlockRng, + Rsdr: CryptoRng, { } From 17c8b2625ceef3f958087e8b10a4d19ddd53bcc5 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 27 Feb 2023 00:07:03 -0800 Subject: [PATCH 53/54] Don't run the random write test (#1294) --- rand_core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 9a6c0baa..a5a7fb15 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -451,7 +451,7 @@ impl RngCore for Box { /// /// # Examples /// -/// ```rust +/// ```no_run /// # use std::{io, io::Read}; /// # use std::fs::File; /// # use rand_core::{OsRng, RngCore}; From 0f3ecedef0ea4445a38493fa3d4173b49b3574f9 Mon Sep 17 00:00:00 2001 From: Thomas Dupic <32767097+Thopic@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:08:13 -0500 Subject: [PATCH 54/54] =?UTF-8?q?Poisson=20distribution=20falls=20into=20a?= =?UTF-8?q?n=20infinite=20loop=20for=20parameter=20=CE=BB=3D=E2=88=9E=20(#?= =?UTF-8?q?1290)=20(#1291)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rand_distr/CHANGELOG.md | 1 + rand_distr/src/poisson.rs | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 50cd913b..d1da24a5 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This breaks serialization compatibility with older versions. - Upgrade Rand - Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda +- Fix `Poisson` distribution instantiation so it return an error if lambda is infinite ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index b0b3d15a..199313a2 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -44,14 +44,17 @@ where F: Float + FloatConst, Standard: Distribution /// Error type returned from `Poisson::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { - /// `lambda <= 0` or `nan`. + /// `lambda <= 0` ShapeTooSmall, + /// `lambda = ∞` or `lambda = nan` + NonFinite, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Error::ShapeTooSmall => "lambda is not positive in Poisson distribution", + Error::NonFinite => "lambda is infinite or nan in Poisson distribution", }) } } @@ -66,6 +69,9 @@ where F: Float + FloatConst, Standard: Distribution /// Construct a new `Poisson` with the given shape parameter /// `lambda`. pub fn new(lambda: F) -> Result, Error> { + if !lambda.is_finite() { + return Err(Error::NonFinite); + } if !(lambda > F::zero()) { return Err(Error::ShapeTooSmall); } @@ -163,7 +169,7 @@ mod test { fn test_poisson_avg() { test_poisson_avg_gen::(10.0, 0.1); test_poisson_avg_gen::(15.0, 0.1); - + test_poisson_avg_gen::(10.0, 0.1); test_poisson_avg_gen::(15.0, 0.1); @@ -178,6 +184,12 @@ mod test { Poisson::new(0.0).unwrap(); } + #[test] + #[should_panic] + fn test_poisson_invalid_lambda_infinity() { + Poisson::new(f64::INFINITY).unwrap(); + } + #[test] #[should_panic] fn test_poisson_invalid_lambda_neg() { @@ -188,4 +200,4 @@ mod test { fn poisson_distributions_can_be_compared() { assert_eq!(Poisson::new(1.0), Poisson::new(1.0)); } -} \ No newline at end of file +}