【问题标题】:c - Generate a random number outside of a range without a loopc - 在没有循环的范围内生成一个随机数
【发布时间】:2018-06-21 23:27:58
【问题描述】:

在 C 中,我可以使用这个简单的表达式来生成一个范围内的随机数:

rand() % (max_number + 1 - minimum_number) + minimum_number

是否有类似的、非基于循环的表达式可以生成范围外的随机数?例如,如果我的范围是 3 到 5,我想要一个介于 0 到 2 或 6 到 RAND_MAX 范围内的随机数。

【问题讨论】:

  • 是的,你可以!提示:使用无符号。 (除了rand()% something 会有偏见)
  • 你可以这样做,但如果你的范围与RAND_MAX 相比很小,那么你的结果将有很大的偏差,纠正这种偏差的常用方法是使用循环。 (如果你的范围,通过循环的预期行程次数也会很小。)
  • 关于偏见和拒绝抽样的有趣博文:dimitri.xyz/random-ints-from-random-bits
  • 请注意,RAND_MAX 依赖于实现,仅保证为 32767。因此,如果您的 max_numberminimum_number 是不受限制的 32 或 64 位数字,您将需要更复杂的解决方案来生成数字,然后将其调整为超出您的范围。
  • 把数字的范围想象成一个封闭的圆圈。现在排除一个范围的问题和在一个范围内查找数字的问题是一样的。

标签: c random


【解决方案1】:

这可行:

int r = rand() % (maxval+1 - rangesize);
if(r>=rangemin)
    r+=rangesize;

在您的示例中,rangesize 将是 3,因为在您想要避免的范围内有三个数字,而 rangemin 将是 3,因为它是该范围内的最小数字。

这将产生[0, maxval] 范围内的随机数,[rangemin, rangemin+rangesize-1] 范围内的数字除外

但是,请注意,使用 rand() % x 通常会导致分布不佳,因此如果这很重要,您需要考虑这一点。感谢 rici 指出这一点。有关更多信息,请参阅他的回答。

但假设您有一个函数r(lo, hi) 可以生成从lohi 的均匀分布的数字,那么转换if(r>=rangemin) r+=rangesize 就可以正常工作并且不会弄乱分布。

相关链接:

Generating a uniform distribution of INTEGERS in C

http://eternallyconfuzzled.com/arts/jsw_art_rand.aspx

【讨论】:

  • 可能是因为结果不会随机分布。或者更确切地说,对于大样本,分布将比函数 rand() 更不均匀
  • 在 OP 中,它说“如果我的范围是 3 到 5,我想要一个介于 0 到 2 或 6 到 RAND_MAX 范围内的随机数。”将这个精确的用例输入到这个解决方案中,我们得到一个函数,它产生 0、1 和 2 的频率是其余范围内数字的两倍,这与均匀分布有很大偏差。
  • @rici 嗯,我现在做了一个测试,你似乎是对的。
  • @klutt:怎么样:ideone.com/XFyN3O? (奇怪的是,SakoDaemon 的解决方案在这种情况下可以完美运行。停止的时钟一天两次是正确的。)
  • @rici 我自己做了一个测试,确认你是对的。现在我正在尝试了解问题,看看是否有简单的解决方法。
【解决方案2】:

前提是不正确的,假设你想要一个无偏的随机数。并且因为前提不正确,所以没有类似的解决方案。或者,更好的说法是,因为提供的代码会产生有偏差的样本,所以任何用于在范围之外进行采样的类似代码也会产生有偏差的样本。

在 C 中,我可以使用这个简单的表达式来生成一个范围内的随机数:

 rand() % (max_number + 1 - minimum_number) + minimum_number

如果该范围的大小相对于rand() 返回的可能值范围的大小而言较小,则或多或少会起作用。如果rand() 本身是无偏的并且所需范围的大小是RAND_MAX + 1 的一个因素,它只会产生一个真正无偏的值。由于RAND_MAX + 1 通常是 2 的幂,因此唯一可以产生无偏选择的范围大小也是 2 的幂。

通过鸽子洞原理很容易看出这一点。想象一下有s 鸽子洞,每个对应于所需范围内的一个值。 (s 必须是max - min + 1,当然。)现在rand() 可以产生的每个RAND_MAX + 1 值都必须放入其中一个鸽子洞。由于这些值中的每一个具有相等的概率,并且选择鸽子洞的概率是其内容概率的总和,因此无偏结果要求所有鸽子洞具有相同数量的值,这仅当 s 是可能值数量的一个因素时才有可能。

RAND_MAX + 1 是 32768(例如 Windows)这种不常见的情况下,如果 s 是 6(掷骰子),那么四个鸽子洞中的每一个都会有 5461 个值,并且其他两个中有 5462 个值。这种偏见并不大,但它不会通过博彩专员的检查。

当期望的范围接近RAND_MAX + 1 时,情况会更加戏剧化,如果排除一个小范围,就会出现这种情况。在这种情况下,大多数鸽子洞只有一个值,少数幸运鸽子洞每个都有两个值,因此被选中的可能性是两倍。

最简单的解决方法是涉及循环的“拒绝采样”。如果出现这些值之一,我们会通过再次调用rand() 来拒绝s % (RAND_MAX + 1) 可能返回的rand()。 (拒绝哪些值并不重要,但拒绝小于 s % (RAND_MAX + 1) 的值很简单。)在最坏的情况下,将拒绝一半以下的可能返回值,并且循环将平均运行一次。在更常见的情况下,它几乎不会运行,分支预测会将其成本降低到非常少。

【讨论】:

    【解决方案3】:

    一个简单的解决方案是:

    int b = rand() % 2, nr;
    if (b)
        nr = rand() % min;  // [0, min - 1]
    else
        nr = rand() % (RAND_MAX - max) + max + 1;  // [max + 1, RAND_MAX]
    

    为避免对某些间隔产生偏差(意味着不同数字的不同概率),您仍然必须使用循环。您可能需要检查here 提供的答案。

    编辑: 正如 klutt 指出的那样,这个解决方案增加了它自己的一些偏差,因为获得的数字有 50% 的机会低于您的最小值,并且有 50% 的机会高于您的最大值,无论两个间隔之间的大小差异如何。因此,除非您特别想要这种行为,否则其他解决方案更适合最小化偏差。

    【讨论】:

      猜你喜欢
      • 2018-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多