在整个范围内均匀生成随机数


93

我需要在指定的时间间隔[max; min]中生成随机数。

同样,随机数应在整个间隔内均匀分布,而不是位于特定点上。

目前,我生成为:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

根据我的测试,随机数仅在一个点附近产生。

Example
min = 3604607;
max = 7654607;

生成的随机数:

3631594
3609293
3630000
3628441
3636376
3621404

从下面的答案中:好的,RAND_MAX是32767。我在C ++ Windows平台上。还有其他方法可以生成具有均匀分布的随机数吗?



1
我不知道C ++ rand()是统一的。您正在使用哪个库?cstdlib.hrand()不统一:cplusplus.com/reference/cstdlib/rand
迈克·沃伦

3
不,rand()是统一的(某些早期的错误实现中除外)。不均匀的是使用模数“%”运算符来限制范围。请参阅stackoverflow.com/questions/2999075/…以获取适当的解决方案,或者如果您有可用的“ arc4random_uniform”,那么也可以直接使用它。
John Meacham 2013年

@ Alien01:您是否考虑通过“ Shoe”(“为什么rand是一个坏主意”等)将接受的答案更改为答案?我的答案真的过时了,每次我得到支持时,我都会觉得有人在错误的过道旁奔跑。
peterchen '16

关于c ++ 11中随机性的漂亮白皮书
Pupsik

Answers:


153

为什么rand是个坏主意

您在此处获得的大多数答案都是使用rand函数和模运算符。该方法可能不会均匀地生成数字(取决于的范围和值RAND_MAX),因此不建议使用。

C ++ 11及其一代产品

随着C ++ 11的出现,出现了许多其他选择。很好地满足您的要求,其中之一非常适合:std::uniform_int_distribution。这是一个例子:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

这里的运行实例。

其他随机发生器

<random>提供了无数的其他随机数生成与不同类型的分布,包括伯努利,泊松分布和正常的。

如何打乱容器?

该标准提供std::shuffle,可以如下使用:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

该算法将以线性复杂度随机重新排列元素。

助推随机

如果您无权使用C ++ 11 +编译器,另一种选择是使用Boost.Random。它的接口与C ++ 11的接口非常相似。


22
请注意此答案,因为它要现代得多。
gsamaras 2014年

是正确的答案。谢谢!尽管如此,我还是希望对代码的每个步骤进行更深入的描述。例如什么是mt19937类型?
阿波罗

@Apollo文档说“ Matsumoto and Nishimura的32位Mersenne Twister,1998年”。我假设这是一种生成伪随机数的算法。
Shoe

@Shoe,对于给定范围,它以相同顺序生成数字1 9 6 2 8 7 1 4 7 7。每次我们运行程序时,您如何将其随机化?

1
@Richard有什么选择?
擦鞋

59

[edit] 警告:请勿rand()用于统计,模拟,加密或任何严重的事情。

这足以使数字对于匆忙的普通人来说显得随机,仅此而已。

有关更好的选择,请参见@Jefffrey的答复;有关加密安全的随机数,请参见此答案


通常,高位比低位显示出更好的分布,因此为简单起见,建议生成范围随机数的推荐方法是:

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

注意:确保RAND_MAX + 1不会溢出(感谢Demi)!

除法在区间[0,1)中生成一个随机数;将其“拉伸”到所需范围。仅当max-min + 1接近RAND_MAX时,您才需要Mark Ransom发布的“ BigRand()”函数。

这也避免了模数导致的切片问题,这会使您的数字更加恶化。


不能保证内置的随机数生成器具有统计仿真所需的质量。数字对人类“看起来是随机的”是可以的,但是对于严肃的应用,您应该采取更好的方法-或至少检查其属性(均匀分布通常是好的,但是值倾向于相关,并且顺序是确定的)。Knuth在随机数生成器上有一篇出色的文章(如果很难读),我最近发现LFSR非常出色,而且易于实现,因为它的属性适合您。


