题 如何缩小rand()的数字?


以下代码每秒输出一个随机数:

int main ()
{
    srand(time(NULL)); // Seeds number generator with execution time.

    while (true)
    {
        int rawRand = rand();

        std::cout << rawRand << std::endl;

        sleep(1);
    }
}

我如何调整这些数字的大小,以便它们总是在0-100的范围内?


34
2017-11-16 15:46


起源


int GetRandom(){return 59; / *完全随机选取的数字* /} - AlexanderMP
这是我看到的xkcd参考吗? :P - Maxpm
呐,看到了太多的地方说出确切的消息来源,并且能够放弃自己写作。 - AlexanderMP
睡眠时间以毫秒表示,使用睡眠(1000)睡眠一秒钟。 - Nikola Smiljanić
@Nikola不是我正在使用的实现,它不是。多么奇怪。 - Maxpm


答案:


如果您正在使用C ++并且关注良好的分发,则可以使用 TR1 C ++ 11 <random>

#include <random>

std::random_device rseed;
std::mt19937 rgen(rseed()); // mersenne_twister
std::uniform_int_distribution<int> idist(0,100); // [0,100]

std::cout << idist(rgen) << std::endl;

75
2017-11-16 17:02



虽然这是获得随机数均匀分布的正确方法,但这并不能解决MaxPM的问题,即无法获得良好的分布,但询问“如何缩小rand()中的数字”。 - dr jimbob
random_device 不会一直有效:每次在我的情况下都会返回34992116121。 - AbcAeffchen
@AbcAeffchen:不幸的是,您使用的编译器/版本是什么?你可能会看到同样的问题 这另一个问题。 - Blastfurnace
我正在使用gcc 4.9.1(64位版本)。谢谢你的链接。 - AbcAeffchen
@AbcAeffchen:我没有4.9.1测试,但我知道它可以工作 gcc 4.8.1 和Visual C ++ 2010-2013。我用谷歌搜索gcc和 std::random_device 但没有发现任何事,抱歉。 - Blastfurnace


到目前为止发布的所有示例实际上都给出了分布不均的结果经常执行代码并创建统计信息以查看值如何变得偏斜。

生成真实的更好方法 制服 任意范围内的随机数分布[0, ñ]是以下(假设 rand 实际上遵循统一分布,这是非常明显的):

unsigned result;
do {
    result = rand();
} while (result > N);

当然,这种方法很慢,但它  产生良好的分布。一个稍微聪明的方法是找到最大倍数 ñ 小于 RAND_MAX 并将其用作上限。在那之后,人们可以安全地接受 result % (N + 1)

作出解释 为什么 天真模数方法很糟糕,为什么上面的更好, 请参阅Julienne的优秀文章 运用 rand


30
2017-11-16 15:55



实际上,PRNG产生均匀分布的数字应该是你能想到的。可以找到稍微聪明的方法 java.util.Random#nextInt(int) 例如。 - Joey
通过这样做,您可以轻松做得更好 while(result > (RAND_MAX - RAND_MAX % N)) 然后除以 RAND_MAX/N。对于小N,你扔掉的数字要少得多,但保持均匀分布。 - Adam Rosenfield
@Joey:Java方式非常聪明,但有点难以解释,我不希望初学者(甚至中级)程序员在统计学方面没有特殊知识来理解它。我认为,在给出正确分布的同时,我仍然很容易解释和实施。 - Konrad Rudolph
@Adam:这是我解释过的“稍微聪明的方式”,没有提供代码。但是,我不明白为什么你会分开 RAND_MAX/N。只是用 % (N+1) 一定很好吗? - Konrad Rudolph
虽然这绝对是真的;效果很轻微。 RAND_MAX至少为32677,在我的机器上为2,147,483,647。对于最小RAND_MAX,这意味着0-77范围内的每个数字出现327次,而78-99中的数字仅出现326次,使得它们减少0.3%。对于我的机器的RAND_MAX,差异是数字0-47比数字48-99更可能是0.000 005%。对于大多数需求(例如,在严重的蒙特卡罗建模之外),简单模数将正常工作。 - dr jimbob


int rawRand = rand() % 101;

请参阅(更多详细信息):

rand - C ++参考

其他人也指出,这不会给你最好的随机数分布。如果在您的代码中这种事情很重要,您将不得不这样做:

int rawRand = (rand() * 1.0 / RAND_MAX) * 100;


25
2017-11-16 15:49



请不要在实践中使用这种方法 - 这很糟糕。 - Konrad Rudolph
请注意,您将从中获得稍微不均匀的分布。较低的数字通常会以这种方式出现。有关解决此问题的好方法,请查看 java.util.Random#nextInt(int)。 - Joey
就像我之前说的那样,使用modulo方法并不是一个完美的随机方法。 100个数字,uint有648个完整范围0-100,一个范围0-87。因此,0-87的数字比88-100的数字有更好的机会。 - AlexanderMP
对于需要随机数的人,他们不会使用rand来生成它们。模数和重新缩放范围调整引入的失真仅在您实际上首先具有随机分布时才有意义。 - Michael Shaw
-1。你仍然会得到一个不均匀的分布。 rand() 有RAND_MAX + 1个值;除非它是101的倍数(它可能不是),否则没有办法将它们分配给101个桶而没有其中一个更大。 - tc.


