# Best way to get a vector of BigFloat values drawn from a uniform distribution

Is there some better and simple way to implement this for `BigFloat`:

``````function rand_uniform(lo::F, hi::F, n::Signed) where {F <: AbstractFloat}
ret = Vector{F}(undef, n)
dist = Distributions.Uniform(lo, hi)
for i ∈ eachindex(ret)
ret[i] = rand(dist)
end
ret
end
``````

This approach fails because of a `Distributions` bug, it returns `Vector{Float64}` instead of `Vector{BigFloat}` even though `lo` and `hi` are `BigFloat`:

``````rand(Distributions.Uniform(lo, hi), n)
``````

This, on the other hand, fails with a stack overflow because of recursion, for some reason:

``````rand!(zeros(F, n), Distributions.Uniform(lo, hi))
``````
1 Like

If you only need uniformly distributed numbers, than it might be as simple as

``````rand_uniform(lo::T, hi::T, n) where {T <: AbstractFloat} = (hi - lo) * rand(T, n) .+ lo
``````

Example result:

``````julia> rand_uniform(big(2.0), big(3.0), 10)
10-element Vector{BigFloat}:
2.9174284555376025201145330738918913272402954502191116535401251098289736926065
2.19631750020517507697116801083470708819543746756621169816997226270517883434895
2.436503101481603637612779871925157875792727427507907758741273987186075483228166
2.252714788297513525073447778551048335730249363760619306742365774731777906788439
2.30163769835545213520563708849814348263120048796123561822508355898440209243495
2.11121906581318486235373868765400026140501833929437774286345600289310872262491
2.608123684200581891039323989360851860777985059179252353217119384669158234991661
2.148349095950641373356503905512936681549144318191796575473727217069682413323919
2.085010092947450646897480351115790914412328410397893567808432852935416907217017
2.909521362406743263806281684702704618299470210785351171484376026730330458982956
``````

Edit: The code assumes that `rand(T, n)` returns numbers between 0 and 1, so it only works for floating-point inputs.

1 Like

Why not just use the built-in `rand` function?

``````rand_uniform(lo::F, hi::F, n::Integer) where {F <: AbstractFloat} =
[rand(F) * (hi - lo) + lo for _=1:n]
``````

(I used a comprehension, unlike @barucden’s solution, to avoid allocating extra temporary arrays.)

2 Likes

Out of curiosity, I wanted to see how big of a difference this makes. I am observing the opposite effect though!

``````julia> using BenchmarkTools

julia> rand_uniform1(lo::F, hi::F, n::Integer) where {F <: AbstractFloat} =
[rand(F) * (hi - lo) + lo for _=1:n]
rand_uniform1 (generic function with 1 method)

julia> rand_uniform2(lo::T, hi::T, n) where {T <: AbstractFloat} = (hi - lo) * rand(T, n) .+ lo
rand_uniform2 (generic function with 1 method)

julia> @btime rand_uniform1(big(1.0), big(10.0), 1000);
464.910 μs (9005 allocations: 508.14 KiB)

julia> @btime rand_uniform2(big(1.0), big(10.0), 1000);
337.539 μs (6010 allocations: 328.90 KiB)

julia> @btime rand_uniform1(big(1.0), big(10.0), 10000);
4.763 ms (90006 allocations: 4.96 MiB)

julia> @btime rand_uniform2(big(1.0), big(10.0), 10000);
3.481 ms (60013 allocations: 3.20 MiB)
``````

The version with a temporary array is faster and allocates less. Why is that?

1 Like

Yeah, this is getting interesting. Both your and Steven’s function’s presented some opportunities for further optimization:

``````function rand_uniform_nsajko(lo::F, hi::F, n::Signed) where {F <: AbstractFloat}
ret = Vector{F}(undef, n)
dist = Distributions.Uniform(lo, hi)
for i ∈ eachindex(ret)
ret[i] = rand(dist)
end
ret
end

rand_uniform_barucden(lo::T, hi::T, n) where {T <: AbstractFloat} =
(hi - lo) * rand(T, n) .+ lo

rand_uniform_barucden_opt(lo::T, hi::T, n) where {T <: AbstractFloat} =
(hi - lo) .* rand(T, n) .+ lo

rand_uniform_stevengj(lo::F, hi::F, n::Integer) where {F <: AbstractFloat} =
[rand(F) * (hi - lo) + lo for _=1:n]

rand_uniform_stevengj_opt(lo::F, hi::F, n::Integer) where {F <: AbstractFloat} =
let d = hi - lo
[rand(F) * d + lo for _=1:n]
end

# Almost as fast as rand_uniform_barucden_opt
rand_uniform_map(lo::F, hi::F, n::Signed) where {F <: AbstractFloat} =
let v = rand(F, n)
map!(
let d = hi - lo, l = lo
x -> x*d + l
end,
v,
v,
)
end
``````

The optimized version of your function, `rand_uniform_barucden_opt`, seems to be the fastest of all. I guess this could be because your approach uses `rand(::Type, ::Int)`, which could be specially optimized in the Julia stdlib.