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

时间:2010-03-24 16:55:53

标签: c random

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

How to generate a random number in C?

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

我将如何做到这一点?

11 个答案:

答案 0 :(得分:161)

到目前为止,所有答案在数学上都是错误的。返回rand() % N并不统一给出[0, N)范围内的数字,除非N除以rand()返回的区间长度(即2的幂)。此外,人们不知道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;
}

循环是获得完美均匀分布所必需的。例如,如果给出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()应用于{{} 1}}代替random_extended()(和random()代替2**n - 1)来拉取小于RAND_MAX的随机值,假设您有一个可以容纳这样的数字类型一个值。最后,当然,您可以使用2**n[min, max]中获取值,包括负值。

答案 1 :(得分: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);
}

答案 2 :(得分:19)

如果你知道一个范围的最大值和最小值,并且想要在范围之间生成包含数字,那么这是一个公式:

r = (rand() % (max + 1 - min)) + min

答案 3 :(得分:17)

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

       return (max - min +1)*scaled + min;
}

有关其他选项,请参阅here

答案 4 :(得分:11)

你不会这样做:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;

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

答案 5 :(得分: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);
...

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

当比特数与n相比足够大时,偏差变得无法估量。

如果RAND_MAX + 1小于n(如this question),或者它不是2的幂,则无关紧要,但必须注意避免整数溢出如果RAND_MAX * n很大。

答案 6 :(得分:4)

这是一个比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;
}
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

答案 7 :(得分:4)

为了避免模偏差(在其他答案中建议),您可以随时使用:

arc4random_uniform(MAX-MIN)+MIN

&#34; MAX&#34;是上限和&#34; MIN&#34;下限。例如,对于10到20之间的数字:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

简单的解决方案,比使用&#34; rand()%N&#34;。

更好

答案 8 :(得分:2)

虽然Ryan是正确的,但基于已知的随机性来源,解决方案可以更加简单。重新陈述问题:

  • 有一个随机源,输出范围[0, MAX)中具有均匀分布的整数。
  • 目标是在[rmin, rmax]范围0 <= rmin < rmax < MAX范围内生成均匀分布的随机整数。

根据我的经验,如果垃圾箱的数量(或#34;框&#34;)明显小于原始数字的范围,原始来源的加密强度 - 那里没有必要经历所有那些繁琐,并且简单的模除法就足够了(如output = rnd.next() % (rmax+1),如果rmin == 0),并产生均匀分布的随机数&#34;足够&#34;,和没有任何速度损失。关键因素是随机源(即孩子,不要在家里用rand()尝试这个。)

以下是它在实践中如何运作的一个例子/证明。我想生成从1到22的随机数,具有加密强源,产生随机字节(基于Intel RDRAND)。结果是:

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

这就像我的目的一样接近统一(公平骰子投掷,为诸如http://users.telenet.be/d.rijmenants/en/kl-7sim.htm等WWII密码机生成加密强大的码本)。输出没有任何明显的偏见。

这里是密码强(真)随机数生成器的来源: Intel Digital Random Number Generator 以及产生64位(无符号)随机数的示例代码。

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

我在Mac OS X上使用clang-6.0.1(直接)编译它,使用gcc-4.8.3使用&#34; -Wa,q&#34; flag(因为GAS不支持这些新指令)。

答案 9 :(得分:1)

如前所述,modulo是不够的,因为它会扭曲分布。下面是我的代码,它掩盖了位并使用它们来确保分布不会偏斜。

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

以下简单代码可让您查看分发:

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t rand = random_in_range(0,n - 1);
        if(rand >= n){
            printf("bug: rand out of range %u\n",(unsigned int)rand);
            return 1;
        }
        numbers[rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}

答案 10 :(得分:0)

将返回范围为[0,1]的浮点数:

#define rand01() (((double)random())/((double)(RAND_MAX)))