Skip to content

Commit

Permalink
Simplify isaac init code
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Oct 21, 2017
1 parent adcd8e5 commit 130b64c
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 140 deletions.
108 changes: 37 additions & 71 deletions src/prng/isaac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct IsaacRng {
}

// Cannot be derived because [u32; 256] does not implement Clone
// FIXME: remove once RFC 2000 gets implemented
impl Clone for IsaacRng {
fn clone(&self) -> IsaacRng {
IsaacRng {
Expand All @@ -115,62 +116,21 @@ impl fmt::Debug for IsaacRng {
}
}

fn mix(a: &mut w32, b: &mut w32, c: &mut w32, d: &mut w32,
e: &mut w32, f: &mut w32, g: &mut w32, h: &mut w32) {
*a ^= *b << 11; *d += *a; *b += *c;
*b ^= *c >> 2; *e += *b; *c += *d;
*c ^= *d << 8; *f += *c; *d += *e;
*d ^= *e >> 16; *g += *d; *e += *f;
*e ^= *f << 10; *h += *e; *f += *g;
*f ^= *g >> 4; *a += *f; *g += *h;
*g ^= *h << 8; *b += *g; *h += *a;
*h ^= *a >> 9; *c += *h; *a += *b;
}

impl IsaacRng {
/// Creates 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.
pub fn new_from_u64(seed: u64) -> IsaacRng {
let mut a = w(0x1367df5a);
let mut b = w(0x95d90059);
let mut c = w(0xc3163e4b);
let mut d = w(0x0f421ad8);
let mut e = w(0xd92a4a78);
let mut f = w(0xa51a3c49);
let mut g = w(0xc4efea1b);
let mut h = w(0x30609119);

let mut mem = [w(0); RAND_SIZE];

a += w(seed as u32);
b += w((seed >> 32) as u32);

for i in (0..RAND_SIZE/8).map(|i| i * 8) {
mix(&mut a, &mut b, &mut c, &mut d, &mut e, &mut f, &mut g, &mut h);
mem[i ] = a; mem[i+1] = b;
mem[i+2] = c; mem[i+3] = d;
mem[i+4] = e; mem[i+5] = f;
mem[i+6] = g; mem[i+7] = h;
}
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.

let mut rng = IsaacRng {
rsl: [w(0); RAND_SIZE],
mem: mem,
a: w(0),
b: w(0),
c: w(0),
cnt: 0,
};

// Prepare the first set of results
rng.isaac();
rng
init(key, 1)
}

/// Refills the output buffer (`self.rsl`)
Expand Down Expand Up @@ -278,7 +238,7 @@ impl Rng for IsaacRng {
}
}

/// Creates a new ISAAC-64 random number generator.
/// Creates a new ISAAC random number generator.
///
/// The author Bob Jenkins describes how to best initialize ISAAC here:
/// https://rt.cpan.org/Public/Bug/Display.html?id=64324
Expand All @@ -304,7 +264,7 @@ impl Rng for IsaacRng {
/// mixes it, and combines that with the next 32 bytes, et cetera. Then loops
/// over all the elements the same way a second time."
#[inline]
fn init(key: [w32; RAND_SIZE]) -> IsaacRng {
fn init(mut mem: [w32; RAND_SIZE], rounds: u32) -> IsaacRng {
// These numbers are the result of initializing a...h with the
// fractional part of the golden ratio in binary (0x9e3779b9)
// and applying mix() 4 times.
Expand All @@ -317,29 +277,23 @@ fn init(key: [w32; RAND_SIZE]) -> IsaacRng {
let mut g = w(0xc4efea1b);
let mut h = w(0x30609119);

let mut mem = [w(0); RAND_SIZE];

macro_rules! memloop {
($arr:expr) => {{
for i in (0..RAND_SIZE/8).map(|i| i * 8) {
a += $arr[i ]; b += $arr[i+1];
c += $arr[i+2]; d += $arr[i+3];
e += $arr[i+4]; f += $arr[i+5];
g += $arr[i+6]; h += $arr[i+7];
mix(&mut a, &mut b, &mut c, &mut d,
&mut e, &mut f, &mut g, &mut h);
mem[i ] = a; mem[i+1] = b;
mem[i+2] = c; mem[i+3] = d;
mem[i+4] = e; mem[i+5] = f;
mem[i+6] = g; mem[i+7] = h;
}
}}
// Normally this should do two passes, to make all of the seed effect all
// of `mem`
for _ in 0..rounds {
for i in (0..RAND_SIZE/8).map(|i| i * 8) {
a += mem[i ]; b += mem[i+1];
c += mem[i+2]; d += mem[i+3];
e += mem[i+4]; f += mem[i+5];
g += mem[i+6]; h += mem[i+7];
mix(&mut a, &mut b, &mut c, &mut d,
&mut e, &mut f, &mut g, &mut h);
mem[i ] = a; mem[i+1] = b;
mem[i+2] = c; mem[i+3] = d;
mem[i+4] = e; mem[i+5] = f;
mem[i+6] = g; mem[i+7] = h;
}
}

memloop!(key);
// Do a second pass to make all of the seed affect all of `mem`
memloop!(mem);

let mut rng = IsaacRng {
rsl: [w(0); RAND_SIZE],
mem: mem,
Expand All @@ -354,6 +308,18 @@ fn init(key: [w32; RAND_SIZE]) -> IsaacRng {
rng
}

fn mix(a: &mut w32, b: &mut w32, c: &mut w32, d: &mut w32,
e: &mut w32, f: &mut w32, g: &mut w32, h: &mut w32) {
*a ^= *b << 11; *d += *a; *b += *c;
*b ^= *c >> 2; *e += *b; *c += *d;
*c ^= *d << 8; *f += *c; *d += *e;
*d ^= *e >> 16; *g += *d; *e += *f;
*e ^= *f << 10; *h += *e; *f += *g;
*f ^= *g >> 4; *a += *f; *g += *h;
*g ^= *h << 8; *b += *g; *h += *a;
*h ^= *a >> 9; *c += *h; *a += *b;
}

impl SeedFromRng for IsaacRng {
fn from_rng<R: Rng+?Sized>(other: &mut R) -> Result<Self> {
let mut key = [w(0); RAND_SIZE];
Expand All @@ -364,7 +330,7 @@ impl SeedFromRng for IsaacRng {
other.try_fill(slice)?;
}

Ok(init(key))
Ok(init(key, 2))
}
}

Expand All @@ -385,7 +351,7 @@ impl<'a> SeedableRng<&'a [u32]> for IsaacRng {
*rsl_elem = w(seed_elem);
}

init(key)
init(key, 2)
}
}

Expand Down
104 changes: 35 additions & 69 deletions src/prng/isaac64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub struct Isaac64Rng {
}

// Cannot be derived because [u64; 256] does not implement Clone
// FIXME: remove once RFC 2000 gets implemented
impl Clone for Isaac64Rng {
fn clone(&self) -> Isaac64Rng {
Isaac64Rng {
Expand All @@ -99,61 +100,20 @@ impl fmt::Debug for Isaac64Rng {
}
}

fn mix(a: &mut w64, b: &mut w64, c: &mut w64, d: &mut w64,
e: &mut w64, f: &mut w64, g: &mut w64, h: &mut w64) {
*a -= *e; *f ^= *h >> 9; *h += *a;
*b -= *f; *g ^= *a << 9; *a += *b;
*c -= *g; *h ^= *b >> 23; *b += *c;
*d -= *h; *a ^= *c << 15; *c += *d;
*e -= *a; *b ^= *d >> 14; *d += *e;
*f -= *b; *c ^= *e << 20; *e += *f;
*g -= *c; *d ^= *f >> 17; *f += *g;
*h -= *d; *e ^= *g << 14; *g += *h;
}

impl Isaac64Rng {
/// Creates 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.
pub fn new_from_u64(seed: u64) -> Isaac64Rng {
let mut a = w(0x647c4677a2884b7c);
let mut b = w(0xb9f8b322c73ac862);
let mut c = w(0x8c0ea5053d4712a0);
let mut d = w(0xb29b2e824a595524);
let mut e = w(0x82f053db8355e0ce);
let mut f = w(0x48fe4a0fa5a09315);
let mut g = w(0xae985bf2cbfc89ed);
let mut h = w(0x98f5704f6c44c0ab);

let mut mem = [w(0); RAND_SIZE];

a += w(seed);

for i in (0..RAND_SIZE/8).map(|i| i * 8) {
mix(&mut a, &mut b, &mut c, &mut d, &mut e, &mut f, &mut g, &mut h);
mem[i ] = a; mem[i+1] = b;
mem[i+2] = c; mem[i+3] = d;
mem[i+4] = e; mem[i+5] = f;
mem[i+6] = g; mem[i+7] = h;
}
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.

let mut rng = Isaac64Rng {
rsl: [w(0); RAND_SIZE],
mem: mem,
a: w(0),
b: w(0),
c: w(0),
cnt: 0,
};

// Prepare the first set of results
rng.isaac64();
rng
init(key, 1)
}

/// Refills the output buffer (`self.rsl`)
Expand Down Expand Up @@ -263,7 +223,7 @@ impl Rng for Isaac64Rng {
}

/// Creates a new ISAAC-64 random number generator.
fn init(key: [w64; RAND_SIZE]) -> Isaac64Rng {
fn init(mut mem: [w64; RAND_SIZE], rounds: u32) -> Isaac64Rng {
// These numbers are the result of initializing a...h with the
// fractional part of the golden ratio in binary (0x9e3779b97f4a7c13)
// and applying mix() 4 times.
Expand All @@ -276,29 +236,23 @@ fn init(key: [w64; RAND_SIZE]) -> Isaac64Rng {
let mut g = w(0xae985bf2cbfc89ed);
let mut h = w(0x98f5704f6c44c0ab);

let mut mem = [w(0); RAND_SIZE];

macro_rules! memloop {
($arr:expr) => {{
for i in (0..RAND_SIZE/8).map(|i| i * 8) {
a += $arr[i ]; b += $arr[i+1];
c += $arr[i+2]; d += $arr[i+3];
e += $arr[i+4]; f += $arr[i+5];
g += $arr[i+6]; h += $arr[i+7];
mix(&mut a, &mut b, &mut c, &mut d,
&mut e, &mut f, &mut g, &mut h);
mem[i ] = a; mem[i+1] = b;
mem[i+2] = c; mem[i+3] = d;
mem[i+4] = e; mem[i+5] = f;
mem[i+6] = g; mem[i+7] = h;
}
}}
// Normally this should do two passes, to make all of the seed effect all
// of `mem`
for _ in 0..rounds {
for i in (0..RAND_SIZE/8).map(|i| i * 8) {
a += mem[i ]; b += mem[i+1];
c += mem[i+2]; d += mem[i+3];
e += mem[i+4]; f += mem[i+5];
g += mem[i+6]; h += mem[i+7];
mix(&mut a, &mut b, &mut c, &mut d,
&mut e, &mut f, &mut g, &mut h);
mem[i ] = a; mem[i+1] = b;
mem[i+2] = c; mem[i+3] = d;
mem[i+4] = e; mem[i+5] = f;
mem[i+6] = g; mem[i+7] = h;
}
}

memloop!(key);
// Do a second pass to make all of the seed affect all of `mem`
memloop!(mem);

let mut rng = Isaac64Rng {
rsl: [w(0); RAND_SIZE],
mem: mem,
Expand All @@ -313,6 +267,18 @@ fn init(key: [w64; RAND_SIZE]) -> Isaac64Rng {
rng
}

fn mix(a: &mut w64, b: &mut w64, c: &mut w64, d: &mut w64,
e: &mut w64, f: &mut w64, g: &mut w64, h: &mut w64) {
*a -= *e; *f ^= *h >> 9; *h += *a;
*b -= *f; *g ^= *a << 9; *a += *b;
*c -= *g; *h ^= *b >> 23; *b += *c;
*d -= *h; *a ^= *c << 15; *c += *d;
*e -= *a; *b ^= *d >> 14; *d += *e;
*f -= *b; *c ^= *e << 20; *e += *f;
*g -= *c; *d ^= *f >> 17; *f += *g;
*h -= *d; *e ^= *g << 14; *g += *h;
}

impl SeedFromRng for Isaac64Rng {
fn from_rng<R: Rng+?Sized>(other: &mut R) -> Result<Self> {
let mut key = [w(0); RAND_SIZE];
Expand All @@ -323,7 +289,7 @@ impl SeedFromRng for Isaac64Rng {
other.try_fill(slice)?;
}

Ok(init(key))
Ok(init(key, 2))
}
}

Expand All @@ -344,7 +310,7 @@ impl<'a> SeedableRng<&'a [u64]> for Isaac64Rng {
*rsl_elem = w(seed_elem);
}

init(key)
init(key, 2)
}
}

Expand Down

0 comments on commit 130b64c

Please sign in to comment.