【问题标题】:How to generate random numbers in [0 ... 1.0] in Common Lisp如何在 Common Lisp 中的 [0 ... 1.0] 中生成随机数
【发布时间】:2021-12-10 16:19:31
【问题描述】:

我对 Common Lisp 伪随机数生成的理解是,(random 1.0) 将生成一个严格小于 1 的分数。我希望得到不超过 1.0 的数字。这可能吗?我想我可以决定精度并生成整数并除以范围,但我想知道是否有更广泛接受的方式来执行此操作。谢谢。

【问题讨论】:

  • 你为什么需要那个?从纯数学的角度来看,[0, 1) 或 [0, 1] 上的均匀分布是无法区分的,因为在这两种情况下生成 1 的概率为零。但是,我们谈论的是计算机,这里没有连续函数之类的东西:根据您的具体用例,这种区别可能很重要,但我认为这取决于潜在的问题。
  • 我多年来一直使用 [0,1),但最近我仔细查看了一本关于遗传算法的书 (Michalewicz, 3ed),他规定了 [0,1]。另外(我承认,没有做任何适当的分析)我的总体印象是,我生成的分数平均小于 0.5 ......但这只是我的一种感觉......我没有用过这个过去,所以我必须道歉,我发布这个之前没有太多的深谋远虑。

标签: random lisp probability


【解决方案1】:

如你所说,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)。

【讨论】:

  • 感谢您的回复——我觉得它确实很好地概括了我的需求(并且又教会了我一点关于 Common Lisp 的知识!——我从大约 1994 年开始使用该语言,但一直在寻找“黑洞” “据我所知)。我没有考虑过使用双浮点数,这对我来说可能是个好主意。
【解决方案2】:

我在这里有一点不同的想法。我们可以使用原始范围,并在该范围内的某处选择一个映射到范围限制的受害者编号,而不是尝试将范围扩展到一个 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。也就是说,range 值如 1.0 永远不会是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 - epsilon 的额外 50% 概率。

【讨论】:

  • 感谢您抽出宝贵时间回复 - 我采用其他解决方案只是因为它更简单 - 但阅读您的反馈也很有趣。
猜你喜欢
  • 2018-03-29
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-18
  • 1970-01-01
  • 1970-01-01
  • 2014-09-22
  • 2016-02-17
相关资源
最近更新 更多