Should this always be the case? For uniformly distributed random number in [0,1) I would expect the least significant bits to have some skew. But is it right that the final bit is always zero?
julia> count = 0
for i in 1:10000
if bits(rand())[end] =='1'
count+=1
end
end; count
0
julia> count = 0
for i in 1:10000
if bits(rand())[end-1] =='1'
count+=1
end
end; count
2473
julia> count = 0
for i in 1:10000
if bits(rand())[end-2] =='1'
count+=1
end
end; count
3727
julia> count = 0
for i in 1:10000
if bits(rand())[end-3] =='1'
count+=1
end
end; count
4280
julia> count = 0
for i in 1:10000
if bits(rand())[end-4] =='1'
count+=1
end
end; count
4655
Version info:
julia> versioninfo()
Julia Version 0.4.5
Commit 2ac304d (2016-03-18 00:58 UTC)
Platform Info:
System: Darwin (x86_64-apple-darwin13.4.0)
CPU: Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz
WORD_SIZE: 64
BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
LAPACK: libopenblas64_
LIBM: libopenlibm
LLVM: libLLVM-3.3
If you count from the right then the distribution of each bit is Bernoulli(p(n)) with p(n)=(2^(n-1)-1)/2^n and p(1)=0. There is probably a good theoretical explantion for this but I don't know it and can't come up with it right now. @simonbyrne Do you know why the bits have this distributional pattern?
rand() is a bit of a lie. We do the naive but common thing of subtracting 1.0 from a random number uniformly distributed within [1.0, 2.0) where the possible floating point numbers are actually evenly spaced. Below 1.0, the distribution of possible floats is unevenly sampled, and I don't know of many implementations that are actually capable of generating all of them in a way that best approximates the continuous uniform distribution.
edit: See https://github.com/art4711/random-double/blob/master/rd.c for some fun
Fiddling with Matlab I see that their rand does set the last bit to 1. Edit: It seems that the distribution of bit end-i in Julia is similar to the distribution of bit end-(i+1) in Matlab.
@tkelman The blob you linked to, is that the actual algorithm used for Julia? Or is it as you described random in [1.0,2.0) and then subtracting one? Because in the latter case I (almost) see how the probability distribution comes up.
The floating point representation of 2.0 is (let's do it in Float16 so it's less typing) 0100000000000000 and 1.0 is 0011110000000000 (we see 1 sign, 5 exponent, and 10 mantissa). So to generate a number in [1.0, 2.0) we only need to generate a random 10 bit binary string.
Including the 'hidden bit" we are looking at a binary string of 11 letters, the first being 1, and the 10 following are random.
Now subtract from it 1.0, which corresponds to 1 00000 00000. So we have a string of 10 bits, but the leading few could be zero! This means we have to shift to the left to recover the hidden bit and divide out the shift in the exponent. What is clear then is that
end-1 is 0 is the probability of "1 leading zero" + probability of "leading 1 but trailing 0" = 3/4. If we really want to put that last bit to use, we can generate the float in "65-bit float" and truncate. Which, now that I am thinking about it, is basically the same as what is written in the rd.c code above. (And also explains what is seen in Matlab.)
The page I linked to is just some guy's fun little piece of C code, I don't think that's a commonly used algorithm. We could certainly port it from C to Julia and see how it compares in performance and distribution properties though. Julia subtracts 1:
julia> printloc(fileline; before=0, after=0) = run(pipeline(`head -n$(fileline[2]+after) $(fileline[1])`, `tail -n$(1+before+after)`));
julia> printloc(@functionloc rand())
@inline rand() = rand(GLOBAL_RNG, CloseOpen)
julia> printloc(@functionloc rand(Base.Random.GLOBAL_RNG, Base.Random.CloseOpen))
@inline rand{I<:FloatInterval}(r::MersenneTwister, ::Type{I}) = (reserve_1(r); rand_inbounds(r, I))
julia> printloc(@functionloc Base.Random.rand_inbounds(Base.Random.GLOBAL_RNG, Base.Random.CloseOpen))
@inline rand_inbounds(r::MersenneTwister, ::Type{CloseOpen}) = rand_inbounds(r, Close1Open2) - 1.0
julia> printloc(@functionloc Base.Random.rand_inbounds(Base.Random.GLOBAL_RNG, Base.Random.Close1Open2))
@inline rand_inbounds(r::MersenneTwister, ::Type{Close1Open2}) = mt_pop!(r)
julia> printloc(@functionloc Base.Random.mt_pop!(Base.Random.GLOBAL_RNG))
@inline mt_pop!(r::MersenneTwister) = @inbounds return r.vals[r.idx+=1]
julia> printloc(@functionloc Base.Random.reserve_1(Base.Random.GLOBAL_RNG))
@inline reserve_1(r::MersenneTwister) = mt_empty(r) && gen_rand(r)
julia> printloc(@functionloc(Base.Random.gen_rand(Base.Random.GLOBAL_RNG)), before=1, after=2)
function gen_rand(r::MersenneTwister)
dsfmt_fill_array_close1_open2!(r.state, pointer(r.vals), length(r.vals))
mt_setfull!(r)
end
julia> printloc(@functionloc(Base.dSFMT.dsfmt_fill_array_close1_open2!(Base.Random.GLOBAL_RNG.state, pointer(Base.Random.GLOBAL_RNG.vals), length(Base.Random.GLOBAL_RNG.vals))), before=1, after=6)
function dsfmt_fill_array_close1_open2!(s::DSFMT_state, A::Ptr{Float64}, n::Int)
@assert Csize_t(A) % 16 == 0 # the underlying C array must be 16-byte aligned
@assert dsfmt_min_array_size <= n && iseven(n)
ccall((:dsfmt_fill_array_close1_open2,:libdSFMT),
Void,
(Ptr{Void}, Ptr{Float64}, Int),
s.val, A, n)
end
There are 4 options here:
Float64, then multiply by 0x1p-64 (though this introduces some rounding issues where it would be technically possible to get a 1.0)Some thoughts from the point of view of a user:
rand(Base.Random.Close1Open2))p(n)=(2^(n-1)-1)/2^n for julia (or p(n) = (2^n - 1)/ 2^(n+1) for matlabOn 13 May 2016 at 05:58, Simon Byrne [email protected] wrote:
There are 4 options here:
1.
Generate uniformly on [1,2), and subtract 1 (what we currently do),
and means that all numbers will have a 0 as last bit due to the loss of
precision. This is the easiest/fastest (as you can just generate 52 random
bits, bitwise or, then do a floating point subtraction), and is the only
mode supported by dSFMT (it only generates 52 bits per iteration).
2.Generate at the precision of 0.5, so numbers [1/2,1) will have 0/1 as
last bit with equal probability, and those in [0,1/2) will always have 0 as
the last bit. This is slightly trickier to do, and will involve some
performance penalty. This StackOverflow post
http://stackoverflow.com/q/35350699/392585 discusses this in the
context of single precision, but the ideas should transfer. The simplest
idea is to generate a uniform [1/2,1), then subtract 0.5 with probability
0.5, though this is obviously going to be terrible for branch prediction
(one suggestion in a comment there is to always subtract, but bitmask the
second argument to 0.0 with probability 1/2). To do this we would need an
RNG which could generate at least 53 bits per iteration)
3.Generate at full precision for any argument, so that all significands
are equally likely for all exponents. This is the "most accurate", but also
the most expensive, as it will take a lot more random bits to generate a
number [2^-11, 2^-10) than a number [1/2,1). I don't know of any software
which does this.
4.Some mix of 2 & 3: generate as many random bits as we can with a
single iteration: i.e. if we have a 64-bit RNG, numbers greater than 2^-12
would be generated to full precision, and numbers below would see some
truncation. The easiest way to do this would be via a cast to Float64,
then multiply by 0x1p-64 (though this introduces some rounding issues
where it would be technically possible to get a 1.0)—
You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
https://github.com/JuliaLang/julia/issues/16344#issuecomment-219001816
Euclid taught me that without assumptions there is no proof. Therefore, in
any argument, examine the assumptions. ~ E.T.Bell
The probability distribution for the final bits in the form
p(n)=(2^(n-1)-1)/2^nfor julia (orp(n) = (2^n - 1)/ 2^(n+1)for matlab
are the correct theoretical distributions for random floating point
numbers, independently of precision.
I'm not quite sure what you mean here: what's n?
n is as in this comment above, in other words p(n) is the probability that the n'th bit (counting from the right) is 1.
Ah, I missed that comment. This is a consequence of option 1 above (or option 2 in the case of matlab), and nothing intrinsic to random numbers. Basically the nth bit will certainly be zero if it falls in the interval [0,2^(1-n)), and will be zero with the probability of 1/2 otherwise.
Is there more to do here?
From my perspective: not really. The result is not a bug (it is by design for performance issues). I'll close.
Most helpful comment
rand()is a bit of a lie. We do the naive but common thing of subtracting 1.0 from a random number uniformly distributed within[1.0, 2.0)where the possible floating point numbers are actually evenly spaced. Below 1.0, the distribution of possible floats is unevenly sampled, and I don't know of many implementations that are actually capable of generating all of them in a way that best approximates the continuous uniform distribution.edit: See https://github.com/art4711/random-double/blob/master/rd.c for some fun