我需要一个能在给定范围内生成随机整数的函数(包括边界值)。我没有不合理的质量/随机性要求,我有四个要求:
我目前有以下C ++代码:
output = min + (rand() * (int)(max - min) / RAND_MAX)
问题是,它并不是真正统一的 - 只有当rand()= RAND_MAX时才返回max(对于Visual C ++,它是1/32727)。这是小范围的主要问题,例如< -1,1>,其中最后一个值几乎从不返回。
所以我抓住笔和纸,然后想出了下面的公式(它建立在(int)(n + 0.5)整数舍入技巧上):
但它仍然没有给我统一分配。使用10000个样本的重复运行给出值为-1,0.1的比率为37:50: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)
让我们将问题分成两部分:
n
。第一部分显然是最难的。我们假设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
没有引入任何偏差这一事实提出一个优化,如上所述。
算法非常简单:
这是我的示例代码:
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;最大。