Skip to content

Commit

Permalink
SmallRng: Replace PCG algorithm with xoshiro{128,256}++
Browse files Browse the repository at this point in the history
Due to close correlations of PCG streams (rust-random#907) and lack of right-state
propagation (rust-random#905), the `SmallRng` algorithm is switched to
xoshiro{128,256}++. The implementation is taken from the `rand_xoshiro`
crate and slightly simplified.

Fixes rust-random#910.
  • Loading branch information
vks committed Sep 15, 2020
1 parent 54b77d8 commit b41b4fd
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 14 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ simd_support = ["packed_simd"]
std_rng = ["rand_chacha", "rand_hc"]

# Option: enable SmallRng
small_rng = ["rand_pcg"]
small_rng = []

[workspace]
members = [
Expand All @@ -56,7 +56,6 @@ members = [

[dependencies]
rand_core = { path = "rand_core", version = "0.5.1" }
rand_pcg = { path = "rand_pcg", version = "0.2.1", optional = true }
log = { version = "0.4.4", optional = true }
serde = { version = "1.0.103", features = ["derive"], optional = true }

Expand Down
6 changes: 6 additions & 0 deletions src/rngs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@

pub mod mock; // Public so we don't export `StepRng` directly, making it a bit
// more clear it is intended for testing.

#[cfg(all(feature = "small_rng", target_pointer_width = "64"))]
mod xoshiro256plusplus;
#[cfg(all(feature = "small_rng", not(target_pointer_width = "64")))]
mod xoshiro128plusplus;
#[cfg(feature = "small_rng")] mod small;

#[cfg(feature = "std_rng")] mod std;
#[cfg(all(feature = "std", feature = "std_rng"))] pub(crate) mod thread;

Expand Down
23 changes: 11 additions & 12 deletions src/rngs/small.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
use rand_core::{Error, RngCore, SeedableRng};

#[cfg(all(not(target_os = "emscripten"), target_pointer_width = "64"))]
type Rng = rand_pcg::Pcg64Mcg;
#[cfg(not(all(not(target_os = "emscripten"), target_pointer_width = "64")))]
type Rng = rand_pcg::Pcg32;
#[cfg(target_pointer_width = "64")]
type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus;
#[cfg(not(target_pointer_width = "64"))]
type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus;

/// A small-state, fast non-crypto PRNG
///
Expand All @@ -25,15 +25,14 @@ type Rng = rand_pcg::Pcg32;
/// The algorithm is deterministic but should not be considered reproducible
/// due to dependence on platform and possible replacement in future
/// library versions. For a reproducible generator, use a named PRNG from an
/// external crate, e.g. [rand_pcg] or [rand_chacha].
/// external crate, e.g. [rand_xoshiro] or [rand_chacha].
/// Refer also to [The Book](https://rust-random.github.io/book/guide-rngs.html).
///
/// The PRNG algorithm in `SmallRng` is chosen to be
/// efficient on the current platform, without consideration for cryptography
/// or security. The size of its state is much smaller than [`StdRng`].
/// The current algorithm is [`Pcg64Mcg`](rand_pcg::Pcg64Mcg) on 64-bit
/// platforms and [`Pcg32`](rand_pcg::Pcg32) on 32-bit platforms. Both are
/// implemented by the [rand_pcg] crate.
/// The PRNG algorithm in `SmallRng` is chosen to be efficient on the current
/// platform, without consideration for cryptography or security. The size of
/// its state is much smaller than [`StdRng`]. The current algorithm is
/// `Xoshiro256PlusPlus` on 64-bit platforms and `Xoshiro128PlusPlus` on 32-bit
/// platforms. Both are implemented by the [rand_xoshiro] crate.
///
/// # Examples
///
Expand Down Expand Up @@ -69,7 +68,7 @@ type Rng = rand_pcg::Pcg32;
/// [`StdRng`]: crate::rngs::StdRng
/// [`thread_rng`]: crate::thread_rng
/// [rand_chacha]: https://crates.io/crates/rand_chacha
/// [rand_pcg]: https://crates.io/crates/rand_pcg
/// [rand_xoshiro]: https://crates.io/crates/rand_pcg
#[cfg_attr(doc_cfg, doc(cfg(feature = "small_rng")))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SmallRng(Rng);
Expand Down
101 changes: 101 additions & 0 deletions src/rngs/xoshiro128plusplus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[cfg(feature="serde1")] use serde::{Serialize, Deserialize};
use rand_core::impls::{next_u64_via_u32, fill_bytes_via_next};
use rand_core::le::read_u32_into;
use rand_core::{SeedableRng, RngCore, Error};

/// A xoshiro128++ random number generator.
///
/// The xoshiro128++ algorithm is not suitable for cryptographic purposes, but
/// is very fast and has excellent statistical properties.
///
/// The algorithm used here is translated from [the `xoshiro128plusplus.c`
/// reference source code](http://xoshiro.di.unimi.it/xoshiro128plusplus.c) by
/// David Blackman and Sebastiano Vigna.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
pub struct Xoshiro128PlusPlus {
s: [u32; 4],
}

impl SeedableRng for Xoshiro128PlusPlus {
type Seed = [u8; 16];

/// Create a new `Xoshiro128PlusPlus`. If `seed` is entirely 0, it will be
/// mapped to a different seed.
#[inline]
fn from_seed(seed: [u8; 16]) -> Xoshiro128PlusPlus {
if seed.iter().all(|&x| x == 0) {
return Self::seed_from_u64(0);
}
let mut state = [0; 4];
read_u32_into(&seed, &mut state);
Xoshiro128PlusPlus { s: state }
}
}

impl RngCore for Xoshiro128PlusPlus {
#[inline]
fn next_u32(&mut self) -> u32 {
let result_starstar = self.s[0]
.wrapping_add(self.s[3])
.rotate_left(7)
.wrapping_add(self.s[0]);

let t = self.s[1] << 9;

self.s[2] ^= self.s[0];
self.s[3] ^= self.s[1];
self.s[1] ^= self.s[2];
self.s[0] ^= self.s[3];

self.s[2] ^= t;

self.s[3] = self.s[3].rotate_left(11);

result_starstar
}

#[inline]
fn next_u64(&mut self) -> u64 {
next_u64_via_u32(self)
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
fill_bytes_via_next(self, dest);
}

#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn reference() {
let mut rng = Xoshiro128PlusPlus::from_seed(
[1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]);
// These values were produced with the reference implementation:
// http://xoshiro.di.unimi.it/xoshiro128plusplus.c
let expected = [
641, 1573767, 3222811527, 3517856514, 836907274, 4247214768,
3867114732, 1355841295, 495546011, 621204420,
];
for &e in &expected {
assert_eq!(rng.next_u32(), e);
}
}
}
103 changes: 103 additions & 0 deletions src/rngs/xoshiro256plusplus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[cfg(feature="serde1")] use serde::{Serialize, Deserialize};
use rand_core::impls::fill_bytes_via_next;
use rand_core::le::read_u64_into;
use rand_core::{SeedableRng, RngCore, Error};

/// A xoshiro256** random number generator.
///
/// The xoshiro256** algorithm is not suitable for cryptographic purposes, but
/// is very fast and has excellent statistical properties.
///
/// The algorithm used here is translated from [the `xoshiro256plusplus.c`
/// reference source code](http://xoshiro.di.unimi.it/xoshiro256plusplus.c) by
/// David Blackman and Sebastiano Vigna.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))]
pub struct Xoshiro256PlusPlus {
s: [u64; 4],
}

impl SeedableRng for Xoshiro256PlusPlus {
type Seed = [u8; 32];

/// Create a new `Xoshiro256PlusPlus`. If `seed` is entirely 0, it will be
/// mapped to a different seed.
#[inline]
fn from_seed(seed: [u8; 32]) -> Xoshiro256PlusPlus {
if seed.iter().all(|&x| x == 0) {
return Self::seed_from_u64(0);
}
let mut state = [0; 4];
read_u64_into(&seed, &mut state);
Xoshiro256PlusPlus { s: state }
}
}

impl RngCore for Xoshiro256PlusPlus {
#[inline]
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}

#[inline]
fn next_u64(&mut self) -> u64 {
let result_plusplus = self.s[0]
.wrapping_add(self.s[3])
.rotate_left(23)
.wrapping_add(self.s[0]);

let t = self.s[1] << 17;

self.s[2] ^= self.s[0];
self.s[3] ^= self.s[1];
self.s[1] ^= self.s[2];
self.s[0] ^= self.s[3];

self.s[2] ^= t;

self.s[3] = self.s[3].rotate_left(45);

result_plusplus
}

#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
fill_bytes_via_next(self, dest);
}

#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn reference() {
let mut rng = Xoshiro256PlusPlus::from_seed(
[1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]);
// These values were produced with the reference implementation:
// http://xoshiro.di.unimi.it/xoshiro256plusplus.c
let expected = [
41943041, 58720359, 3588806011781223, 3591011842654386,
9228616714210784205, 9973669472204895162, 14011001112246962877,
12406186145184390807, 15849039046786891736, 10450023813501588000,
];
for &e in &expected {
assert_eq!(rng.next_u64(), e);
}
}
}

0 comments on commit b41b4fd

Please sign in to comment.