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

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

"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.

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)
>>> random.uniform(0., 5e-324)
>>> random.uniform(0., 5e-324)