I think is enough with the next
next(r::MaskedRNG) = next(r.rng) & r.mask
next(r::Union{RNG, MaskedRNG}, m::Integer) = next(r) & m
Because the second next(r)
will be dispatched correctly (without the m
argument) to the first two.
Actually, I would go event further and write everything as follows, to avoid needing to write overly specific types.
mutable struct RNG <: AbstractRNG
state::UInt64
end
# the default constructor promote to Integer by default
RNG() = RNG(time_ns())
struct MaskedRNG <: AbstractRNG
rng::RNG
mask::UInt64
end
MaskedRNG(nbits) = MaskedRNG(RNG(), (1 << UInt8(nbits)) - 1)
# MaskedRNG(seed::Integer, nbits) = MaskedRNG(RNG(seed), (1 << UInt8(nbits)) - 1) # error when nbits = 64
# this next version allows setting high nbits using negatives, like -8 to mask to 0xff00000000000000
# in benchmarking it takes the same time as the above
MaskedRNG(seed::Integer, nbits) = MaskedRNG(RNG(seed), ~UInt64(0) >> ifelse(0 < abs(nbits) <= 64, sign(nbits)*64 - nbits, 64))
next(r::RNG) = (r.state = _rng(r.state))
next(r::MaskedRNG) = next(r.rng) & r.mask
next(r::Union{RNG, MaskedRNG}, m::Integer) = next(r) & m # this will dispatch correctly
function _rng(x::UInt64)
x ⊻= (x << 21)
x ⊻= (x >>> 35)
x ⊻= (x << 4)
return x % UInt64
end