从范围生成随机整数

时间:2011-02-15 19:55:34

标签: c++ random

我需要一个能在给定范围内生成随机整数的函数(包括边界值)。我没有不合理的质量/随机性要求,我有四个要求:

  • 我需要快速。我的项目需要产生数百万(有时甚至数千万)的随机数,而我当前的发电机功能已被证明是一个瓶颈。
  • 我需要它合理均匀(使用rand()非常好)。
  • min-max范围可以是< 0,1>范围内的任何值。至< -32727,32727>。
  • 它必须是可播种的。

我目前有以下C ++代码:

output = min + (rand() * (int)(max - min) / RAND_MAX)

问题是,它并不是真正统一的 - 只有当rand()= RAND_MAX时才返回max(对于Visual C ++,它是1/32727)。这是小范围的主要问题,例如< -1,1>,其中最后一个值几乎从不返回。

所以我抓住笔和纸,然后想出了下面的公式(它建立在(int)(n + 0.5)整数舍入技巧上):

enter image description here

但它仍然没有给我统一分配。使用10000个样本的重复运行给出值为-1,0.1的比率为37:50:13。

请你建议更好的配方? (甚至整个伪随机数发生器功能)

13 个答案:

答案 0 :(得分:266)

最简单(也就是最好)的C ++(使用2011标准)答案是

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

无需重新发明轮子。无需担心偏见。无需担心将时间用作随机种子。

答案 1 :(得分:93)

快速,比你的更好,但仍然没有适当的统一分布式解决方案

output = min + (rand() % static_cast<int>(max - min + 1))

除非范围的大小是2的幂,否则无论rand()的质量如何,此方法都会生成biased non-uniform distributed个数字。要全面测试此方法的质量,请read this

答案 2 :(得分:60)

如果你的编译器支持C ++ 0x并且使用它是一个选项,那么新的标准<random>标题可能会满足你的需求。它具有高质量uniform_int_distribution,可以接受最小和最大边界(包括你需要的),你可以选择各种随机数发生器来插入该发行版。

这是生成在[-57,365]中均匀分布的一百万个随机int的代码。我已经使用了新的std <chrono>设施来计时,因为你提到性能是你的一个主要问题。

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

对我来说(2.8 GHz Intel Core i5)打印出来:

2.10268e + 07每秒随机数。

您可以通过将int传递给其构造函数来生成生成器:

    G g(seed);

如果您后来发现int未涵盖分发所需的范围,可以通过更改uniform_int_distribution之类的方式来解决此问题(例如改为long long):< / p>

    typedef std::uniform_int_distribution<long long> D;

如果您后来发现minstd_rand不是一个足够高质量的生成器,那么也可以轻松地将其换出。 E.g:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

对随机数生成器进行单独控制,随机分布可以非常自由。

我还计算(未显示)此分布的前4个“时刻”(使用minstd_rand)并将它们与theoretical values进行比较,以尝试量化分布的质量:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

x_前缀是指“预期”)

答案 3 :(得分:15)

让我们将问题分成两部分:

  • 生成0到(最大 - 最小)范围内的随机数n
  • 将min添加到该数字

第一部分显然是最难的。我们假设rand()的返回值是完全一致的。使用modulo会增加偏见 到第一个(RAND_MAX + 1) % (max-min+1)个数字。因此,如果我们能够将RAND_MAX神奇地更改为RAND_MAX - (RAND_MAX + 1) % (max-min+1),那么就不会有任何偏见。

事实证明,如果我们愿意允许伪非确定性进入算法的运行时间,我们就可以使用这种直觉。每当rand()返回一个太大的数字时,我们只需要另一个随机数,直到我们得到一个足够小的数字。

现在的运行时间为geometrically distributed,期望值为1/p,其中p是第一次尝试获得足够小数量的概率。由于RAND_MAX - (RAND_MAX + 1) % (max-min+1)始终小于(RAND_MAX + 1) / 2, 我们知道p > 1/2,所以预期的迭代次数总是小于2 适用于任何范围。应该可以使用这种技术在标准CPU上在不到一秒的时间内生成数千万个随机数。

编辑:

虽然上述技术上是正确的,但DSimon的答案在实践中可能更有用。你不应该自己实现这些东西。我已经看到很多拒绝采样的实现,通常很难看出它是否正确。

答案 4 :(得分:13)