看到 man 3 rand  - 你需要通过除以来缩放 RAND_MAX 获得范围[0,1],之后您可以将目标范围乘以100。


4
2017-11-16 15:48



有趣。这种方法比模数法有什么优势吗? - Maxpm
好吧,取决于垃圾 rand() 是开始。但这通常很垃圾。 - Steve Jessop
不可以。不均匀性的传播方式不同。但是你仍然比其他人更频繁地获得一些数字。 - Joey
+1并且我有点惊讶这是建议分裂的唯一答案 RAND_MAX 而不是 % 模量。 - aschepler
@Joey:关键是它避免了在实践中看到的最令人发指的不良行为。例如LCG,其中最低有效位在连续样本上交替。因此,如果采用偶数的模数,则您的值将具有相同的属性。如果你扩展,他们至少会躲避那颗子弹。要记住的事情 rand() 是允许它是一个残暴的PRNG。任何使用它都是可疑的,如果需要好的随机数,但有些甚至比其他人更可疑。 - Steve Jessop


你可以做

cout << rawRand % 100 << endl; // Outputs between 0 and 99

cout << rawRand % 101 << endl; // outputs between 0 and 100

为人民贬低;注意这最初发布后一分钟我留下了评论:

http://www.cplusplus.com/reference/clibrary/cstdlib/rand “请注意,这个模运算不会在跨度中生成真正均匀分布的随机数(因为在大多数情况下,较低的数字稍微更可能),但它通常是短跨度的良好近似值。”

使用64位整数并使用100个数字作为输出时,数字0-16用数字表示1.00000000000000000455%(相对精度相同,分布为1%,大约10-18),而数字17-99用数字表示0.99999999999999999913%。是的,没有完美的分布,但对于小跨度非常好的近似。

另请注意,OP在哪里要求分配相同的号码?对于我们所知道的所有这些都被用于小偏差无关紧要的目的(例如,加密以外的任何东西 - 如果他们使用加密数字这个问题对于他们编写自己的密码学来说太天真了)。

编辑  - 对于真正关心随机数均匀分布的人,以下代码有效。请注意,这不一定是64位随机整数的最佳选择,它需要两次调用 rand()每10 ^ 18次电话一次。

unsigned N = 100; // want numbers 0-99
unsigned long randTruncation = (RAND_MAX / N) * N; 
// include every number the N times by ensuring rawRand is between 0 and randTruncation - 1 or regenerate.
unsigned long rawRand = rand();

while (rawRand >= randTruncation) {
    rawRand = rand();  
// with 64-bit int and range of 0-99 will need to generate two random numbers
// about 1 in every (2^63)/16 ~ 10^18 times (1 million million times)

// with 32-bit int and range of 0-99 will need to generate two random numbers 
// once every 46 million times.

}
cout << rawRand % N << stdl::endl;

4
2017-11-16 15:49



从 cplusplus.com/reference/clibrary/cstdlib/rand  “请注意,这个模运算不会在跨度中生成真正均匀分布的随机数(因为在大多数情况下,较低的数字稍微更可能),但它通常是短跨度的良好近似值。” - dr jimbob


对于范围从最小到最大(包括),使用: int result = rand() % (max - min + 1) + min;


0
2017-11-16 15:49





你想要多长时间的答案。

最简单的是使用除以101时的余数进行转换:

int value = rawRand % 101;

半精灵会使用双打重新缩放:

double dbl = 100 * ((double)rawRand / RAND_MAX);
int ivalue = (int)(dbl + 0.5);   // round up for above 0.5

纯粹主义者会说rand不会产生随机数。

对于您的信息,随机数的质量通过采用一系列数字然后计算该序列的来源是随机的数学概率来测量。如果你是随机性的话,使用余数的简单hack是一个非常糟糕的选择。


-4
2017-11-16 15:59





有些人发布了以下代码作为示例:

int rawRand = (rand() / RAND_MAX) * 100;

这是解决问题的无效方法,因为rand()和RAND_MAX都是整数。在C ++中,这会导致积分除法,这将截断结果小数点。当RAND_MAX> = rand()时,该操作的结果是1或0,这意味着rawRand只能是0或100.执行此操作的正确方法如下:

int rawRand = (rand() / static_cast<double>(RAND_MAX)) * 100;

由于一个操作数现在是一个双精度浮点除法,它将返回0到1之间的适当值。


-4
2017-11-16 16:57



这只是部分正确,仍然不会生成均匀分布的数字 rawRand == 100 这是非常不可能的 RAND_MAX “生产”它。 - Micha Wiedenmann


rawRand%101将给出[0-100],包括[0-100]。


-5
2017-11-16 15:48



这会让他们不随机。除非在适当的范围内执行模数,否则分布测试的均匀性将失败,或者除数大约为2的幂。 - ingyhere