If you have a random function such as uniform()
which may give a half-open or fully-open range (and you don't know which), probably the simplest way to ensure it gives a half-open range is just to filter out the high values.
In other words, something like:
def half_open_uniform(a, b):
# raise exception if a == b ?
result = uniform(a, b)
while result == b:
result = uniform(a, b)
return result
If uniform()
already returns a half-open value, the while
loop will never run and you'll get back a value you want.
If the value it returns is fully-open and your upper and lower bounds have a decent spread, the vast majority of cases will also not activate the while
loop.
If the upper and lower bounds are very close, the probability of getting the upper bound increases but, even then, it will most likely just run once.
If the two bounds are identical, that probabilty rises sharply to 100%. But calling something like half_open_uniform(1, 1)
can be caught with a simple pre-check at the start of the function, probably raising an exception (see the comment).
By way of example, consider the following code which will choose random.uniform()
if no arguments are provided, and that filter function given if any arguments are:
import random
import sys
def half_open_uniform(a, b):
result = random.uniform(a, b)
while result == b:
result = random.uniform(a, b)
return result
fn = half_open_uniform if len(sys.argv) > 1 else random.uniform
upper = 1.0000000000001
count = 0
while True:
count += 1
x = fn(1, upper)
if x == upper:
print(">>>", count, x)
break
if count % 100000 == 0:
print(">>>", count, x)
You can see that, with the uniform one (running with no argument), you get the upper bound occasionally with the first number on each line being the number of iterations before the upper was delivered (each of these lines is the result of a single run):
>>> 452 1.0000000000001
>>> 321 1.0000000000001
>>> 766 1.0000000000001
>>> 387 1.0000000000001
>>> 889 1.0000000000001
>>> 616 1.0000000000001
>>> 82 1.0000000000001
>>> 357 1.0000000000001
>>> 2443 1.0000000000001
>>> 71 1.0000000000001
On the other hand, running with an argument (using the filter function), it just keeps on going for a great deal of time:
>>> 100000 1.0000000000000604
>>> 200000 1.0000000000000642
>>> 300000 1.0000000000000466
>>> 400000 1.0000000000000304
>>> 500000 1.000000000000074
>>> 600000 1.0000000000000007
>>> 700000 1.0000000000000346
>>> 800000 1.0000000000000826
>>> 900000 1.0000000000000588
>>> 1000000 1.0000000000000773
:
>>> 49700000 1.0000000000000449
>>> 49800000 1.0000000000000446
>>> 49900000 1.0000000000000027
>>> 50000000 1.0000000000000786
:
That's fifty million calls without getting back the upper value. And, of course, it was still going. I let it get to a little over two billion iterations before I got bored :-)