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

Posted

技术标签:

【中文标题】在整个范围内均匀生成随机数【英文标题】:Generate random numbers uniformly over an entire range 【发布时间】:2010-09-22 06:29:37 【问题描述】:

我需要在指定的区间 [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 平台上。有没有其他方法可以生成均匀分布的随机数?

【问题讨论】:

构建一个 Dice-O-Matic:gamesbyemail.com/News/DiceOMatic 我不知道 C++ 的 rand() 是统一的。你用的是哪个库? cstdlib.hrand() 不统一:cplusplus.com/reference/cstdlib/rand 不,rand() 是统一的(除了一些早期的错误实现)。不统一的是使用模数“%”运算符来限制范围。请参阅***.com/questions/2999075/… 以获得正确的解决方案,或者如果您有 'arc4random_uniform' 可用,那么您也可以直接使用它。 @Alien01:您是否考虑将接受的答案更改为“鞋”(“为什么 rand 是个坏主意”等)?我的答案真的过时了,每次我得到一个赞成票时,我都觉得有人跑错了通道。 不错 white paper 关于 c++11 中的随机数。 【参考方案1】:

如果您希望数字在范围内均匀分布,您应该将范围分成多个相等的部分,这些部分代表您需要的点数。然后为每个部分获取一个带有最小值/最大值的随机数。

另外注意,您可能不应该使用rand(),因为它实际上并不擅长生成随机数。我不知道您在哪个平台上运行,但您可能可以调用更好的函数,例如 random()

【讨论】:

【参考方案2】:

检查RAND_MAX 在你的系统上是什么——我猜它只有 16 位,你的范围太大了。

除此之外,请参阅以下讨论:Generating Random Integers within a Desired Range 以及有关使用(或不使用)C rand() function 的说明。

【讨论】:

好的 RAND_MAX 是 32767。我在 C++ windows 平台上。。有没有其他方法可以生成均匀分布的随机数?【参考方案3】:

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

您也可以使用加密算法(如AES)编写自己的代码。通过选择一个种子和一个IV,然后不断地重新加密加密函数的输出。使用 OpenSSL 更容易,但不那么有男子气概。

【讨论】:

我不能使用任何第三方库?我只能使用 C++。 然后走男子汉路线,实现AES或其他一些加密算法。 RC4 对于代码来说是微不足道的,并且对于所有实际目的来说足够随机(WEP 除外,但这不完全是 RC4 的错)。我的意思是,这是非常简单的代码。比如,20行左右。***条目有伪代码。 为什么不能使用第三方代码?如果这是一个家庭作业问题,你应该这样说,因为在这种情况下,很多人宁愿给出有用的提示,而不是提供完整的解决方案。如果这不是家庭作业,那就去踢那个说“没有第 3 方代码”的人,因为他是个白痴。 OpenSSL rand() 函数文档的更多直接链接:openssl.org/docs/crypto/rand.html#【参考方案4】:

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

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

【讨论】:

【参考方案5】:

就其性质而言,少量随机数样本不必均匀分布。毕竟,它们是随机的。我同意,如果随机数生成器生成的数字始终似乎是分组的,那么它可能有问题。

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

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

【讨论】:

“均匀分布”有很好的定义,标准的随机生成器通常很接近。 是的,你是对的,随机数生成器应该产生随着时间在其分布中通常是均匀的输出。我想我的观点是,在少数实例(如示例中所示的 6 个)中,输出并不总是一致的。 克鲁格是对的。小样本中的均匀分布表明该样本绝对不是随机的。 比尔,它表明没有这样的事情。小样本大多是没有意义的,但是如果RNG应该是均匀的,输出是均匀的,那为什么比非均匀的小样本差? 任何一种方式的显着分布都表明非随机性:我认为比尔只是意味着 6 个等距的结果也值得怀疑。在 OP 中,6 个值位于 32k/4M 的范围内,或小于所需范围的 1%。这是误报的可能性太小,无法争论。【参考方案6】:

如果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 一个好的随机数生成器的周期大于它可以生成的输出数量,因此 0x1234 不会总是后跟 0x5678。【参考方案7】:

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

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

【讨论】:

请说明您需要它们的用途,那里有大量的 PRNG 算法。此外,如果您编辑主要问题而不是发布回复,会更容易。 这对我来说效果最好......我可以用这个公式得到更好的分布随机数...... 如果您的范围超过 RAND_MAX,结果可能不会一致。也就是说,无论调用多少次函数,该范围内的值都不会被表示。 另外,如果max和min都是unsigned int,min为0,max为MAX_UINT,则((max)-(min)+1)为0,结果为总是 0。当心这种类型的数学运算时溢出!正如 dmckee 所指出的,这会在目标范围内扩展分布,但不能保证超过 RAND_MAX 的唯一值。【参考方案8】:

man 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)));

