14

In the documentation it is said that there is a chance that uniform(0,1) can generate the values 0 and 1.

I have run uniform(0, 1) 10000 times, but it never produced zero. Even in the case of uniform(0, 0.001).

Can random.uniform(0,1) ever generate 0 or 1?

wim
  • 338,267
  • 99
  • 616
  • 750
Venkatesh Gandi
  • 441
  • 1
  • 9
  • 24
  • 3
    It is theoretically possibly, but practically will never happen. Mathematically, a standard uniform random variable can take on any value in the interval of 0 to 1. if `X ~ U(0,1)`, then the `P(X=x)` is *almost surely* 0, for all values of x. (This is because there are infinitely many possible values in the interval.) If you're looking for exactly 0 or 1, you should use a different function- for example `random.choice` – pault Oct 04 '19 at 19:01
  • 1
    @pault *almost surely* has a very specific meaning in mathematics, which doesn't really make sense here since we have a discrete distribution not a continuous interval. There are only a finite number of floats between 0 and 1. – wim Oct 04 '19 at 19:04
  • @wim I was speaking mathematically, not about pseudo random floats. I've updated the comment. – pault Oct 04 '19 at 19:05
  • 2
    @pault So why are you speaking mathematically when the OP is asking about the implementation of `random.uniform`? – wim Oct 04 '19 at 19:07
  • 1
    If that documentation is accurate, I’m kind of curious as to how it’s made to possibly produce *both* 0 and 1. Seems like [0, 1) would be a lot easier (which is how `Math.random()` works in JavaScript, for example). – Ry- Oct 04 '19 at 19:08
  • @wim I was going to start there and then extend to implementation of random numbers (for completeness) but now there's a great answer so no need to add anything more. – pault Oct 04 '19 at 19:08
  • Looked at the source. Assuming `1 * x == x` when `x` is a non-NaN float (seems reasonable, but floats are scary) `uniform(0, 1)` can indeed never produce 1 under current CPython. – Ry- Oct 04 '19 at 19:12
  • The [CPython source](https://github.com/python/cpython/blob/master/Modules/_randommodule.c#L146) specifically says `random_random` generates a random number on `[0,1)` with 53-bit resolution, so no it is not possible for CPython to generate the upper bound of the random range. – blhsing Oct 04 '19 at 19:13
  • 1
    50 points bounty for the first person to post a random seed which makes `random.uniform(0, 1)` return a 0 on the first call – wim Oct 04 '19 at 19:17

4 Answers4

19

uniform(0, 1) can produce 0, but it'll never produce 1.

The documentation tells you that the endpoint b could be included in the values produced:

The end-point value b may or may not be included in the range depending on floating-point rounding in the equation a + (b-a) * random().

So for uniform(0, 1), the formula 0 + (1-0) * random(), simplified to 1 * random(), would have to be capable of producing 1 exactly. That would only happen if random.random() is 1.0 exactly. However, random() never produces 1.0.

Quoting the random.random() documentation:

Return the next random floating point number in the range [0.0, 1.0).

The notation [..., ...) means that the first value is part of all possible values, but the second one is not. random.random() will at most produce values very close to 1.0. Python's float type is a IEEE 754 base64 floating point value, which encodes a number of binary fractions (1/2, 1/4, 1/5, etc.) that make up the value, and the value random.random() produces is simply the sum of a random selection of those 53 such fractions from 2 ** -1 (1/2) through to 2 ** -53 (1/9007199254740992).

However, because it can produce values very close to 1.0, together with rounding errors that occur when you multiply floating point nubmers, you can produce b for some values of a and b. But 0 and 1 are not among those values.

Note that random.random() can produce 0.0, so a is always included in the possible values for random.uniform() (a + (b - a) * 0 == a). Because there are 2 ** 53 different values that random.random() can produce (all possible combinations of those 53 binary fractions), there is only a 1 in 2 ** 53 (so 1 in 9007199254740992) chance of that ever happening.

So the highest possible value that random.random() can produce is 1 - (2 ** -53); simply pick a small enough value for b - a to allow for rounding to kick in when multiplied by higher random.random() values. The smaller b - a is, the greater the chances of that happening:

>>> import random, sys
>>> def find_b():
...     a, b = 0, sys.float_info.epsilon
...     while random.uniform(a, b) != b:
...         b /= 2
...     else:
...         return b
...
>>> print("uniform(0, {0}) == {0}".format(find_b()))
...
uniform(0, 4e-323) == 4e-323

If you hit b = 0.0, then we've divided 1023 times, the above value means we got lucky after 1019 divisions. The highest value I found so far (running the above function in a loop with max()) is 8.095e-320 (1008 divisions), but there are probably higher values. It's all a game of chance. :-)

It can also happen if there are not many discrete steps between a and b, like when a and b have a high exponent and so may appear to be far appart. Floating point values are still only approximations, and the number of values they can encode is finite. For example, there is only 1 binary fraction of difference between sys.float_info.max and sys.float_info.max - (2 ** 970), so there is a 50-50 chance random.uniform(sys.float_info.max - (2 ** 970), sys.float_info.max) produces sys.float_info.max:

>>> a, b = sys.float_info.max - (2 ** 970), sys.float_info.max
>>> values = [random.uniform(a, b) for _ in range(10000)]
>>> values.count(sys.float_info.max)  # should be roughly 5000
4997
wim
  • 338,267
  • 99
  • 616
  • 750
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
7

"Several times" isn't enough. 10,000 isn't enough. random.uniform chooses from among 2^53 (9,007,199,254,740,992) different values. You're interested in two of them. As such, you should expect to generate several quadrillion random values before getting a value that is exactly 0 or 1. So it's possible, but it's very very likely that you will never observe it.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • 4
    For `uniform(0, 1)` it is **impossible** to produce `1` as the outcome. That's because the function is simply defined as `def uniform(a, b): return a + (b - a) * random()` and `random()` can never produce `1.0`. – Martijn Pieters Oct 04 '19 at 19:13
  • 2
    @MartijnPieters I believe you're correct, and I upvoted your answer. I suspected as much, but I wasn't sure, and it was aside from the main thrust of my answer, so I let it be :) – hobbs Oct 04 '19 at 19:15
2

Sure. You were already on the right track with trying uniform(0, 0.001) instead. Just keep restricting the bounds enough to make it happen sooner.

>>> random.uniform(0., 5e-324)
5e-324
>>> random.uniform(0., 5e-324)
5e-324
>>> random.uniform(0., 5e-324)
0.0
wim
  • 338,267
  • 99
  • 616
  • 750
1

You can try and generate a loop that counts the amount of iterations needed in order to be shown an exact 0 (don't).

Furthermore, as Hobbs stated, the amount of values being uniformly sampled are 9,007,199,254,740,992. Which means the probability of seeing a 0 is exactly 1/9,007,199,254,740,992. Which in general terms and rounding up means you will need in average 10 quatrillion samples to find a 0. Of course you might find it in your first 10 attempts, or never ever.

Sampling a 1 is impossible as the interval defined for the values is closed with a parenthesis, hence not including 1.

Celius Stingher
  • 17,835
  • 6
  • 23
  • 53