Skip to content

Commit

Permalink
restrict RandIntGen to generate random numbers in the interval [1,n]
Browse files Browse the repository at this point in the history
This is based on @ivarne idea
(cf. JuliaLang#8255 (comment)),
and continues commit 48f27bc in "avoiding duplicating the getindex logic".

The API to get a RandIntGen object is now to call randintgen(n).
This allows any Integer type to implement this function (e.g. BigInt).
Previously, a call like rand(big(1:10)) caused a stack overflow, it is now
a "no method matching" error, until randintgen(::BigInt) is implemented,
possibly using a new type similar to RandIntGen.
  • Loading branch information
rfourquet authored and rafael committed Sep 30, 2014
1 parent 46c7bbf commit 4bfcba9
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 19 deletions.
26 changes: 11 additions & 15 deletions base/random.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,25 +162,24 @@ maxmultiple(k::Uint128) = div(typemax(Uint128), k + (k == 0))*k - 1
maxmultiplemix(k::Uint64) = itrunc(Uint64, div((k >> 32 != 0)*0x0000000000000000FFFFFFFF00000000 + 0x0000000100000000, k + (k == 0))*k - 1)

immutable RandIntGen{T<:Integer, U<:Unsigned}
a::T # first element of the range
k::U # range length or zero for full range
k::U # range length (or zero for full range)
u::U # rejection threshold
end
# generators with 32, 128 bits entropy
RandIntGen{T, U<:Union(Uint32, Uint128)}(a::T, k::U) = RandIntGen{T, U}(a, k, maxmultiple(k))
RandIntGen{U<:Union(Uint32, Uint128)}(T::Type, k::U) = RandIntGen{T, U}(k, maxmultiple(k))
# mixed 32/64 bits entropy generator
RandIntGen{T}(a::T, k::Uint64) = RandIntGen{T,Uint64}(a, k, maxmultiplemix(k))
RandIntGen(T::Type, k::Uint64) = RandIntGen{T,Uint64}(k, maxmultiplemix(k))


# generator for ranges
RandIntGen{T<:Unsigned}(r::UnitRange{T}) = isempty(r) ? error("range must be non-empty") : RandIntGen(first(r), last(r) - first(r) + one(T))

# generator API
# randintgen(k) returns a helper object for generating random integers in the range 1:k
randintgen{T<:Unsigned}(k::T) = k<1 ? error("range must be non-empty") : RandIntGen(T, k)
# specialized versions
for (T, U) in [(Uint8, Uint32), (Uint16, Uint32),
(Int8, Uint32), (Int16, Uint32), (Int32, Uint32), (Int64, Uint64), (Int128, Uint128),
(Bool, Uint32), (Char, Uint32)]

@eval RandIntGen(r::UnitRange{$T}) = isempty(r) ? error("range must be non-empty") : RandIntGen(first(r), convert($U, unsigned(last(r) - first(r)) + one($U))) # overflow ok
@eval randintgen(k::$T) = k<1 ? error("range must be non-empty") : RandIntGen($T, convert($U, k)) # overflow ok
end

# this function uses 32 bit entropy for small ranges of length <= typemax(Uint32) + 1
Expand All @@ -198,19 +197,18 @@ function rand{T<:Union(Uint64, Int64)}(g::RandIntGen{T,Uint64})
x = rand(Uint64)
end
end
return reinterpret(T, reinterpret(Uint64, g.a) + rem_knuth(x, g.k))
return reinterpret(T, one(Uint64) + rem_knuth(x, g.k))
end

function rand{T<:Integer, U<:Unsigned}(g::RandIntGen{T,U})
x = rand(U)
while x > g.u
x = rand(U)
end
itrunc(T, unsigned(g.a) + rem_knuth(x, g.k))
itrunc(T, one(U) + rem_knuth(x, g.k))
end

rand{T<:Union(Signed,Unsigned,Bool,Char)}(r::UnitRange{T}) = rand(RandIntGen(r))
rand{T}(r::Range{T}) = r[rand(1:(length(r)))]
rand{T}(r::Range{T}) = r[rand(randintgen(length(r)))]

function rand!(g::RandIntGen, A::AbstractArray)
for i = 1 : length(A)
Expand All @@ -219,10 +217,8 @@ function rand!(g::RandIntGen, A::AbstractArray)
return A
end

rand!{T<:Union(Signed,Unsigned,Bool,Char)}(r::UnitRange{T}, A::AbstractArray) = rand!(RandIntGen(r), A)

function rand!(r::Range, A::AbstractArray)
g = RandIntGen(1:(length(r)))
g = randintgen(length(r))
for i = 1 : length(A)
@inbounds A[i] = r[rand(g)]
end
Expand Down
8 changes: 4 additions & 4 deletions test/random.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ if sizeof(Int32) < sizeof(Int)
r = rand(int32(-1):typemax(Int32))
@test typeof(r) == Int32
@test -1 <= r <= typemax(Int32)
@test all([div(0x00010000000000000000,k)*k - 1 == Base.Random.RandIntGen(uint64(1:k)).u for k in 13 .+ int64(2).^(32:62)])
@test all([div(0x00010000000000000000,k)*k - 1 == Base.Random.RandIntGen(int64(1:k)).u for k in 13 .+ int64(2).^(32:61)])
@test all([div(0x00010000000000000000,k)*k - 1 == Base.Random.randintgen(uint64(k)).u for k in 13 .+ int64(2).^(32:62)])
@test all([div(0x00010000000000000000,k)*k - 1 == Base.Random.randintgen(int64(k)).u for k in 13 .+ int64(2).^(32:61)])

end

Expand Down Expand Up @@ -160,8 +160,8 @@ r = uint64(rand(uint32(97:122)))
srand(seed)
@test r == rand(uint64(97:122))

@test all([div(0x000100000000,k)*k - 1 == Base.Random.RandIntGen(uint64(1:k)).u for k in 13 .+ int64(2).^(1:30)])
@test all([div(0x000100000000,k)*k - 1 == Base.Random.RandIntGen(int64(1:k)).u for k in 13 .+ int64(2).^(1:30)])
@test all([div(0x000100000000,k)*k - 1 == Base.Random.randintgen(uint64(k)).u for k in 13 .+ int64(2).^(1:30)])
@test all([div(0x000100000000,k)*k - 1 == Base.Random.randintgen(int64(k)).u for k in 13 .+ int64(2).^(1:30)])

import Base.Random: uuid4, UUID

Expand Down

0 comments on commit 4bfcba9

Please sign in to comment.