当然,这并不像其他一些消息所指出的那样是完美的随机性或一致性,但这对于大多数情况来说已经足够了。

【讨论】:

这只是将分布重新排列为 appear 更均匀,但实际上对于大范围(例如 OP 的情况)不再如此【参考方案9】:

[编辑] 警告:不要将rand() 用于统计、模拟、密码学或任何严肃的事情。

对于一个匆忙的普通人来说,让数字看起来随机就足够了,仅此而已。

请参阅@Jefffrey's reply 了解更好的选择,或this answer 了解加密安全随机数。


一般而言,高位比低位显示出更好的分布,因此为简单目的生成范围随机数的推荐方法是:

((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 非常出色,而且非常易于实现,因为它的属性适合您。

【讨论】:

BigRand 可以提供更好的结果,即使所需的范围不超过 RAND_MAX。考虑当 RAND_MAX 为 32767 并且您想要 32767 个可能值时 - 这 32768 个随机数中的两个(包括零)将映射到相同的输出,并且出现的可能性是其他的两倍。几乎不是一个理想的随机属性! (RAND_MAX + 1) 是个坏主意。这可以翻转并给你一个负值。更好的做法是:((double)RAND_MAX) + 1.0 @peterchen:我想你误解了黛米在说什么。她的意思是:( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min 只需将转化率提高一倍即可避免问题。 另外,这只是将范围内底部的 32767 个值的分布更改为该范围内均匀分布的 32767 个值,并且该算法永远不会选择剩余的 4017233 个值。 给定的答案是 1。正确的公式是:((double) rand() / (RAND_MAX+1.0)) * (max-min) + min "max-min+1 " 在使用 % 而不是 * 时使用。你会明白为什么当你做 min=0, max=1 时。 peterchen 或 @peter-mortensen 可以修改它吗?【参考方案10】:

如果可以,请使用Boost。我的random library 祝我好运。

uniform_int 应该做你想做的事。

【讨论】:

我已经使用 merseinne twister 对 uniform_int 进行了一些工作,不幸的是,对于某些范围,uniform_int 返回的值并不像我预期的那样统一。例如 uniform_int( 0, 3 ) 倾向于产生比 1 或 2 更多的 0 @ScaryAardvark 这听起来像是 uniform_int 的糟糕实现。生成无偏见的输出非常容易,这里有多个问题演示了该方法。 @Mark Ransom。是的,我完全同意。【参考方案11】:

为什么rand 是个坏主意

您在这里得到的大多数答案都使用了rand 函数和模运算符。该方法may not generate numbers uniformly(取决于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';

here 是正在运行的示例。

模板功能可能会有所帮助:

template<typename T>
T random(T range_from, T range_to) 
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<T>    distr(range_from, range_to);
    return distr(generator);

其他随机生成器

&lt;random&gt; header 提供了无数其他具有不同分布类型的随机数生成器,包括伯努利、泊松和正态分布。

如何随机播放容器?

标准提供了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);

该算法将随机重新排序元素,具有线性复杂度。

Boost.Random

如果您无法访问 C++11+ 编译器,另一种选择是使用 Boost.Random。它的界面与 C++11 非常相似。

【讨论】:

请注意这个答案,因为它更现代。 这个是正确的答案。谢谢!尽管如此,我还是希望看到对该代码每一步的更深入的描述。例如。什么是mt19937 类型? @Apollo 文档说“松本和西村的 32 位 Mersenne Twister,1998 年”。我假设它是一种生成伪随机数的算法。 @Shoe,对于给定的范围,它以相同的顺序生成数字,1 9 6 2 8 7 1 4 7 7。每次我们运行程序时如何随机化? @Richard 还有什么选择?【参考方案12】:

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

警告:不要忘记由于拉伸和可能的精度错误(即使 RAND_MAX 足够大),您将只能生成均匀分布的“箱”,而不是所有数字[最小,最大]。


@Solution: Bigrand

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


信息:只要 rand() 的范围超出您的区间范围并且 rand() 是“统一的”,您的方法(模数)就“很好”。最多前一个 max - min 数的误差为 1/(RAND_MAX +1)。

另外,我建议也切换到 C++11 中的新 random packagee,它提供了比 rand() 更好、更多种类的实现。

【讨论】:

【参考方案13】:

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

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);

【讨论】:

【参考方案14】:

这应该在[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 做事的方式。

【讨论】:

【参考方案15】:

我想通过对 2015 年最新技术的简短概述来补充 Angry Shoe 和 peterchen 的出色回答:

一些不错的选择

randutils

randutils 库(presentation) 是一个有趣的新奇事物,它提供了一个简单的界面和(声明的)强大的随机功能。它的缺点是它增加了对您的项目的依赖,而且它是新的,还没有经过广泛的测试。无论如何,免费(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

Boost.Random (documentation) 是启发C++11&lt;random&gt; 的库,与他们共享大部分界面。 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';

虽然最小样本运行良好,但实际程序应该使用一对改进:

make mt19937 a thread_local:生成器相当丰满(> 2 KB),最好不要分配在堆栈上 种子mt19937 具有多个整数:Mersenne Twister 的状态很大,可以在初始化期间利用更多的熵

一些不太好的选择

C++11 库

虽然是最惯用的解决方案,但&lt;random&gt; 库并没有提供太多以换取其接口的复杂性,即使是基本需求也是如此。缺陷在于std::random_device:标准不要求其输出的任何最低质量(只要entropy() 返回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 解决方案,同样的建议也适用。

戈多的解决方案

最小样本:一个骰子

#include <iostream>
#include <random>

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

这是一个简单、有效且简洁的解决方案。唯一的缺陷,编译需要一段时间——大约两年,前提是 C++17 准时发布,实验性的randint 函数被批准进入新标准。或许到那个时候,播种质量的保障也会有所改善。

worse-is-better 解决方案

最小样本:一个骰子

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

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

旧的 C 解决方案被认为是有害的,并且有充分的理由(请参阅此处的其他答案或 this detailed analysis)。尽管如此,它还是有它的优点:简单、便携、快速和诚实,因为众所周知,一个人得到的随机数并不像样,因此人们不会试图将它们用于严肃的目的。

会计巨魔解决方案

最小样本:一个骰子

#include <iostream>

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

虽然 9 对于常规掷骰子来说有点不寻常,但人们不得不佩服这个解决方案中优良品质的完美结合,它设法成为最快、最简单、最易于缓存和最便携的解决方案。通过用 4 代替 9,可以得到任何类型的龙与地下城死亡的完美生成器,同时仍然避免包含符号的值 1、2 和 3。唯一的小缺陷是,由于呆伯特会计巨魔的坏脾气,这个程序实际上会产生未定义的行为。

【讨论】:

randutils 库现在称为 PCG。【参考方案16】:

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

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

例如:

int myRand = QUICK_RAND(10, 20);

你必须打电话

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

否则数字不会接近随机。

【讨论】:

问题是要求均匀分布。该提议的解决方案不会产生均匀分布。标准 C++ 库具有用于 Pseudo-random number generation 的设施。如果需要,那些确实提供统一分布。【参考方案17】:

这是我想出的解决方案:

#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 时可能会出现问题。自行测试并确定它是否满足您的需求。

【讨论】:

以上是关于在整个范围内均匀生成随机数的主要内容,如果未能解决你的问题,请参考以下文章

扩展随机数

在矩形内(均匀地)生成随机点?

随机数值域拓宽

javascript生成规定范围的随机整数

将随机范围从 1-5 扩展到 1-7

Java 生成随机数