Skip to content

Commit

Permalink
Merge pull request #537 from dhardy/from_u64_stable
Browse files Browse the repository at this point in the history
Implement SeedableRng::seed_from_u64
  • Loading branch information
dhardy authored Sep 3, 2018
2 parents 523058f + 16291b6 commit 24fbd1f
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 49 deletions.
8 changes: 8 additions & 0 deletions rand_core/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng<R> {
Self::new(R::from_seed(seed))
}

fn seed_from_u64(seed: u64) -> Self {
Self::new(R::seed_from_u64(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
Ok(Self::new(R::from_rng(rng)?))
}
Expand Down Expand Up @@ -494,6 +498,10 @@ impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng64<R> {
Self::new(R::from_seed(seed))
}

fn seed_from_u64(seed: u64) -> Self {
Self::new(R::seed_from_u64(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
Ok(Self::new(R::from_rng(rng)?))
}
Expand Down
85 changes: 84 additions & 1 deletion rand_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

use core::default::Default;
use core::convert::AsMut;
use core::ptr::copy_nonoverlapping;

#[cfg(all(feature="alloc", not(feature="std")))] use alloc::boxed::Box;

Expand Down Expand Up @@ -297,7 +298,46 @@ pub trait SeedableRng: Sized {
/// for example `0xBAD5EEDu32` or `0x0DDB1A5E5BAD5EEDu64` ("odd biases? bad
/// seed"). This is assuming only a small number of values must be rejected.
fn from_seed(seed: Self::Seed) -> Self;


/// Create a new PRNG using a `u64` seed.
///
/// This is a convenience-wrapper around `from_seed` to allow construction
/// of any `SeedableRng` from a simple `u64` value. It is designed such that
/// low Hamming Weight numbers like 0 and 1 can be used and should still
/// result in good, independent seeds to the PRNG which is returned.
///
/// This **is not suitable for cryptography**, as should be clear given that
/// the input size is only 64 bits.
///
/// Implementations for PRNGs *may* provide their own implementations of
/// this function, but the default implementation should be good enough for
/// all purposes. *Changing* the implementation of this function should be
/// considered a value-breaking change.
fn seed_from_u64(mut state: u64) -> Self {
// We use PCG32 to generate a u32 sequence, and copy to the seed
const MUL: u64 = 6364136223846793005;
const INC: u64 = 11634580027462260723;

let mut seed = Self::Seed::default();
for chunk in seed.as_mut().chunks_mut(4) {
// We advance the state first (to get away from the input value,
// in case it has low Hamming Weight).
state = state.wrapping_mul(MUL).wrapping_add(INC);

// Use PCG output function with to_le to generate x:
let xorshifted = (((state >> 18) ^ state) >> 27) as u32;
let rot = (state >> 59) as u32;
let x = xorshifted.rotate_right(rot).to_le();

unsafe {
let p = &x as *const u32 as *const u8;
copy_nonoverlapping(p, chunk.as_mut_ptr(), chunk.len());
}
}

Self::from_seed(seed)
}

/// Create a new PRNG seeded from another `Rng`.
///
/// This is the recommended way to initialize PRNGs with fresh entropy. The
Expand Down Expand Up @@ -402,3 +442,46 @@ impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {}
// Implement `CryptoRng` for boxed references to an `CryptoRng`.
#[cfg(feature="alloc")]
impl<R: CryptoRng + ?Sized> CryptoRng for Box<R> {}

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

#[test]
fn test_seed_from_u64() {
struct SeedableNum(u64);
impl SeedableRng for SeedableNum {
type Seed = [u8; 8];
fn from_seed(seed: Self::Seed) -> Self {
let mut x = [0u64; 1];
le::read_u64_into(&seed, &mut x);
SeedableNum(x[0])
}
}

const N: usize = 8;
const SEEDS: [u64; N] = [0u64, 1, 2, 3, 4, 8, 16, -1i64 as u64];
let mut results = [0u64; N];
for (i, seed) in SEEDS.iter().enumerate() {
let SeedableNum(x) = SeedableNum::seed_from_u64(*seed);
results[i] = x;
}

for (i1, r1) in results.iter().enumerate() {
let weight = r1.count_ones();
// This is the binomial distribution B(64, 0.5), so chance of
// weight < 20 is binocdf(19, 64, 0.5) = 7.8e-4, and same for
// weight > 44.
assert!(weight >= 20 && weight <= 44);

for (i2, r2) in results.iter().enumerate() {
if i1 == i2 { continue; }
let diff_weight = (r1 ^ r2).count_ones();
assert!(diff_weight >= 20);
}
}

// value-breakage test:
assert_eq!(results[0], 5029875928683246316);
}
}
16 changes: 1 addition & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,21 +1061,7 @@ mod test {
}

pub fn rng(seed: u64) -> TestRng<StdRng> {
// TODO: use from_hashable
let mut state = seed;
let mut seed = <StdRng as SeedableRng>::Seed::default();
for x in seed.iter_mut() {
// PCG algorithm
const MUL: u64 = 6364136223846793005;
const INC: u64 = 11634580027462260723;
let oldstate = state;
state = oldstate.wrapping_mul(MUL).wrapping_add(INC);

let xorshifted = (((oldstate >> 18) ^ oldstate) >> 27) as u32;
let rot = (oldstate >> 59) as u32;
*x = xorshifted.rotate_right(rot) as u8;
}
TestRng { inner: StdRng::from_seed(seed) }
TestRng { inner: StdRng::seed_from_u64(seed) }
}

#[test]
Expand Down
48 changes: 28 additions & 20 deletions src/prng/isaac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ impl SeedableRng for IsaacRng {
fn from_seed(seed: Self::Seed) -> Self {
IsaacRng(BlockRng::<IsaacCore>::from_seed(seed))
}

/// Create an ISAAC random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
fn seed_from_u64(seed: u64) -> Self {
IsaacRng(BlockRng::<IsaacCore>::seed_from_u64(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
BlockRng::<IsaacCore>::from_rng(rng).map(|rng| IsaacRng(rng))
Expand All @@ -133,14 +140,15 @@ impl IsaacRng {
/// DEPRECATED. `IsaacRng::new_from_u64(0)` will produce identical results.
#[deprecated(since="0.5.0", note="use the FromEntropy or SeedableRng trait")]
pub fn new_unseeded() -> Self {
Self::new_from_u64(0)
Self::seed_from_u64(0)
}

/// Create an ISAAC random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
#[deprecated(since="0.6.0", note="use SeedableRng::seed_from_u64 instead")]
pub fn new_from_u64(seed: u64) -> Self {
IsaacRng(BlockRng::new(IsaacCore::new_from_u64(seed)))
Self::seed_from_u64(seed)
}
}

Expand Down Expand Up @@ -311,22 +319,6 @@ impl IsaacCore {

Self { mem, a: w(0), b: w(0), c: w(0) }
}

/// Create an ISAAC random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
fn new_from_u64(seed: u64) -> Self {
let mut key = [w(0); RAND_SIZE];
key[0] = w(seed as u32);
key[1] = w((seed >> 32) as u32);
// Initialize with only one pass.
// A second pass does not improve the quality here, because all of the
// seed was already available in the first round.
// Not doing the second pass has the small advantage that if
// `seed == 0` this method produces exactly the same state as the
// reference implementation when used unseeded.
Self::init(key, 1)
}
}

impl SeedableRng for IsaacCore {
Expand All @@ -342,6 +334,22 @@ impl SeedableRng for IsaacCore {
}
Self::init(seed_extended, 2)
}

/// Create an ISAAC random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
fn seed_from_u64(seed: u64) -> Self {
let mut key = [w(0); RAND_SIZE];
key[0] = w(seed as u32);
key[1] = w((seed >> 32) as u32);
// Initialize with only one pass.
// A second pass does not improve the quality here, because all of the
// seed was already available in the first round.
// Not doing the second pass has the small advantage that if
// `seed == 0` this method produces exactly the same state as the
// reference implementation when used unseeded.
Self::init(key, 1)
}

fn from_rng<R: RngCore>(mut rng: R) -> Result<Self, Error> {
// Custom `from_rng` implementation that fills a seed with the same size
Expand Down Expand Up @@ -435,11 +443,11 @@ mod test {
#[test]
fn test_isaac_new_uninitialized() {
// Compare the results from initializing `IsaacRng` with
// `new_from_u64(0)`, to make sure it is the same as the reference
// `seed_from_u64(0)`, to make sure it is the same as the reference
// implementation when used uninitialized.
// Note: We only test the first 16 integers, not the full 256 of the
// first block.
let mut rng = IsaacRng::new_from_u64(0);
let mut rng = IsaacRng::seed_from_u64(0);
let mut results = [0u32; 16];
for i in results.iter_mut() { *i = rng.next_u32(); }
let expected: [u32; 16] = [
Expand Down
39 changes: 26 additions & 13 deletions src/prng/isaac64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ impl SeedableRng for Isaac64Rng {
Isaac64Rng(BlockRng64::<Isaac64Core>::from_seed(seed))
}

/// Create an ISAAC random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
fn seed_from_u64(seed: u64) -> Self {
Isaac64Rng(BlockRng64::<Isaac64Core>::seed_from_u64(seed))
}

fn from_rng<S: RngCore>(rng: S) -> Result<Self, Error> {
BlockRng64::<Isaac64Core>::from_rng(rng).map(|rng| Isaac64Rng(rng))
}
Expand All @@ -123,14 +130,15 @@ impl Isaac64Rng {
/// DEPRECATED. `Isaac64Rng::new_from_u64(0)` will produce identical results.
#[deprecated(since="0.5.0", note="use the FromEntropy or SeedableRng trait")]
pub fn new_unseeded() -> Self {
Self::new_from_u64(0)
Self::seed_from_u64(0)
}

/// Create an ISAAC-64 random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
#[deprecated(since="0.6.0", note="use SeedableRng::seed_from_u64 instead")]
pub fn new_from_u64(seed: u64) -> Self {
Isaac64Rng(BlockRng64::new(Isaac64Core::new_from_u64(seed)))
Self::seed_from_u64(seed)
}
}

Expand Down Expand Up @@ -280,16 +288,9 @@ impl Isaac64Core {
/// Create an ISAAC-64 random number generator using an `u64` as seed.
/// If `seed == 0` this will produce the same stream of random numbers as
/// the reference implementation when used unseeded.
#[deprecated(since="0.6.0", note="use SeedableRng::seed_from_u64 instead")]
pub fn new_from_u64(seed: u64) -> Self {
let mut key = [w(0); RAND_SIZE];
key[0] = w(seed);
// Initialize with only one pass.
// A second pass does not improve the quality here, because all of the
// seed was already available in the first round.
// Not doing the second pass has the small advantage that if
// `seed == 0` this method produces exactly the same state as the
// reference implementation when used unseeded.
Self::init(key, 1)
Self::seed_from_u64(seed)
}
}

Expand All @@ -306,6 +307,18 @@ impl SeedableRng for Isaac64Core {
}
Self::init(seed_extended, 2)
}

fn seed_from_u64(seed: u64) -> Self {
let mut key = [w(0); RAND_SIZE];
key[0] = w(seed);
// Initialize with only one pass.
// A second pass does not improve the quality here, because all of the
// seed was already available in the first round.
// Not doing the second pass has the small advantage that if
// `seed == 0` this method produces exactly the same state as the
// reference implementation when used unseeded.
Self::init(key, 1)
}

fn from_rng<R: RngCore>(mut rng: R) -> Result<Self, Error> {
// Custom `from_rng` implementation that fills a seed with the same size
Expand Down Expand Up @@ -425,11 +438,11 @@ mod test {
#[test]
fn test_isaac64_new_uninitialized() {
// Compare the results from initializing `IsaacRng` with
// `new_from_u64(0)`, to make sure it is the same as the reference
// `seed_from_u64(0)`, to make sure it is the same as the reference
// implementation when used uninitialized.
// Note: We only test the first 16 integers, not the full 256 of the
// first block.
let mut rng = Isaac64Rng::new_from_u64(0);
let mut rng = Isaac64Rng::seed_from_u64(0);
let mut results = [0u64; 16];
for i in results.iter_mut() { *i = rng.next_u64(); }
let expected: [u64; 16] = [
Expand Down

0 comments on commit 24fbd1f

Please sign in to comment.