Mersenne Twister怎么样? boost实现非常易于使用,并且在许多实际应用程序中经过了充分测试。我自己在几个学术项目中使用它,如人工智能和进化算法。

以下是他们制作一个简单的功能来滚动六面骰子的例子:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

哦,这里有一些更多的拉皮条,以防万一你不相信你应该使用它超过极差的rand()

  

Mersenne Twister是一个“随机的   数字“Makoto发明的发电机   Matsumoto和Takuji Nishimura;其   网站包括众多   算法的实现。

     

基本上,Mersenne Twister是一个   非常大的线性反馈偏移   寄存器。该算法在a上运行   19,937位种子,储存在   624无符号的624元素数组   整数。值2 ^ 19937-1是a   梅森素;技术   操纵种子是基于   较旧的“扭曲”算法 - 因此   “Mersenne Twister”这个名字。

     

梅森的一个吸引人的方面   Twister是它的二进制使用   操作 - 而不是   耗时的乘法 - 为   生成数字。算法也   有很长一段时间,而且很好   粒度。它既快又快   对非加密应用程序有效。

答案 5 :(得分:11)

int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

这是32768个整数到(nMax-nMin + 1)整数的映射。如果(nMax-nMin + 1)很小(如您的要求),映射将非常好。但请注意,如果(nMax-nMin + 1)很大,则映射将不起作用(例如 - 您无法以相同的概率将32768值映射到30000个值)。如果需要这样的范围 - 你应该使用32位或64位随机源,而不是15位rand(),或忽略超出范围的rand()结果。

答案 6 :(得分:4)

这是一个在[low, high]生成数字的无偏见版本:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

如果您的范围相当小,则没有理由在do循环中缓存比较的右侧。

答案 7 :(得分:3)

我推荐Boost.Random library,它非常详细,文档齐全,可以让您明确指定所需的分发,而在非加密方案中,实际上outperform可以实现典型的C库rand实现。< / p>

答案 8 :(得分:1)

假设min和max是int值, [和]表示包含此值, (和)表示不包括此值, 使用上面的方法使用c ++ rand()

获取正确的值

参考: for()[] define,visit:

https://en.wikipedia.org/wiki/Interval_(mathematics)

对于rand和srand函数或RAND_MAX定义,请访问:

http://en.cppreference.com/w/cpp/numeric/random/rand

[min,max]

int randNum = rand() % (max - min + 1) + min

(分钟,最大值)

int randNum = rand() % (max - min) + min + 1

[min,max]

int randNum = rand() % (max - min) + min

(分钟,最大)

int randNum = rand() % (max - min - 1) + min + 1

答案 9 :(得分:0)

在这个线程中已经讨论过拒绝抽样,但我想根据rand() % 2^something没有引入任何偏差这一事实提出一个优化,如上所述。

算法非常简单:

  • 计算大于间隔长度的最小功率2
  • 随机化一个数字&#34; new&#34;间隔
  • 如果小于原始间隔的长度,则返回该数字
    • 否则拒绝

这是我的示例代码:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

这特别适用于小间隔,因为2的功率将更接近&#34;到实际的间隔长度,所以未命中的数量会更小。

PS
显然避免递归会更有效率(不需要一遍又一遍地计算日志天花板......)但我认为这个例子更具可读性。

答案 10 :(得分:0)

请注意,在大多数建议中,您从rand()函数获得的初始随机值(通常是0到RAND_MAX)被简单地浪费了。您只能在其中创建一个随机数,而有一个声音程序可以为您提供更多随机数。

假定您想要整数随机数的[min,max]区域。我们从[0,max-min]

开始

以b = max-min + 1

为底

从代表您从基数b中的rand()获得的数字开始。

那样,您便有下限(log(b,RAND_MAX)),因为基b中的每个数字(除最后一个数字外)均表示[0,max-min]范围内的随机数。

当然,对于每个随机数r + min,最终移至[min,max]很简单。

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

如果NUM_DIGIT是您可以提取的以b为底的位数,则为

NUM_DIGIT = floor(log(b,RAND_MAX))

然后,上述操作是从提供b

答案 11 :(得分:-1)

这个公式很简单,所以试试这个表达式,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0

答案 12 :(得分:-2)

如果我没有弄错的话,以下表达应该是公正的:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

我在这里假设rand()给你一个0.0到1.0范围内的随机值,不包括1.0,max和min是整数,条件是min&lt;最大。