我注意到rand()库函数在一个循环中调用一次时,几乎总是产生正数。
1 2 3 4
| for (i = 0; i < 100; i ++) {
printf("%d
", rand());
} |
但是当我添加两个rand()调用时,生成的数字现在有更多的负数。
有人能解释为什么我在第二种情况下看到负数吗?
PS:我在循环之前初始化seed为srand(time(NULL))。
- rand()不能是负数…
- rand()+rand()可以欠流
- 您的编译器的RAND_MAX是什么?你通常可以在stdlib.h中找到它。(有趣的是:检查man 3 rand,它带有一行描述"坏随机数生成器"。)
- 如果用无符号int或long long代替int作为说明符呢?
- @萨加卡:那将是未定义的行为。rand()+rand()是一个int。
- 做每个理智的程序员都会做的事情。我宁愿有一个积极的UB而不是一个消极的!;)
- @六:那不是乌兰巴托的寄托,因为已经发生了附加。你不能让ub成为被定义的行为。一个头脑清醒的程序员会避开乌兰巴托。
- 即使没有溢出,rand() + rand()也会产生一个三角形的分布(如滚动两个骰子),而不是一个均匀的分布。这可能不是你想要的。
- 你说的"它几乎总是产生正数"是什么意思?rand()的定义是产生特定范围内的数字(0到rand_max之间),因此您应该看到的唯一非正数是零。那么在第一种情况下,你在哪里看到负数呢?
- @DAN04:但是当rand_max等于int_max时溢出,abs(rand()+rand())再次具有均匀分布。
- 另外,在生成多个随机数时,考虑arc4random()。
- 另外,考虑到rand()+rand()在0和2*rand_max之间没有产生相同的随机分布,同样的原因是滚动6面模具两次会产生与滚动12面模具一次不同的结果。
- @奥拉夫,你没尝到那种讽刺的味道吗?
- 不知道这个问题是怎么得到这么多赞成票的…对于每个程序员来说,值太大和溢出应该是一个基本的和熟悉的概念。
- @扎比斯:对不起,我有时甚至用自己的语言也不擅长这一点:—)
- @祥吉:"应该……"嗯,我应该得到1美分(无论什么货币)。有很多人不知道二进制算术和C标准。大多数人认为2s补码是有符号值的唯一表示,如果他们考虑到这一点的话。
- @祥吉:问"A"问题没问题,基本的还是复杂的。你可能会错过一些东西。
- 提示:尝试将随机数保存到变量中,并打印带有负和的对。
- @奥拉夫什么是乌兰巴托?你在说什么?
- @当然可以。这就是问题所在。只是有点惊讶,它得到了这么多的赞成票,成为了时事通讯中的头号问题。
- @地狱耶鲁:未定义的巴哈维奥
- 它欠我们的爱:(
rand()定义为返回0和RAND_MAX之间的整数。
可能溢出。您所观察到的可能是由于整数溢出导致的未定义行为的结果。
- 评论不用于扩展讨论;此对话已被移动到聊天。
- 实际上,这取决于rand是否返回有符号或无符号整数,因为无符号整数的溢出定义良好。
- @Jakubarnod:标准将rand()定义为int。
- @奥拉夫哦,对不起,我没有注意到这是一个C语言的问题,认为这是语言不可知论。
- @Jakubarnold:作为溢出行为,每种语言如何指定不同的行为?例如,当int增长时,python没有任何可用内存。
- @它取决于语言决定如何表示有符号整数。Java没有检测整数溢出(直到Java 8)的机制,并定义了它的环绕,并且只使用2的补码表示,并定义了它对于有符号整数溢出的合法性。C显然支持2的补码。
- @蓝月:这正是我的观点。但是,小心点,否则我们会被转移到聊天室去:(C允许除2s之外的补码,但GCC实际上不允许,btw。)
- @即使jakubarnold返回unsigned;使用%d打印无符号整数是未定义的。
- 正如下面的评论?它可以溢出。当这种情况发生时,它会从它能取的最大的正数,直接变为负数。
- @埃文卡斯莱克不,这不是普遍的行为。你说的是关于2的补码表示。但是C语言也允许其他的表示。C语言规范表示有符号整数溢出未定义。因此,一般来说,任何程序都不应该依赖于这种行为,并且需要仔细编码,以避免导致有符号整数溢出。但这不适用于无符号整数,因为它们将以定义良好的(约简模2)方式"环绕"。[继续]…
- 这是C标准中与有符号整数溢出相关的引用:如果在表达式的计算过程中发生异常情况(即,如果结果没有数学定义或不在其类型的可表示值范围内),则行为未定义。
- @我站在蓝月纠正。
- @Evancarslake从C编译器确实使用标准的问题上移开了一点,对于有符号整数,如果他们知道b > 0,他们可以假设a + b > a。他们还可以假设,如果有后来执行的语句a + 5,那么当前值低于INT_MAX - 5。因此,即使在没有陷阱程序的2的补码处理器/解释器上,也可能不会表现得像int是没有陷阱的2的补码。
问题是增加了。rand()返回0...RAND_MAX的int值。所以,如果你再加上两个,你就可以得到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 + 5或5 + 6等。
所以,从这方面来说,添加也是不好的。
还请注意,rand()生成的结果并不相互独立,因为它们是由伪随机数生成器生成的。还要注意,本标准并未规定计算值的质量或均匀分布。
- 谢谢。我添加的原因是为了避免将"0"作为代码中的随机数。rand()+rand()是一个快速而肮脏的解决方案,我很容易想到。
- @巴德玛:那如果两个电话都返回0怎么办?
- 机会有多大?与使用一个rand()相比更罕见
- @巴德玛:我只是想知道埃多克斯1〔9〕是否得到了标准的保证。(听起来可能,但不确定是否需要)。如果是这样的话,您只需强制转换一个结果和增量(按这个顺序!).
- 是的,那是可以做的另一件事。
- @Badmad:考虑到这一点,理想情况下,随机数生成器需要均匀分布。这意味着所有数字出现的机会都相等。现在,对于rand()为0,这是一个单值。但是,rand()+rand()可以用两种方法产生0:两个调用都返回0,或者两个调用都返回半int_max,当它们求和时,结果将为0。在这种情况下,您现在获得0的可能性是原来的两倍。但是,如果rand_max大于int_max的一半,则更多的值可以和为0。本质上,两个均匀分布的和是不均匀的。
- @尼古拉斯·雷切特:连续得到两个0的可能性不到两倍。p(0)=1/(rand_max+1)。P(0,0)=P(0)*P(0)。
- 当您想要一个非均匀分布时,可以增加多个随机数:stackoverflow.com/questions/30492259/&hellip;
- @ C?乌尔:是的,但这不是重点。您仍然需要注意溢出/ub。不过,我会更正一下我的答案-谢谢。这里的一个一般问题是在算法和实现之间进行划分——这个问题显然是关于实现的。
- 为了避免0,简单的"当结果为0时,重新滚动"?
- @Olivierdulac:有些应用程序不能允许循环(是的,这是非常不可能的,但是-是的)。我的观点是,关于int溢出的更多信息。
- 添加它们不仅是避免0的一种糟糕方法,而且还会导致不均匀分布。你得到一个像掷骰子结果一样的分布:7是2或12的6倍。
- @Olaf Re:正如我所读的&167;6.2.5&167;6.2.5 9"有符号整数类型的非负值范围是相应无符号整数类型的子范围"表示UINT_MAX >= INT_MAX,而不是UINT_MAX > INT_MAX。字符类型有其他限制。我在INTn_MAX == UINTn_MAX中遇到的唯一一个系统是在一个超宽的类型上被比特的,其中底层代码只处理有符号数学,所以最宽的无符号类型有一个填充位而不是一个符号位。
- @谢谢你指出这一点。实际上我以为我已经涵盖了这一点,但也许我的措辞有点不清楚。实际上,我并没有说明>是有保证的,但这是添加不调用ub的必要要求。当我写这个答案的时候,我对标准吹毛求疵还很陌生。不管怎样,我希望新措辞更清楚一点。
- @Olaf很好的更新。在imo中,代码可以断言Utype_MAX/2 == Stype_MAX,除非您还希望1的补码、符号大小、9位字节和其他异域/历史平台具有高度的可移植性。有趣的练习,但不那么重要。
- 不需要划分,可能会增加另一个问题。原始条件>就足够了。只需在代码中使用_Static_assert。但不管怎样,对于大多数应用来说,这是一个肮脏的黑客行为,因为很少有人想要完整的范围。
这是对在对本答案进行评论时澄清问题的回答,
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() + 1呢?
- @可能溢出的askvictor(尽管不太可能)。
- @Gerrit-取决于max_int和rand_max
- @Askvictor哦,对了。我原以为它们是一样的,但这在任何地方都没有说明。我的错误。
- @格瑞特,如果他们不一样,我会很惊讶,但我想这是一个供学究使用的地方。
- @askvictor——在我的电脑上,有了我的编译器,RAND_MAX就是MAX_INT。所以添加一个可能会溢出。不太可能,但并非不可能。我使用过其他系统,其中RAND_MAX仅为32767。在那里添加一个不能溢出。
- @davidhammen:32767+1==0x8000,这实际上是16位int的溢出(rand_max这个值的原因很可能是一些16位的遗留/函数调用)。请注意,int可以非常好地16位宽(适用于许多系统)。
- 不太可能,但是如果你的应用程序每秒生成100万个随机数,假设32位系统上的rand_max==max_int,它每天会发生大约20次。
- 如果rand_max==max_int,rand()+1将以与rand()的值为0完全相同的概率溢出,这使得该解决方案完全没有意义。如果您愿意冒这个风险,忽略溢出的可能性,那么您也可以按原样使用rand(),忽略返回0的可能性。
- @是的,这是一个很好的解决方案。
- @Gerrit-"这可能会溢出(尽管不太可能)。"你不是说"最终会溢出。"
- @如果实验重复的次数足够多,任何p>0$的事件最终都会发生。这两种说法是相同的。
基本上,rand()产生0和RAND_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,因此不会产生负和。
- OP希望从结果中排除0。这个加法也不能提供随机值的均匀分布。
- @奥拉夫:不能保证连续两次调用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.
一个简单的解决方案(好吧,称之为"黑客"),它不会产生零结果,也不会溢出:
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 |
这将限制您的最大值,但如果您不关心这一点,那么这应该对您很好。
- 旁注:注意有符号变量的右移。它只为非负值定义良好,对于负值,它是实现定义的。(幸运的是,rand()总是返回非负值)。但是,我将把优化留在这里的编译器。
- @奥拉夫:一般来说,由两人签署的除法比轮班效率低。除非编译器编写者已投入精力告诉编译器rand将是非负的,否则移位将比用有符号整数2除法更有效。由2u划分可以工作,但如果x是int可能导致有关从无符号到有符号的隐式转换的警告。
- @supercat:请再次详细阅读我的评论。您应该非常清楚,任何合理的编译器都会使用/ 2的移位(我甚至在-O0之类的东西上也看到过这种情况,即没有明确要求的优化)。这可能是C代码最琐碎和最成熟的优化。点是除法是由标准定义的,适用于整个整数范围,而不仅仅是非负值。再次:将优化留给编译器,首先编写正确和清晰的代码。这对初学者更为重要。
- @olaf:我测试过的每一个编译器在将rand()右移一或除以2u时生成的代码比除以2时生成的代码更有效,即使使用-O3时也是如此。有人可能会合理地说,这种优化并不重要,但说"将这种优化留给编译器"意味着编译器可能会执行它们。你知道有什么编译器能做到吗?
- @Supercat:那么您应该使用更多的现代编译器。上次我检查生成的汇编程序时,gcc刚刚生成了很好的代码。尽管如此,尽管我很想有一个腹股沟,但我还是不想被最后一次对你的延长感到苦恼。这些帖子已经有多年的历史了,我的评论完全有效。谢谢您。
- @奥拉夫:我刚刚检查了GCC 8.1和CLANG 6.0的Godbolt。考虑到int rdiv2a(void) { return rand()/2; } int rdiv2b(void) { return rand()>>1; },它们分别为rdiv2a生成8或9条指令,而为rdiv2b生成5条指令。使用/2并不是"将优化留给编译器",而是决定不使用它们。在大多数情况下,这样的优化并不重要,但应该知道这就是我们正在做的。
- @supercat:是的,这似乎是为了正确处理否定的情况。但它并不像你所说的那样使用除法。另外两条指令最多每一个时钟周期(它们可能在非合成程序中折叠,但即使不是,这里也完全不相关),顺便说一句。如果您想没有区别,请使用unsigned,这对rand()是很好的,而且这里的"优化"效果更好。哦,和X64(我使用的)不同的架构可以很好地为两者生成相同的代码。顺便说一句:这就是我在这里要讨论的全部内容。你在提议不好的做法。
要避免0,请尝试以下操作:
1
| int rnumb = rand()%(INT_MAX -1)+1; |
您需要包括limits.h。
- 这将使得到1的概率加倍。如果rand()生成0,则它基本上与有条件地添加1相同(但可能较慢)。
- 是的,你说得对。如果rand()=0或int_max-1,则rnumb将为1。
- 更糟糕的是,当我开始思考它的时候。实际上,它将使1和2的支持率增加一倍(均假定为RAND_MAX == INT_MAX)。我确实忘记了江户记(11)。
- 这里的-1没有价值。rand()%INT_MAX+1;仍将只生成[1…int_max]范围内的值。
而其他人所说的可能溢出很可能是导致负的原因,即使使用无符号整数也是如此。真正的问题是使用时间/日期功能作为种子。如果你真的熟悉了这个功能,你就会知道我为什么这么说了。因为它真正做的是给出一个距离(经过的时间)从一个给定的日期/时间。虽然使用日期/时间功能作为rand()的种子是非常常见的做法,但它确实不是最佳选择。你应该寻找更好的选择,因为关于这个话题有很多理论,我不可能全部深入。你在这个等式中加上溢出的可能性,这种方法从一开始就注定要失败。
那些发布rand()+1的用户使用的是大多数人使用的解决方案,以确保他们不会得到负数。但是,这种方法也不是最好的方法。
你能做的最好的事情是花额外的时间来编写和使用适当的异常处理,并且只在结果为零时添加到rand()数字。并且,正确处理负数。rand()功能并不完美,因此需要与异常处理结合使用,以确保最终得到所需的结果。
花费额外的时间和精力来调查、研究和正确地实现rand()功能是非常值得花费时间和精力的。就我的两分钱。祝你好运…
- rand()没有指定要使用的种子。标准没有规定它使用伪随机生成器,而不是与任何时间的关系。它也不说明发电机的质量。实际问题很明显是溢出。注意,rand()+1用于避免0;rand()不返回负值。对不起,但你确实错过了要点。这与PRNG的质量无关。…
- …在gnu/linux its下的良好实践是从/dev/random播种,然后使用良好的prng(不确定glibc的rand()的质量),或者继续使用设备-如果没有足够的熵可用,则冒着应用程序阻塞的风险。尝试在应用程序中获取熵很可能是一个漏洞,因为这可能更容易受到攻击。现在它变硬了-不在这里