Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions spec/std/random/pcg32_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,40 @@ describe "Random::PCG32" do
m1.next_u.should eq m2.next_u
end

it "#split" do
rng0 = Random::PCG32.new
rng1 = rng0.split
rng2 = rng0.split
rng3 = rng1.split # split of split

seq0 = 5.times.map { rng0.next_u }.to_a
seq1 = 5.times.map { rng1.next_u }.to_a
seq2 = 5.times.map { rng2.next_u }.to_a
seq3 = 5.times.map { rng3.next_u }.to_a

seq1.should_not eq(seq0)
seq1.should_not eq(seq2)
seq1.should_not eq(seq3)
seq2.should_not eq(seq0)
seq2.should_not eq(seq3)
seq3.should_not eq(seq0)
end

it "#split_internal" do
rng0 = Random::PCG32.new(123_u64, 456_u64)
rng1 =
{% if compare_versions(Crystal::VERSION, "1.12.0") >= 0 %}
buf = uninitialized ReferenceStorage(Random::PCG32)
Random::PCG32.unsafe_construct(pointerof(buf), rng0)
buf.to_reference
{% else %}
rng0.dup
{% end %}
rng0.split_internal(rng1)
rng0.next_u.should eq(3152259133_u64)
rng1.next_u.should eq(2489095755_u64)
end

it "can be initialized without explicit seed" do
Random::PCG32.new.should be_a Random::PCG32
end
Expand Down
6 changes: 6 additions & 0 deletions spec/std/random_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,10 @@ describe "Random" do
typeof(array).should eq(StaticArray({{type}}, 4))
{% end %}
end

it "fails to split" do
expect_raises(NotImplementedError, "TestRNG(Int32)#split") do
TestRNG(Int32).new([0]).split
end
end
end
28 changes: 28 additions & 0 deletions src/random.cr
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,34 @@ module Random
# the maximal value for the chosen type.
abstract def next_u

# Splits the current instance into two seemingly independent instances that
# will return distinct sequences of random numbers. Returns a new instance.
#
# ```
# random = Random.new
# split1 = random.split
# split2 = random.split
#
# 5.times.map { random.rand(99) }.to_a # => [79, 42, 54, 17, 52]
# 5.times.map { split1.rand(99) }.to_a # => [90, 37, 15, 74, 61]
# 5.times.map { split2.rand(99) }.to_a # => [6, 87, 5, 73, 71]
# ```
def split : self
copy = dup
split_internal(copy)
copy
end

# The internal implementation for `#split` where *self* is the original
# instance and *other* the duplicated instance to be returned.
#
# The default `Random` implementation in stdlib is splittable, but not every
# PRNG algorithm is splittable, so the method raises a `NotImplementedError`
# exception by default.
def split_internal(other : self) : Nil
raise NotImplementedError.new("#{self.class}#split")
end

# Generates a random `Bool`.
#
# ```
Expand Down
11 changes: 11 additions & 0 deletions src/random/pcg32.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class Random::PCG32
new(Random::Secure.rand(UInt64::MIN..UInt64::MAX), Random::Secure.rand(UInt64::MIN..UInt64::MAX))
end

# :nodoc:
def initialize(other : self)
@state = other.@state
@inc = other.@inc
end

def initialize(initstate : UInt64, initseq = 0_u64)
# initialize to zeros to prevent compiler complains
@state = 0_u64
Expand Down Expand Up @@ -87,4 +93,9 @@ class Random::PCG32
end
@state = acc_mult &* @state &+ acc_plus
end

def split_internal(other : self) : Nil
@inc = ((@inc &+ 1) << 1) | 1
other.next_u
end
end