为什么rand() + rand()会产生负数?

我观察到rand()库函数在循环中被调用一次时,它几乎总是产生正数。

for (i = 0; i < 100; i++) {
printf("%d\n", rand());
}

但是当我添加两次rand()调用时,生成的数字现在有更多的负数。

for (i = 0; i < 100; i++) {
printf("%d = %d\n", rand(), (rand() + rand()));
}

有人能解释一下为什么我在第二种情况下看到负数吗?

PS:我在循环之前将种子初始化为srand(time(NULL))

46935 次浏览

rand()被定义为返回一个介于0RAND_MAX之间的整数。

rand() + rand()

可能会溢出。你观察到的可能是由整数溢出引起的未定义的行为的结果。

问题是加法。rand()返回0...RAND_MAXint值。所以,如果你把其中的两个加起来,你会得到RAND_MAX * 2。如果超出了INT_MAX,则加法的结果超出了int可以容纳的有效范围。带符号的值溢出是未定义的行为,可能导致你的键盘用外语与你对话。

由于在这里添加两个随机结果没有任何好处,所以简单的想法就是不要这样做。或者,你可以在加法之前将每个结果强制转换为unsigned int,如果它可以保存总和的话。或者使用大一点的字体。注意,long不一定比int宽,如果int至少是64位,则同样适用于long long !

结论:避免加法。它没有提供更多的“随机性”。如果需要更多位,可以将值sum = a + b * (RAND_MAX + 1)连接起来,但这也可能需要比int更大的数据类型。

正如你所说的原因是为了避免零结果:这不能通过添加两个rand()调用的结果来避免,因为两个rand()调用的结果都可以为零。相反,您可以只增加。如果RAND_MAX == INT_MAX,则不能在int中执行此操作。然而,(unsigned int)rand() + 1将非常非常可能。很可能(不是肯定的),因为它确实需要UINT_MAX > INT_MAX,这在我所知道的所有实现上都是真的(涵盖了相当多的嵌入式架构,dsp和过去30年的所有桌面,移动和服务器平台)。

警告:

虽然已经在这里的评论中提到,但请注意,将两个随机值相加得到的是均匀分布,但三角形分布就像滚动两个骰子:要得到12(两个骰子),两个骰子都必须显示6。对于11,已经有两个可能的变体:6 + 55 + 6,等等。

所以,加法从这个方面来说也是不好的。

还要注意,rand()生成的结果并不是相互独立的,因为它们是由伪随机数生成器生成的。还要注意的是,该标准没有规定计算值的质量或均匀分布。

这是对这个答案评论中澄清问题的回答,

我添加的原因是为了避免在我的代码中使用“0”作为随机数。Rand ()+ Rand()是我脑海中迅速出现的肮脏解决方案。

问题是避免0。提议的解决方案(至少)存在两个问题。一个是,正如其他答案所指出的,rand()+rand()可以调用未定义的行为。最好的建议是永远不要调用未定义的行为。另一个问题是不能保证rand()不会连续两次产生0。

下面的方法拒绝0,避免未定义的行为,并且在绝大多数情况下将比两次调用rand()更快:

int rnum;
for (rnum = rand(); rnum == 0; rnum = rand()) {}
// or do rnum = rand(); while (rnum == 0);

尽管其他人所说的可能溢出很可能是负数的原因,即使使用无符号整数也是如此。真正的问题实际上是使用时间/日期功能作为种子。如果你真正熟悉这个功能,你就会知道我为什么这么说。它真正做的是给出一个距离(经过的时间)从给定的日期/时间。虽然使用日期/时间功能作为rand()的种子是一种非常常见的做法,但它确实不是最佳选择。你应该寻找更好的选择,因为关于这个话题有很多理论,我不可能一一介绍。如果在这个等式中加上溢出的可能性,这种方法从一开始就注定要失败。

那些发布rand()+1的人正在使用最常用的解决方案,以确保他们不会得到负数。但是,这种方法也不是最好的方法。

您能做的最好的事情是花费额外的时间来编写和使用适当的异常处理,如果和/或当您最终得到零结果时,只添加到rand()数字。正确处理负数。rand()功能并不完美,因此需要与异常处理一起使用,以确保最终得到所需的结果。

花费额外的时间和精力来调查、研究和正确地实现rand()功能是非常值得的。这只是我的个人意见。祝你好运……

也许你可以尝试一种比较棘手的方法,确保2 rand()的sum返回的值永远不会超过RAND_MAX的值。一种可能的方法是sum = rand()/2 + rand()/2;这将确保对于RAND_MAX值为32767的16位编译器,即使两个rand都返回32767,即使(32767/2 = 16383)16383+16383 = 32766,也不会产生负和。

要避免0,试试这个:

int rnumb = rand()%(INT_MAX-1)+1;

你需要包含limits.h

基本上,rand()产生的数字在0RAND_MAX之间,在你的例子中是2 RAND_MAX > INT_MAX

可以对数据类型的最大值进行模数处理,以防止溢出。这当然会破坏随机数的分布,但rand只是一种快速获取随机数的方法。

#include <stdio.h>
#include <limits.h>


int main(void)
{
int i=0;


for (i=0; i<100; i++)
printf(" %d : %d \n", rand(), ((rand() % (INT_MAX/2))+(rand() % (INT_MAX/2))));


for (i=0; i<100; i++)
printf(" %d : %ld \n", rand(), ((rand() % (LONG_MAX/2))+(rand() % (LONG_MAX/2))));


return 0;
}

我添加的原因是为了避免在我的代码中使用“0”作为随机数。Rand ()+ Rand()是我脑海中迅速出现的肮脏解决方案。

一个简单的解决方案(好吧,称之为“黑客”),永远不会产生零结果,永远不会溢出:

x=(rand()/2)+1    // using divide  -or-
x=(rand()>>1)+1   // using shift which may be faster
// compiler optimization may use shift in both cases

这将限制最大值,但如果您不关心这一点,那么这应该可以为您工作。

谢谢。我添加的原因是为了避免在我的代码中使用“0”作为随机数。Rand ()+ Rand()是我脑海中迅速出现的肮脏解决方案

对我来说,这听起来像一个XY问题,为了不从rand()得到0,你调用rand()两次,使程序变慢,有一个新的挫折,得到0的可能性仍然存在。

另一个解决方案是使用uniform_int_distribution,它在定义的间隔内创建一个随机且均匀分布的数字:

https://wandbox.org/permlink/QKIHG4ghwJf1b7ZN

#include <random>
#include <array>
#include <iostream>
 

int main()
{
const int MAX_VALUE=50;
const int MIN_VALUE=1;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distrib(MIN_VALUE, MAX_VALUE);
std::array<int,MAX_VALUE-MIN_VALUE> weight={0};


for(int i=0; i<50000; i++) {
weight[distrib(gen)-MIN_VALUE]++;
}
    

for(int i=0;i<(int)weight.size();i++) {
std::cout << "value: " << MIN_VALUE+i << " times: " << weight[i] << std::endl;
}
}