When researching for this question and reading the sourcecode in random.py
, I started wondering whether randrange
and randint
really behave as "advertised". I am very much inclined to believe so, but the way I read it, randrange
is essentially implemented as
start + int(random.random()*(stop-start))
(assuming integer values for start
and stop
), so randrange(1, 10)
should return a random number between 1 and 9.
randint(start, stop)
is calling randrange(start, stop+1)
, thereby returning a number between 1 and 10.
My question is now:
If random()
were ever to return 1.0
, then randint(1,10)
would return 11
, wouldn't it?
From
random.py
and the docs:The
)
indicates that the interval is exclusive 1.0. That is, it will never return 1.0.This is a general convention in mathematics,
[
and]
is inclusive, while(
and)
is exclusive, and the two types of parenthesis can be mixed as(a, b]
or[a, b)
. Have a look at wikipedia: Interval (mathematics) for a formal explanation.From Python documentation:
Like almost every PRNG of float numbers..
Other answers have pointed out that the result of
random()
is always strictly less than1.0
; however, that's only half the story.If you're computing
randrange(n)
asint(random() * n)
, you also need to know that for any Python floatx
satisfying0.0 <= x < 1.0
, and any positive integern
, it's true that0.0 <= x * n < n
, so thatint(x * n)
is strictly less thann
.There are two things that could go wrong here: first, when we compute
x * n
,n
is implicitly converted to a float. For large enoughn
, that conversion might alter the value. But if you look at the Python source, you'll see that it only uses theint(random() * n)
method forn
smaller than2**53
(here and below I'm assuming that the platform uses IEEE 754 doubles), which is the range where the conversion ofn
to a float is guaranteed not to lose information (becausen
can be represented exactly as a float).The second thing that could go wrong is that the result of the multiplication
x * n
(which is now being performed as a product of floats, remember) probably won't be exactly representable, so there will be some rounding involved. Ifx
is close enough to1.0
, it's conceivable that the rounding will round the result up ton
itself.To see that this can't happen, we only need to consider the largest possible value for
x
, which is (on almost all machines that Python runs on)1 - 2**-53
. So we need to show that(1 - 2**-53) * n < n
for our positive integern
, since it'll always be true thatrandom() * n <= (1 - 2**-53) * n
.Proof (Sketch) Let
k
be the unique integerk
such that2**(k-1) < n <= 2**k
. Then the next float down fromn
isn - 2**(k-53)
. We need to show thatn*(1-2**53)
(i.e., the actual, unrounded, value of the product) is closer ton - 2**(k-53)
than ton
, so that it'll always be rounded down. But a little arithmetic shows that the distance fromn*(1-2**-53)
ton
is2**-53 * n
, while the distance fromn*(1-2**-53)
ton - 2**(k-53)
is(2**k - n) * 2**-53
. But2**k - n < n
(because we chosek
so that2**(k-1) < n
), so the product is closer ton - 2**(k-53)
, so it will get rounded down (assuming, that is, that the platform is doing some form of round-to-nearest).So we're safe. Phew!
Addendum (2015-07-04): The above assumes IEEE 754 binary64 arithmetic, with round-ties-to-even rounding mode. On many machines, that assumption is fairly safe. However, on x86 machines that use the x87 FPU for floating-point (for example, various flavours of 32-bit Linux), there's a possibility of double rounding in the multiplication, and that makes it possible for
random() * n
to round up ton
in the case whererandom()
returns the largest possible value. The smallest suchn
for which this can happen isn = 2049
. See the discussion at http://bugs.python.org/issue24546 for more.