26

I'm trying to generate a random number between 0.1 and 1.0. We can't use rand.randint because it returns integers. We have also tried random.uniform(0.1,1.0), but it returns a value >= 0.1 and < 1.0, we can't use this, because our search includes also 1.0.

Does somebody else have an idea for this problem?

CoffeeRain
  • 4,460
  • 4
  • 31
  • 50
user2320923
  • 263
  • 1
  • 3
  • 6
  • According to the [docs](http://docs.python.org/2/library/random.html#random.uniform), random.uniform will "Return a random floating point number N such that `a <= N <= b` for `a <= b`" which would indicate that it will return a number <=1.0. Did you find other documentation that refutes this? – SethMMorton Apr 29 '13 at 21:47
  • @SethMMorton: Yes, the very next sentence in the docs you linked to is: 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(). – John Y Apr 29 '13 at 21:47
  • The very next line after that states "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()." – Imre Kerr Apr 29 '13 at 21:48
  • 5
    The chance of getting exactly 1.0 is virtually 0 anyway... – Elmar Peise Apr 29 '13 at 21:48
  • 1
    Just out of curiosity, is this actually causing a problem for you? – Blender Apr 29 '13 at 21:51
  • 4
    If `random.uniform(0.1, 1)` returns `1.0`, I claim it's more likely to be a sign that something's gone wrong than that it's working. – DSM Apr 29 '13 at 21:51
  • 1
    @SethMMorton: We looked at the examples of basic usage: random.uniform(1, 10) # Random float x, 1.0 <= x < 10.0. – user2320923 Apr 29 '13 at 21:55
  • @Blender: Yes, it's a task for school, and it counts for a lot of points, it has to be right. We want just want to be sure – user2320923 Apr 29 '13 at 21:56
  • 3
    I'm struggling to think of a case where this potential inability to return 1.0 in this case would have practical importance, given the extreme unlikelihood of returning that value even if it was possible to return it. If there is a circumstance where it would be important, I would be interested to learn about it. – Simon Apr 29 '13 at 21:58
  • Interesting. It's a problem for school, which means (a) arguments along the lines of "this will never matter in the real world" don't apply, and (b) if your teacher assigned this specifically as a Python problem, I wonder if the *teacher* knows that `random.uniform` behaves the way it does. Can you describe in more detail what you need this for? – John Y Apr 29 '13 at 22:08
  • @JohnY: I think it's not important for the result of our program. But the university runs control-tests to see if their isn't a mistake in our program. These control-test are very precise, and we don't know how the tests are build. We experience no immediate problems, but we want to be sure for these tests. I'm afraid our English isn't good enough to explain our task – user2320923 Apr 29 '13 at 22:22

10 Answers10

22

How "accurate" do you want your random numbers? If you're happy with, say, 10 decimal digits, you can just round random.uniform(0.1, 1.0) to 10 digits. That way you will include both 0.1 and 1.0:

round(random.uniform(0.1, 1.0), 10)

To be precise, 0.1 and 1.0 will have only half of the probability compared to any other number in between and, of course, you loose all random numbers that differ only after 10 digits.

Elmar Peise
  • 14,014
  • 3
  • 21
  • 40
11

You could do this:

>>> import numpy as np
>>> a=.1
>>> b=np.nextafter(1,2)
>>> print(b)
1.0000000000000002
>>> [a+(b-a)*random.random() for i in range(10)]

or, use numpy's uniform:

np.random.uniform(low=0.1, high=np.nextafter(1,2), size=1)

nextafter will produce the platform specific next representable floating pointing number towards a direction. Using numpy's random.uniform is advantageous because it is unambiguous that it does not include the upper bound.


Edit

It does appear that Mark Dickinson's comments is correct: Numpy's documentation is incorrect regarding the upper bound to random.uniform being inclusive or not.

The Numpy documentation states All values generated will be less than high.

This is easily disproved:

>>> low=1.0
>>> high=1.0+2**-49
>>> a=np.random.uniform(low=low, high=high, size=10000)
>>> len(np.where(a==high)[0])
640

Nor is the result uniform over this limited range:

>>> for e in sorted(set(a)):
...    print('{:.16e}: {}'.format(e,len(np.where(a==e)[0])))
... 
1.0000000000000000e+00: 652
1.0000000000000002e+00: 1215
1.0000000000000004e+00: 1249
1.0000000000000007e+00: 1288
1.0000000000000009e+00: 1245
1.0000000000000011e+00: 1241
1.0000000000000013e+00: 1228
1.0000000000000016e+00: 1242
1.0000000000000018e+00: 640

However, combining J.F. Sebastian and Mark Dickinson's comments, I think this works:

import numpy as np
import random 

def rand_range(low=0,high=1,size=1):
    a=np.nextafter(low,float('-inf'))
    b=np.nextafter(high,float('inf'))
    def r():
        def rn(): 
            return a+(b-a)*random.random()

        _rtr=rn()
        while  _rtr > high:
            _rtr=rn()
        if _rtr<low: 
            _rtr=low
        return _rtr     
    return [r() for i in range(size)]

If run with the minimal spread of values in Mark's comment such that there are very few discrete floating point values:

l,h=1,1+2**-48
s=10000
rands=rand_range(l,h,s)    
se=sorted(set(rands))
if len(se)<25:
    for i,e in enumerate(se,1):
        c=rands.count(e)
        note=''
        if e==l: note='low value end point'
        if e==h: note='high value end point'
        print ('{:>2} {:.16e} {:,}, {:.4%} {}'.format(i, e, c, c/s,note))

It produces the desired uniform distribution inclusive of end points:

 1 1.0000000000000000e+00 589, 5.8900% low value end point
 2 1.0000000000000002e+00 544, 5.4400% 
 3 1.0000000000000004e+00 612, 6.1200% 
 4 1.0000000000000007e+00 569, 5.6900% 
 5 1.0000000000000009e+00 593, 5.9300% 
 6 1.0000000000000011e+00 580, 5.8000% 
 7 1.0000000000000013e+00 565, 5.6500% 
 8 1.0000000000000016e+00 584, 5.8400% 
 9 1.0000000000000018e+00 603, 6.0300% 
10 1.0000000000000020e+00 589, 5.8900% 
11 1.0000000000000022e+00 597, 5.9700% 
12 1.0000000000000024e+00 591, 5.9100% 
13 1.0000000000000027e+00 572, 5.7200% 
14 1.0000000000000029e+00 619, 6.1900% 
15 1.0000000000000031e+00 593, 5.9300% 
16 1.0000000000000033e+00 592, 5.9200% 
17 1.0000000000000036e+00 608, 6.0800% high value end point

On the values requested by the OP, it also produces a uniform distribution:

import matplotlib.pyplot as plt

l,h=.1,1  
s=10000
bin_count=20
rands=rand_range(l,h,s)  
count, bins, ignored = plt.hist(np.array(rands),bin_count)   
plt.plot(bins, np.ones_like(bins)*s/bin_count, linewidth=2, color='r')
plt.show()   

Output

uniform

Community
  • 1
  • 1
dawg
  • 98,345
  • 23
  • 131
  • 206
  • 1
    The first suggestion doesn't work, thanks to rounding: given `a = 0.1`, `b = 1+2**-52`, and `rand_max = 1-2**-53` (the largest value that `random` can actually produce), then `a + (b - a) * rand_max` turns out to be greater than 1.0. – Mark Dickinson Apr 30 '13 at 07:42
  • 1
    And I'm afraid that numpy's documentation is incorrect here: if you look at the underlying code, it's doing exactly the same as Python is, and it is indeed possible for `np.random.uniform` to return the upper bound for some values of low and high. For an extreme example, try `np.random.uniform(low = 1.0, high = 1.0 + 2**-52, size=100)`, and note that about half of the output values are equal to `high`. – Mark Dickinson Apr 30 '13 at 07:56
  • 2
    you might need a [`while`-loop as in my answer](http://stackoverflow.com/a/16301559/4279) to avoid generating values larger than `b`. – jfs Apr 30 '13 at 13:50
  • @MarkDickinson: As usual -- you are completely correct. I will rework this answer – dawg Apr 30 '13 at 17:36
  • Much improved answer! (Some would even say more informative than necessary, but I enjoyed the detail. :) – John Y May 02 '13 at 13:47
9

Random.uniform() is just:

def uniform(self, a, b):
    "Get a random number in the range [a, b) or [a, b] depending on rounding."
    return a + (b-a) * self.random()

where self.random() returns a random number in the range [0.0, 1.0).

Python (as well as many other languages) uses floating point to represent real numbers. How 0.1 is represented is described in detail in the docs:

from __future__ import division

BPF = 53 # assume IEEE 754 double-precision binary floating-point format
N = BPF + 3
assert 0.1 == 7205759403792794 / 2 ** N

It allows to find a random number in [0.1, 1] (inclusive) using randint() without losing precision:

n, m = 7205759403792794, 2 ** N
f = randint(n, m) / m

randint(n, m) returns a random integer in [n, m] (inclusive) therefore the above method can potentially return all floating points numbers in [0.1, 1].

An alternative is to find the smallest x such that x > 1 and use:

f = uniform(.1, x)
while f > 1:
    f = uniform(.1, x)

x should be the smallest value to avoid losing precision and to reduce number of calls to uniform() e.g.:

import sys
# from itertools import count

# decimal.Decimal(1).next_plus() analog
# x = next(x for i in count(1) for x in [(2**BPF + i) / 2**BPF] if x > 1)
x = 1 + sys.float_info.epsilon

Both solutions preserve uniformness of the random distribution (no skew).

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
3

With the information you've given (including comments thus far), I still fail to see how the university is going to test your program such that it will make a difference if 1.0 appears or not. (I mean, if you're required to generate random floats, how can they require that any particular value appears?)

OK, so putting the craziness of your requirements aside:

The fact that the lower bound for your random floats is higher than 0 gives you a disturbingly elegant way to use random.random, which guarantees return values in the interval [0.0, 1.0): Simply keep calling random.random, throwing away any values less than 0.1, except 0.0. If you actually get 0.0, return 1.0 instead.

So something like

from random import random

def myRandom():
    while True:
        r = random()
        if r >= 0.1:
            return r
        if r == 0.0:
            return 1.0
John Y
  • 14,123
  • 2
  • 48
  • 72
  • +1: it doesn't skew the random distribution and provides the same precision for floating point values as random(). btw, you can test boundary values by mocking random.Random to provide special values (it is enough to override .random, .getrandbits). Failing tests and solutions that use os.urandom, ssl.RAND_bytes, etc could be checked by hand. The general randomness can be checked by existing tests (as a black box). – jfs Apr 30 '13 at 22:10
2

You can use random.randint simply by doing this trick:

>>> float(random.randint(1000,10000)) / 10000
0.4362

if you want more decimals, just change the interval to:

(1000,10000) 4 digits (10000,100000) 5 digits etc

jabaldonedo
  • 25,822
  • 8
  • 77
  • 77
2

In numpy, you can do the following:

import numpy
numpy.random.uniform(0.1, numpy.nextafter(1, 2))
Mitar
  • 6,756
  • 5
  • 54
  • 86
  • Fixed. Thanks. Hm, In documentation it says it cannot. Is this a known fact about upper bound? Should this be reported as a bug? – Mitar Mar 26 '18 at 16:15
1

Are you unable to use random.random()? This gives a number between 0.0 and 1.0, though you could easily set up a way to get around this.

import random
def randomForMe():
    number = random.random()
    number = round(number, 1)
    if (number == 0):
        number = 0.1

This code would give you a number that is between 0.1 and 1.0, inclusive (0.1 and 1.0 are both possible solutions). Hope this helps.

*I assumed you only wanted numbers to the tenths place. If you want it different, where I typed round(number, 1) change 1 to 2 for hundredths, 3 for thousandths, and so on.

erdekhayser
  • 6,537
  • 2
  • 37
  • 69
  • Why round the number at all? The question did not mention that this was desired. If it was, the problem would be trivially solved with randint and a division by 10. – svk Apr 29 '13 at 22:04
  • @svk I rounded the numbers because all the numbers he had used in the question were up to one decimal place. It can be left out, and in that case, he can just use `random.random()`, I guess. – erdekhayser Apr 29 '13 at 22:11
1

The standard way would be random.random() * 0.9 + 0.1 (random.uniform() internally does just this). This will return numbers between 0.1 and 1.0 without the upper border.

But wait! 0.1 (aka ¹/₁₀) has no clear binary representation (as ⅓ in decimal)! So You won't get a true 0.1 anyway, simply because the computer cannot represent it internally. Sorry ;-)

Alfe
  • 56,346
  • 20
  • 107
  • 159
  • This is identical to what `random.uniform(0.1, 1.0)` does. `without both borders` is inaccurate: `random.random()` *can* return `0.0`, but cannot return `1.0`. – Mark Dickinson Apr 30 '13 at 08:00
  • You're right, Mark. Thanks for the correction, I will include this in the text. – Alfe Apr 30 '13 at 08:53
1

Try random.randint(1, 10)/100.0

0

According to the Python 3.0 documentation:

random.uniform(a, b) Return a random floating point number N such that a <= N <= b for a <= b and b <= N <= a for b < a.

Thus, random.uniform() does, in fact, include the upper limit, at least on Python 3.0.

EDIT: As pointed out by @Blender, the documentation for Python 3.0 seems to be inconsistent with the source code on this point.

EDIT 2: As pointed out by @MarkDickinson, I had unintentionally linked to the Python 3.0 documentation instead of the latest Python 3 documentation here which reads as follows:

random.uniform(a, b) Return a random floating point number N such that a <= N <= b for a <= b and b <= N <= a for b < a.

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().

Community
  • 1
  • 1
Simon
  • 10,679
  • 1
  • 30
  • 44
  • 2
    It seems like the Python 3 documentation is different from the Python 2. The source, however, is identical and both include the same comment: `"Get a random number in the range [a, b) or [a, b] depending on rounding."` – Blender Apr 29 '13 at 21:49
  • 1
    Well, the Python 3 *docs* are different; doesn't necessarily mean the behavior is different. I recall a discussion some while back (sorry, don't remember when exactly and don't have any links) whether to include this caveat. It may well be that the consensus was that no use case really justified a guarantee that the end-point value ever be returned (more or less along the lines of @ExP's comment). That is, if your application *depends on* the end-point appearing, then perhaps you've modeled the problem wrong, or misunderstand (pseudo)random numbers. – John Y Apr 29 '13 at 21:53
  • @Simon: That's a link to the 3.0 docs; try http://docs.python.org/3/library/random.html for a more up-to-date version. – Mark Dickinson Apr 30 '13 at 07:46
  • @MarkDickinson: Oy, how did I miss that. I guess so many people consider 3.1 the first (acceptable) version in the Python 3 series that it just didn't occur to me that anyone would link *specifically* to 3.0. Good catch. – John Y Apr 30 '13 at 14:06
  • @MarkDickinson: Thanks for the clarification. Good spotting. I've amended my answer but I haven't deleted it because awareness of this point seems like it might be useful. – Simon Apr 30 '13 at 20:33