关于C#:为什么rand()+rand()产生负数?

Why does rand() + rand() produce negative numbers?

我注意到rand()库函数在一个循环中调用一次时,几乎总是产生正数。

1
2
3
4
for (i = 0; i < 100; i++) {
    printf("%d
"
, rand());
}

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

1
2
3
4
for (i = 0; i < 100; i++) {
    printf("%d = %d
"
, rand(), (rand() + rand()));
}

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

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


rand()定义为返回0RAND_MAX之间的整数。

1
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_MAX == INT_MAX,则不能在int中进行。然而,(unsigned int)rand() + 1将非常、非常有可能。很可能(不是决定性的),因为它确实需要UINT_MAX > INT_MAX,这在我所知道的所有实现中都是正确的(它涵盖了过去30年中相当多的嵌入式体系结构、DSP以及所有桌面、移动和服务器平台)。

警告:

尽管这里已经有了一些评论,但是请注意,添加两个随机值并不能得到均匀的分布,而是一个三角形的分布,比如滚动两个骰子:要得到12(两个骰子),两个骰子都必须显示6。对于11,已经有两种可能的变体:6 + 55 + 6等。

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

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


这是对在对本答案进行评论时澄清问题的回答,

the reason i was adding was to avoid '0' as the random number in my code. rand()+rand() was the quick dirty solution which readily came to my mind.

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

以下拒绝零,避免未定义的行为,在大多数情况下,将比两次调用rand()更快:

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <limits.h>

int main(void)
{
    int i=0;

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

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

    return 0;
}

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


the reason i was adding was to avoid '0' as the random number in my code. rand()+rand() was the quick dirty solution which readily came to my mind.

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

1
2
3
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,请尝试以下操作:

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

您需要包括limits.h


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

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

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

花费额外的时间和精力来调查、研究和正确地实现rand()功能是非常值得花费时间和精力的。就我的两分钱。祝你好运…