如何在范围内生成随机整数

Jam*_*ing 103 c random

这是以前发布的问题的后续内容:

如何在C中生成随机数?

我希望能够在特定范围内生成一个随机数,例如1到6,以模仿骰子的边.

我该怎么做呢?

Rya*_*ich 166

到目前为止,所有答案在数学上都是错误的.除非除去返回的区间的长度(即2的幂),否则返回rand() % N不会均匀地给出范围中的数字.此外,人们不知道模量是否是独立的:它们可能是去的,它是均匀的但不是非常随机的.唯一合理的假设是推出泊松分布:任何两个相同大小的非重叠子区间同样可能且独立.对于一组有限的值,这意味着均匀分布,并且还确保分散的值很好.[0, N)Nrand()rand()0, 1, 2, ...rand()rand()

这意味着改变范围的唯一正确方法rand()是将其分成盒子; 例如,如果RAND_MAX == 11和你想要一个范围1..6,你应该分配{0,1}1,{2,3}到2,依此类推.这些是不相交的,大小相等的间隔,因此是均匀且独立分布的.

使用浮点除法的建议在数学上是合理的,但原则上存在舍入问题.也许double是足够高的精度使它工作; 也许不是.我不知道,我不想弄明白; 在任何情况下,答案都是系统依赖的.

正确的方法是使用整数运算.也就是说,您需要以下内容:

#include <stdlib.h> // For random(), RAND_MAX

// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= RAND_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_rand = (unsigned long) RAND_MAX + 1,
    bin_size = num_rand / num_bins,
    defect   = num_rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}
Run Code Online (Sandbox Code Playgroud)

循环是获得完美均匀分布所必需的.例如,如果给出0到2之间的随机数,并且只需要0到1之间的随机数,那么你只需继续拉动,直到你得不到2; 不难检查这是否给出0或1的概率相等.这个方法也在他们的回答中给出的链接中描述,尽管编码方式不同.我正在使用random()而不是rand()因为它具有更好的分布(如手册页所述rand()).

如果你想获得超出默认范围的随机值[0, RAND_MAX],那么你必须做一些棘手的事情.也许最有利的是定义一个函数random_extended(),拉n位(使用random_at_most()),并返回[0, 2**n),然后应用random_at_most()random_extended()到位的random()(而2**n - 1代替RAND_MAX)拉一个随机值小于2**n,假设你有一个数值类型,它可以保持这样的一个值.最后,当然,您可以获取[min, max]使用中的值min + random_at_most(max - min),包括负值.

  • 嘿,这个答案被Comet OS书引用;)我第一次在教学书中看到它 (4认同)
  • 它也被引用在 OSTEP 书中:) http://pages.cs.wisc.edu/~remzi/OSTEP/(第 9 章,第 4 页) (4认同)
  • 进一步审查,这里的另一个问题是,当“max - min &gt; RAND_MAX”时,这将不起作用,这比我上面提到的问题更严重(例如VC++的“RAND_MAX”只有32767)。 (3认同)
  • while循环可以更具可读性.你可能想要一个`do {} while()`而不是在条件中执行赋值. (2认同)

the*_*ter 33

继@Ryan Reich的回答后,我想我会提供清理版本.给定第二个边界检查不需要第一个边界检查,并且我已经迭代而不是递归.它返回[min,max]范围内的值,其中max >= min1+max-min < RAND_MAX.

unsigned int rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = RAND_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = rand();
    } while (r >= limit);

    return min + (r / buckets);
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果范围> = RAND_MAX,这将陷入无限循环.问我怎么知道:/ (27认同)
  • 你怎么知道的!? (23认同)

小智 19

如果您知道范围的最大值和最小值,并且希望生成范围之间的数字,则以下是公式:

r = (rand() % (max + 1 - min)) + min
Run Code Online (Sandbox Code Playgroud)

  • 正如Ryan的回答所指出的那样,这会产生偏差的结果. (8认同)
  • 有偏见的结果,潜在的`int`溢出'max + 1-min`. (5认同)

nos*_*nos 17

unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

       return (max - min +1)*scaled + min;
}
Run Code Online (Sandbox Code Playgroud)

请参阅此处了解其他选项.

  • 这有点危险:如果`rand()== RAND_MAX`或`rand()`非常接近`RAND_MAX`和浮点错误,它很可能(很少)返回`max + 1`将最终结果推过`max + 1`.为了安全起见,您应该在返回之前检查结果是否在范围内. (4认同)
  • 如果`RAND_MAX`被Christoph建议用'RAND_MAX + 1.0'替换,那么我相信这是安全的,只要`+ min`是用整数运算完成的:`return(unsigned int)((max - min + 1)*缩放)+ min`.(非显而易见的)原因是假设IEEE 754算术和舍入到半舍入,(以及'max - min + 1`可以完全表示为double,但在典型的机器上也是如此) ),对于任何正的双重"x"和任何双重"缩放"满足"0.0 <=缩放&&缩放<1.0",`x*scaled <x`总是如此. (3认同)
  • @ S.Lott - 不是真的.每个都以不同的方式分配略高一点的赔率案例,就是这样.双重数学给人的印象是那里有更多的精度,但你可以很容易地使用`(((max-min + 1)*rand())/ RAND_MAX)+ min`并得到完全相同的分布(假设RAND_MAX相对于int足够小而不溢出). (2认同)

Arm*_*est 12

你不会这样做:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;
Run Code Online (Sandbox Code Playgroud)

%是模数运算符.基本上它将除以6并返回余数...从0 - 5

  • Simon,给我看一个libc在任何地方使用`rand()`包括生成器状态的低位(如果它使用LCG).到目前为止我还没有见过所有这些(是的,包括MSRC,RAND_MAX只是32767)*删除*低位.出于其他原因不建议使用模数,即它会偏向分布以支持较小的数字. (4认同)

sh1*_*sh1 8

对于那些理解偏差问题但不能忍受基于拒绝的方法的不可预测的运行时间的人来说,这个系列在[0, n-1]区间中产生逐渐减少偏差的随机整数:

r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...
Run Code Online (Sandbox Code Playgroud)

它通过合成高精度定点随机数i * log_2(RAND_MAX + 1)位(其中i是迭代次数)并执行长乘法来实现n.

当比特数足够大时n,偏差变得无比小.

如果RAND_MAX + 1小于n(如在这个问题中),或者它不是2的幂,则无关紧要,但是如果RAND_MAX * n大则必须小心避免整数溢出.

  • 仍然需要"注意避免整数溢出". (3认同)
  • `RAND_MAX`通常是`INT_MAX`,所以`RAND_MAX + 1` - > UB(如INT_MIN) (2认同)
  • @cat今天测试了2个32位`int`编译器,我在一个上找到了'RAND_MAX == 32767`,在另一个上找到了'RAND_MAX == 2147483647`.我的总体经验(几十年)更经常是"RAND_MAX == INT_MAX".所以不同意合理的现代32位架构肯定会在`2 ^ 16/2'处有一个'RAND_MAX`.由于C规范允许`32767 <= RAND_MAX <= INT_MAX`,因此无论如何我都会编码而不是倾向. (2认同)

K. *_*ann 5

这是一个比 Ryan Reich 的解决方案更简单的算法:

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = rand();
    while (randVal >= limit) randVal = rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}
Run Code Online (Sandbox Code Playgroud)
Example (RAND_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to rand() => 13
    ? 13 is not in the bucket-range anymore (>= limit), while-condition is true
        ? retry...
2nd call to rand() => 7
    ? 7 is in the bucket-range (< limit), while-condition is false
        ? Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3
Run Code Online (Sandbox Code Playgroud)