为什么在使用$ RANDOM时得到的结果分布不均?


14

我在Wikipedia$RANDOM上阅读了有关RNG的内容,并在TLDP了解了其功能,但是它并不能真正解释这个结果:

$ max=$((6*3600))
$ for f in {1..100000}; do echo $(($RANDOM%max/3600)); done | sort | uniq -c
  21787 0
  22114 1
  21933 2
  12157 3
  10938 4
  11071 5

为什么大约2倍以上的值更倾向于0、1、2而不是3、4、5,但是当我更改最大模数时,它们几乎均等地分布在所有10个值上?

$ max=$((9*3600))
$ for f in {1..100000}; do echo $(($RANDOM%max/3600)); done | sort | uniq -c
  11940 0
  11199 1
  10898 2
  10945 3
  11239 4
  10928 5
  10875 6
  10759 7
  11217 8

9
通常的解决方法是重新滚动(丢弃收到的数字,然后选择另一个),如果您介于RANDOM的最大值和可以平均地取模的最大可能值之间。这不是随机的,而是跨所有语言/工具/等使用模数限制RNG域的。实现该类型的RNG。
查尔斯·达菲

7
见这种偏见的来源我的2013的文章,如果你想它有多难受了一些不错的图:ericlippert.com/2013/12/16/...
埃里克利珀

1
“随机数的产生非常重要,不能任其偶然。” -罗伯特·科夫尤。仅供参考:大多数程序无法生成真正的随机数
jesse_b

@Eric Lippert谢谢,我会很高兴地阅读它!
cprn

1
请注意,即使您由于模数偏差而遇到问题,该$RANDOM变量在内部也不会使用良好的PRNG。
森林

Answers:


36

为了扩展模偏置的主题,您的公式是:

max=$((6*3600))
$(($RANDOM%max/3600))

并且在此公式中,$RANDOM是0-32767范围内的随机值。

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.

它有助于可视化其如何映射到可能的值:

0 = 0-3599
1 = 3600-7199
2 = 7200-10799
3 = 10800-14399
4 = 14400-17999
5 = 18000-21599
0 = 21600-25199
1 = 25200-28799
2 = 28800-32399
3 = 32400-32767

因此,在您的公式中,0、1、2的机率是4、5的机率的两倍。3的机率也略高于4、5的机率。因此,您的结果为0、1、2作为赢家,而4、5作为输家。

更改为时9*3600,结果为:

0 = 0-3599
1 = 3600-7199
2 = 7200-10799
3 = 10800-14399
4 = 14400-17999
5 = 18000-21599
6 = 21600-25199
7 = 25200-28799
8 = 28800-32399
0 = 32400-32767

1-8的概率相同,但是0仍然有一些偏差,因此0仍然是您测试100'000次迭代的获胜者。

要修正模偏差,您应该首先简化公式(如果您只想使用0-5,则模为6,而不是3600甚至是更疯狂的数字,那是没有意义的)。仅此一种简化就可以大大减少您的偏差(32766映射为0,32767映射为1,这两个数字有微小偏差)。

要完全消除偏差,您需要重新滚动(例如)何时$RANDOM小于32768 % 6(消除不完全映射到可用随机范围的状态)。

max=6
for f in {1..100000}
do
    r=$RANDOM
    while [ $r -lt $((32768 % $max)) ]; do r=$RANDOM; done
    echo $(($r%max))
done | sort | uniq -c | sort -n

测试结果:

  16425 5
  16515 1
  16720 0
  16769 2
  16776 4
  16795 3

另一种选择是使用不具有明显偏差(数量级仅大于32768个可能值)的其他随机源。但是无论如何,实施重新滚动逻辑不会有任何伤害(即使可能永远不会实现)。


您的答案在很大程度上是正确的,除了:“您需要在$ RANDOM低于32768%6时重新滚动”实际上应该是“等于或大于floor((RANDMAX + 1)/ 6)* 6”(即32766) ),并在其下方修复相关的外壳代码。
Nayuki

@Nayuki如果您可以指出特定错误(适用于给定的上下文),我将很乐意予以纠正。我的解决方案只是一个例子,有不同的方法可以做到。您可以从开始范围或结束范围或中间的某个位置消除偏差,这没有什么区别。您可以更好地进行计算(而不是在每次迭代中都取模)。您可以处理特殊情况,例如任意模数和randmax值,还可以处理RANDMAX = INTMAX,其中RANDMAX + 1不存在,但这不是这里的重点。
弗罗斯特沙茨

您的回复明显比您的帖子差。首先,我特别指出了您的哪个短语实际上是错误的。请注意,“ 32768%6” == 2,因此您想在$ RANDOM <2时重新滚动吗?关于范围的开始/结束/中间偏差,您的整个帖子都是关于消除范围结束时的偏差,我的回答也恰好满足了这一点。第三,您谈论处理RANDMAX = INTMAX,但是在回答中您多次提到值32768(= 32767 + 1),这意味着您对计算RANDMAX + 1很满意。
Nayuki

1
@Nayuki我的代码删除了0和1,您的代码删除了32766和32767,我希望您详细说明一下:它有什么区别?我只是人类,我会犯错误,但是到目前为止,您所说的只是“错了”,而没有解释或说明原因。谢谢。
弗罗斯特沙茨

1
没关系,想通了。对不起,误报。
Nayuki

23

这是模偏差。如果RANDOM构造良好,则以相等的概率产生0到32767之间的每个值。使用模数时,将更改概率:模数以上的所有值的概率都将添加到它们映射的值。

在您的示例中,6×3600约为值范围的三分之二。因此,前三分之一的概率与后三分之一的概率相加,这意味着从0到2(大约)的值产生的可能性是从3到5的值的两倍。9×3600接近32767,因此模偏差要小得多,仅会影响32400至32767之间的值。

要回答您的主要问题,至少在Bash中,如果您知道种子,则随机序列是完全可预测的。参见intrand32variables.c

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.