如何在Common Lisp的[0.. 1.0]中生成随机数



我对Common Lisp伪随机数生成的理解是,(random 1.0)将生成严格小于1的分数。 我想获得最多 1.0 的数字(含)。 这可能吗? 我想我可以决定一定程度的精度并生成整数并除以范围,但我想知道是否有更广泛接受的方法可以做到这一点。 谢谢。

正如您所说,默认情况下,random会在 [0,1) 中生成数字,通常(random x)会在 [0,x 中生成随机数)。 如果这些是实数,并且如果分布确实是随机的,那么获得任何数字的概率为零,因此这实际上与 [0,1] 没有什么不同。 但它们不是实数:它们是浮点数,因此获得任何特定值的概率更高,因为 [0,1] 中只有有限数量的浮点数。

幸运的是,你可以准确地表达你想要的东西:CL 有一堆常量,其名称类似于 *-epsilon,这些常量被定义为例如

(/= (+ 1.0f0 single-float-epsilon) 1.0f0)

single-float-epsilon是这是正确的最小single-float

因此(random (+ 1.0f0 single-float-epsilon))将产生 [0,1] 范围内的随机单浮点数,并且最终可能会变成1.0f0。 您可以对此进行测试:

(defun tsit ()
(let ((f (+ 1.0f0 single-float-epsilon)))
(assert (/= f 1.0f0) (f) "oops")
(loop for i upfrom 1
for v = (random f)
when (= v 1.0f0)
return (values i v))))

而对我来说

> (tsit)
12839205
1.0

如果您使用双浮子,则需要...相当长...获取1.0d0(并记住使用double-float-epsilon)。

我这里有一点不同的想法。与其试图将范围扩展到 epsilon,我们可以使用原始范围,并在该范围内的某个地方选择一个映射到范围限制的受害者编号。我们可以通过随机选择一个并不时更改来避免硬编码的受害者:

(defun make-random-gen (range)
(let ((victim nil)
(count 1))
(lambda ()
(when (zerop (decf count))
(setf count 10000
victim (random range)))
(let ((out (random range)))
(if (eql out victim) range out)))))

(defun testit ()
(loop with r = (make-random-gen 1.0)
for x = (funcall r)
until (eql x 1.0)
counting t))

在侦听器上:

[5]> (testit) 
23030093

这里有一个小的偏见,victim永远不会等于range。也就是说,像 1.0 这样的range值永远不会victim,因此总是有一定的机会发生。而其他所有值都可能出现victim,其发生几率暂时降低到零。在输出的统计分析中,这应该是隐约可察觉的,因为range值的出现频率略高于任何其他值。

通过更正来更新此方法会很有趣,尝试这样做是:

(defun make-random-gen (range)
(let ((victim nil)
(count 1))
(labels ((gen ()
(when (zerop (decf count))
(setf count 10000
victim (gen)))
(let ((out (random range)))
(if (eql out victim) range out))))
#'gen)))

现在当我们选择victim时,我们递归我们自己的函数,该函数可能会选择range。每当range被选为victim时,该值都会被正确抑制:range不会出现在输出中,因为out永远不会eqlrange

我们可以用以下挥手的论点来证明这一点:

让我们假设对gen的递归调用略微偏向于输出range。但是每当发生这种情况时,range都会被选为victim,这会阻止它出现在gen的输出中。

有一种负面反馈几乎可以完全纠正偏见。


注意:如果我们的随机数生成lambda也捕获一个随机状态对象并使用它,那么它会设计得更好。然后,它产生的序列将不受伪随机数生成器的其他用途的干扰。这是一个不同的话题。


在理论上,请注意 [0, 1) 和 [0, 1] 都不会产生严格正确的分布。如果我们有一个数学上理想的PRNG,它将在这些范围内产生实际的实数。由于该范围包含不可数的无穷大实值,因此每个实值的概率为零:1/aleph-null,我猜它太小了,以至于无法与实零区分开来。

我们想要的是浮点 PRNG 近似于理想的 PRNG。

问题是每个浮点值都近似于一个实际值范围。所以这意味着如果我们有一个 0.0 到 1.0 范围内的值生成器,它实际上表示从 -epsilon 到 1.0 + epsilon 的实数范围。 如果我们从这个 PRNG 中获取值并绘制一个值的条形图,则图表中的每个条形都必须具有一些非零宽度。 0.0 柱以 0 为中心,1.0 柱以 1 为中心。 实数的分布从左柱的左边缘延伸到右柱的右边缘。

为了创建一个模仿 0.0 到 1.0 区间内值均匀分布的 PRNG,我们必须以一半的概率包含 0.0 和 1.0 值。也就是说,当我们从 PRNG 收集大量值时,图形的 0.0 和 1.0 柱线应该是所有其他柱线的一半左右。

在这些条件下,我们无法区分 [0, 1.0) 区间和 [0, 1.0] 区间,因为它们正好一样大。我们必须包括 1.0 值,大约是通常概率的一半,以解释上述均匀性问题。如果我们简单地排除该值,则会在错误的方向上产生偏差,因为直方图中的 1.0 柱现在具有零值。

我们可以挽救这种情况的一种方法是采用直方图的 1.0 epsilon 条形图,并使该值的可能性增加 50%,使该条形图比平均值高 50%。基本上,我们在 1.0 之前重载范围的最后一个值,以表示 1.0 之前和不包括 1.0 的所有内容,要求该值更有可能。然后,我们从输出中排除 1.0 值。从左边接近 1.0 的所有值都映射到额外的 50% 概率 1.0 - epsilon。

相关内容

  • 没有找到相关文章

最新更新