Skip to content

Commit

Permalink
Do not eagerly call next_uNN in fill_bytes with empty remainder
Browse files Browse the repository at this point in the history
Fix a bug where random numbers are consumed from the Mersenne Twister
even when they will never be used because the remainder from the chunks
iterator in `fill_bytes` is non-empty.

This regression was introduced in:

- #116

Add a basic smoke check for reproducibility with MRI's RNG pulled from
`spinoso-random` in artichoke/artichoke.
  • Loading branch information
lopopolo committed Dec 2, 2021
1 parent 364af42 commit 7cc2335
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 4 deletions.
7 changes: 5 additions & 2 deletions src/mt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,11 @@ impl Mt19937GenRand32 {
next.copy_from_slice(&chunk);
}

dest_chunks
.into_remainder()
let remainder = dest_chunks.into_remainder();
if remainder.is_empty() {
return;
}
remainder
.iter_mut()
.zip(self.next_u32().to_le_bytes().iter())
.for_each(|(cell, &byte)| {
Expand Down
7 changes: 5 additions & 2 deletions src/mt64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,11 @@ impl Mt19937GenRand64 {
next.copy_from_slice(&chunk);
}

dest_chunks
.into_remainder()
let remainder = dest_chunks.into_remainder();
if remainder.is_empty() {
return;
}
remainder
.iter_mut()
.zip(self.next_u64().to_le_bytes().iter())
.for_each(|(cell, &byte)| {
Expand Down
32 changes: 32 additions & 0 deletions tests/ruby_reproducibility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use rand_mt::Mt;

// ```ruby
// # Should double check this is official spec
// it "returns the same numeric output for a given seed across all implementations and platforms" do
// rnd = Random.new(33)
// rnd.bytes(2).should == "\x14\\"
// rnd.bytes(1000) # skip some
// rnd.bytes(2).should == "\xA1p"
// end
//
// it "returns the same numeric output for a given huge seed across all implementations and platforms" do
// rnd = Random.new(bignum_value ** 4)
// rnd.bytes(2).should == "_\x91"
// rnd.bytes(1000) # skip some
// rnd.bytes(2).should == "\x17\x12"
// end
// ```
#[test]
fn spec_bytes() {
let mut rng = Mt::new(33);
let mut buf = [0; 2];
rng.fill_bytes(&mut buf);
assert_eq!(buf[..], b"\x14\\"[..]);

let mut skip = [0; 1000];
rng.fill_bytes(&mut skip);

let mut buf = [0; 2];
rng.fill_bytes(&mut buf);
assert_eq!(buf[..], b"\xA1p"[..]);
}

0 comments on commit 7cc2335

Please sign in to comment.