有没有办法为 random.uniform 设置打开和关闭端点?

Is there a way to set open and closed endpoints for random.uniform?

我有一个方法需要从 sted 之间的范围内 select 随机 float val,但我只想要包括的端点之一。 (或者,换句话说,(st, ed][st, ed)。)我目前的解决方案是:

import random
val = None
st, ed = 0, 360
  ## In application, what I want is an angle, but I've made it generic as an example.
while not (st <= val < ed): # which of st or ed to reject is up to you.
    val = random.uniform(st, ed)

...但是有没有一种方法(通过参数或符号)告诉 random.uniform(或类似的函数)包含或排除一个或另一个端点?也许 NumPy 有答案?

我知道在精度为 13 位的浮点数范围内获得两个等效值的几率,跨度超过(或恰好)一个整数基本上为零,但我会想知道是否只是为了更好地了解工具本身。

从广义上讲,您的解决方案似乎是合理的。如果您有一个 RNG 提供的值范围大于您需要的范围,您将丢弃无效结果并继续计算。显然,如果您的无效范围太大,这可能会 运行 永远存在危险,但最终这是您可能不得不做出的牺牲。

事实上,这就是 Python 首先生成随机值的方式。来自 random._randbelow():

k = n.bit_length()  # don't use (n-1) here because n can be 1
r = getrandbits(k)          # 0 <= r < 2**k
while r >= n:
    r = getrandbits(k)
return r

它不断生成一个低于 2**k 的随机整数,直到结果低于所需的 n。就像你想做的那样。

也就是说,对于您的特定情况,您不需要这样做 - 担心浮点范围的端点有点徒劳。

首先,正如@user2357112 指出的那样,考虑均匀分布的开放端点与封闭端点有点迂腐。在实数线(或任何子集)上,随机选择端点的概率为 0。此外,对于浮点数,情况会变得更糟,而不是更好。由于浮点数不能精确表示所有实数,因此完全有可能(甚至可能)您的任何一个端点都不存在,因此 cannot 永远不会被返回,无论您指定的边界如何。

@EllaShar 有一个有趣的位旋转想法,但我不会在实践中推荐它。如果您确实需要精确控制边界,请坚持您原来的解决方案,它更简单、更具可读性、更清晰正确。


顺便说一句,如果你有一个 RNG returns 范围内的数字 [a, b) 并且你想获得范围 (a, b] 内的数字,你可以简单地取反你的范围, 例如

-random.randrange(-b, -a)

要生成一个在 [st, ed) 中甚至四舍五入后的数字,您可以执行 random.uniform(st, closest_smaller_than_ed).

要获得比 ed 小的最接近 ed 的数字,请使用(参见 this):

numpy.nextafter(ed, ed - 1)

您可能需要检查 ed != st 以避免生成不在 [st, ed) 中的数字。

同样,要在 (st, ed] 中获取 val,请执行以下操作:

if st == ed:
    return ed
st_adjusted = numpy.nextafter(st, st + 1)
return random.uniform(st_adjusted, ed)

编辑:

dimo414的评论是正确的,ed的值对他们来说,得到closest_smaller_than_ed的概率为零。证明:

(我会用名字 adj_ed 来称呼 closest_smaller_than_ed)

方法 random.uniform(st, adj_ed) 等于 st + (adj_ed - st) * random.rand()。当 random.rand() 给出小于 1 的最接近数字时,此处达到最大值,即 numpy.nextafter(1, 0).

所以问题是,是否有一个 ed 值使得 st + (adj_ed - st) * numpy.nextafter(1, 0) < adj_ed。如果有这样一个ed,那么对于这个ed,用我的方法是不可能得到adj_ed的,因为我们能得到的最大数小于adj_ed.

答案是肯定的,有这样一个编辑:st = 0; ed = 360,正如 OP 所建议的那样。

这给出:

>>> st = 0
>>> ed = 360
>>> adj_ed = numpy.nextafter(ed, ed - 1)
>>> adj_1 = numpy.nextafter(1, 0)
>>> st + (adj_ed - st) * adj_1
359.99999999999989
>>> adj_ed
359.99999999999994
>>> st + (adj_ed - st) * adj_1 == numpy.nextafter(adj_ed, adj_ed - 1)
True

编辑2:

我有一个新想法,但我不确定它是否能解决所有问题。

如果我们首先检查 random.uniform(st, ed) 可以给出的最大值会怎样(如果不是,那么一切正常,没有什么需要解决的)。然后我们才按照我之前的建议使用 random.uniform(st, closest_smaller_than_ed)

def does_uniform_include_edge(st, ed):
    adj_1 = numpy.nextafter(1, 0)
    return st + (ed - st) * adj_1 == ed

def uniform_without_edge(st, ed):
    if does_uniform_include_edge(st, ed):
        adj_ed = numpy.nextafter(ed, ed - 1)
        # if there is an ed such that does_uniform_include_edge(st, adj_ed) is False then this solution is wrong. Is there?
        return random.uniform(st, adj_ed)
    return random.uniform(st, ed)

print(uniform_without_edge(st, ed))

对于 st = 0; ed = 360 我们有 does_uniform_include_edge(st, ed) == False 所以我们只是 return random.uniform(st, ed).