4
即使所需范围不超过RAND_MAX,BigRand也可以提供更好的结果。考虑一下RAND_MAX为32767时,您想要32767个可能的值-这32768个随机数中的两个(包括零)将映射到相同的输出,并且发生的可能性是其他两个随机数的两倍。几乎不是理想的随机属性!
Mark Ransom

7
(RAND_MAX + 1)是个坏主意。这可能会延期并为您带来负值。最好执行以下操作:((double)RAND_MAX)+ 1.0
Demi

3
@peterchen:我想你误解了demi在说什么。她的意思是:( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min 只需将转化次数增加一倍即可避免出现问题。
Mooing Duck 2013年

3
同样,这仅将分布从范围内的底部32767值更改为范围内的均匀分布的32767值,并且该算法永远不会选择其余的4017233值。
Mooing Duck 2013年

1
给定的答案是1。正确的等式是:(((double)rand()/(RAND_MAX + 1.0))*(max-min)+ min当不使用%时使用“ max-min + 1” * 。您将看到为什么在执行min = 0,max = 1时的原因。peterchen或@ peter-mortensen可以修改它。
davepc

17

我想通过简要概述2015年的技术水平来补充《愤怒的鞋子》和Peterchen的出色回答:

一些不错的选择

randutils

randutils(演示文稿)是一个有趣的新颖性,提供了简单的界面和(已声明的)强大的随机功能。它的缺点是,它增加了对项目的依赖,并且,它是新的,尚未经过广泛的测试。无论如何,免费(MIT许可证)和仅标头,我认为值得尝试。

样品最少:模具辊

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

即使对该库不感兴趣,该网站(http://www.pcg-random.org/)也提供了许多有关随机数生成主题的有趣文章,尤其是C ++库。

助推随机

Boost.Random (文档)是启发C++11的的库<random>,与之共享许多接口。尽管从理论上讲也是一个外部依赖项,Boost但现在它的状态为“准标准”库,并且其Random模块可以视为生成高质量随机数的经典选择。就C++11解决方案而言,它具有两个优点:

  • 它更可移植,只需要对C ++ 03的编译器支持
  • random_device使用系统特定的方法来提供高质量的播种

唯一的小缺陷是提供的模块random_device不只是标头,还必须编译和链接boost_random

样品最少:模具辊

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

尽管最小的样本可以很好地工作,但是实际程序应该使用以下改进:

  • mt19937一个thread_local:生成器很丰满(> 2 KB)并且最好不要分配在堆栈上
  • mt19937多于一个整数的种子:梅森扭转者(Mersenne Twister)具有很大的状态,并且可以在初始化期间利用更多的熵

一些不太好的选择

C ++ 11库

尽管是最惯用的解决方案,但该<random>库甚至不能满足其基本需求,也无法提供很多接口。缺陷在于std::random_device:标准不要求其输出(只要有entropy()return 0)就必须具有任何最低质量,并且自2015年起,MinGW(不是最常用的编译器,但几乎没有选择)会始终4在最小样本上打印。

样品最少:模具辊

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

如果实施不烂,则此解决方案应等同于Boost 1,并且适用相同的建议。

戈多的解决方案

样品最少:模具辊

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

这是一个简单,有效和简洁的解决方案。唯一的缺陷是,如果及时发布C ++ 17并且实验randint功能已被批准为新标准,则大约需要两年的时间进行编译。也许到那时,播种质量的保证也会提高。

更坏就是更好的解决方案

样品最少:模具辊

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

旧的C解决方案被认为是有害的,并且有充分的理由(请参阅此处的其他答案或此详细分析))。尽管如此,它仍然具有优点:简单,可移植,快速且诚实,从某种意义上说,人们知道一个随机数很难过,因此人们不打算将它们用于严肃的目的。

会计巨魔解决方案

样品最少:模具辊

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

尽管对于常规压模辊来说9的输出有些不寻常,但人们必须佩服此解决方案中优良品质的完美结合,该解决方案是最快,最简单,最易于缓存和最便携的解决方案。用9替换为4可以得到一个完美的生成器,适用于任何类型的龙与地下城死亡,同时仍然避免了带有符号的值1、2和3。唯一的小缺点是,由于迪尔伯特的会计巨魔脾气暴躁,该程序实际上引起未定义的行为。


randutils库现在称为PCG。
tay10r

11

如果RAND_MAX是32767,则可以轻松地使位数增加一倍。

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

我认为这行不通。伪随机数生成器通常是确定性的。例如,如果第一个rand电话返回0x1234,第二个电话返回0x5678,则得到0x12345678。那是唯一可以开始的0x1234号码,因为下一个号码永远是0x5678。您将获得32位结果,但可能只有32768个数字。
user694733 '19

@ user694733一个好的随机数生成器的周期大于它可以生成的输出数量,因此0x1234并不总是跟在0x5678之后。
Mark Ransom

9

如果可以,请使用Boost。我对他们的随机图书馆很幸运。

uniform_int 应该做你想做的。


我已经用merseinne捻线机对uniform_int进行了一些工作,不幸的是,在某些范围内,uniform_int返回的值并不像我期望的那样均匀。例如,junit_int <>(0,3)往往会产生比1或2更多的0
ScaryAardvark 2010年

@ScaryAardvark听起来很糟糕uniform_int。生成无偏输出很容易,这里有多个问题证明了该方法。
Mark Ransom

@马克·兰瑟姆 是的,我完全同意。
ScaryAardvark

8

如果您担心随机性而不是速度,则应使用安全的随机数生成方法。有几种方法可以做到这一点...最简单的方法是使用OpenSSL的 随机数生成器

您还可以使用加密算法(例如AES)编写自己的代码。通过选择一个种子和一个IV,然后连续地重新加密加密功能的输出。使用OpenSSL更容易,但不那么麻烦。


我不能使用任何第三方库?我仅限于C ++。
anand

然后走男子气概的路线,实施AES或其他某种加密算法。
SoapBox

2
RC4对于代码来说是微不足道的,并且对于所有实际用途而言,随机性都足够好(除了WEP,但这并不完全是RC4的错)。我的意思是,这是非常琐碎的代码。像20行左右 Wikipedia条目具有伪代码。
史蒂夫·杰索普

4
为什么不能使用第三方代码?如果这是一个家庭作业问题,您应该这样说,因为在这种情况下,许多人宁愿提供有用的提示,而不是提供完整的解决方案。如果不是家庭作业,那就踢那个说“没有第三方密码”的人,因为他是个白痴。
DevSolar'2

与OpenSSL rand()函数文档的更直接链接:openssl.org/docs/crypto/rand.html#
DevSolar 2010年

5

您应该查看RAND_MAX您的特定编译器/环境。我想如果rand()生成一个随机的16位数字,您会看到这些结果。(您似乎假设它将是一个32位数字)。

我不能保证这是答案,但是请发布您的值RAND_MAX,以及有关您环境的更多详细信息。



2

这不是代码,但是此逻辑可能会对您有所帮助。

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

如果希望数字在整个范围内均匀分布,则应将范围划分为多个相等的部分,以表示所需的点数。然后为每个部分获取一个具有最小值/最大值的随机数。

另一个要注意的是,您可能不应该使用rand()它,因为它实际上并不擅长生成随机数。我不知道您在什么平台上运行,但是可以调用像这样的更好的功能random()


1

[low, high)只要总范围小于RAND_MAX,这将在不使用浮点数的情况下提供范围内的均匀分布。

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

对于大于RAND_MAX的值,您需要类似

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

这大致就是std :: uniform_int_distribution做事情的方式。


0

从本质上讲,一小部分随机数不一定必须均匀分布。毕竟它们是随机的。我同意,如果随机数生成器生成的数字始终一致地被分组,则可能是有问题的。

但是请记住,随机性不一定是统一的。

编辑:我添加了“小样本”来澄清。


“均匀分布”具有定义明确的含义,标准随机生成器通常很接近。
peterchen

是的,您是对的,随机数生成器应产生的输出随着时间的推移通常分布均匀。我想我的意思是,在少数实例(如示例中所示的6个实例)上,输出将不会总是统一的。
克鲁格

克鲁格说得对。小样本中的均匀分布表明样本绝对不是随机的。
比尔蜥蜴

1
比尔,它表示没有这样的事情。小样本几乎没有意义,但是如果假设RNG是均匀的并且输出是均匀的,那为什么比不均匀的小样本更糟糕?
丹·代尔

2
无论哪种方式,重要的分布都表明是非随机的:我认为Bill仅意味着6个等距的结果也将是可疑的。在OP中,有6个值位于32k / 4M范围内,或小于所需范围的1%。这种误报的可能性太小了,无法争论。
史蒂夫·杰索普

0

男人3 rand对于1到10之间的数字给出的解决方案是:

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

在您的情况下,它将是:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

当然,这不是某些其他消息所指出的完美的随机性或均匀性,但这对于大多数情况而言已经足够。


1
这只是重新分配了分布,使其显得更加均匀,但实际上,即使在较大范围内(例如,OP的情况),实际上也没有任何变化
Mooing Duck 2013年

0

@解 ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

警告:不要忘记由于拉伸和可能的精度误差(即使RAND_MAX足够大),您也只能生成分布均匀的“ bin”,而不能生成[min,max]中的所有数字。


@解决方案:Bigrand

警告:请注意,这会使位数加倍,但通常仍然不能生成范围内的所有数字,即,BigRand()不一定会生成其范围内的所有数字。


信息:只要rand()的范围超出间隔范围,并且rand()为“均匀”,则您的方法(模)是“精细”。最多第一个最大-最小数字的误差为1 /(RAND_MAX +1)。

另外,我建议也切换到C ++ 11中的新随机程序包 e,该程序包比rand()提供更好,更多的实现。


0

这是我想出的解决方案:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

这是一个存储桶解决方案,在概念上类似于用于rand() / RAND_MAX获取0-1之间的浮点范围然后将其舍入到存储桶中的解决方案。但是,它使用纯整数数学,并利用整数除法底限将值四舍五入到最接近的存储桶。

它有一些假设。首先,假设RAND_MAX * (max - min + 1)总是适合int32_t。如果RAND_MAX为32767,并且使用32位int计算,则最大范围为32767。如果您的实现具有更大的RAND_MAX,则可以通过使用更大的整数(如int64_t)来克服此问题。其次,如果int64_t使用,但RAND_MAX仍为32767,则在比RAND_MAX您更大的范围内,可能的输出数字将开始出现“空洞”。对于从缩放衍生的任何解决方案,这可能是最大的问题rand()

尽管如此,对大量迭代进行测试仍表明,该方法对于小范围而言非常统一。但是,从数学上讲,这可能会(并且可能)具有一些小的偏差,并且当范围接近时可能会产生问题RAND_MAX。自己进行测试,然后确定它是否满足您的需求。


-1

当然,以下代码不会给您随机数,而是伪随机数。使用以下代码

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

例如:

int myRand = QUICK_RAND(10, 20);

你必须打电话

srand(time(0));  // Initialize random number generator.

否则,数字将不会接近随机。


1
问题是要求统一分配。提出的解决方案将不会产生均匀的分布。标准C ++库具有用于伪随机数生成的功能。这些提供均匀分布的,如果要求。
IInspectable

-3

我刚刚在互联网上找到了这个。这应该工作:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

请阐明您的需求,那里有大量PRNG算法。另外,如果您编辑主要问题而不是发布答复,将会更容易。
peterchen

这对我来说效果最好...我可以使用此公式获得更好的分布式随机数..
anand

4
如果您的范围超过RAND_MAX,则结果可能不一致。也就是说,无论调用函数多少次,都不会显示该范围内的值。
dmckee ---前主持人小猫

4
同样,如果max和min均为unsigned int,且min为0,max为MAX_UINT,则(((max)-(min)+1))将为0,结果始终为0。注意执行此类数学运算的溢出!如dmckee所述,这可以扩展目标范围内的分布,但不能保证超过RAND_MAX个唯一值。
jesup 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.