与rand() % n
不理想有关
做的rand() % n
分布不均匀。您将获得不成比例的某些值,因为值的数量不是20的倍数
接下来,rand()
通常是线性同余生成器(还有很多其他生成器,只是这是最有可能实现的一种-参数不理想(有很多选择参数的方式))。最大的问题是,其中的低位(使用% 20
类型表达式获得的位)通常不是那么随机。我记得一个rand()
从年前从哪里交替的最低位1
到0
每个调用rand()
-这不是很随机的。
从rand(3)手册页中:
Linux C库中rand()和srand()的版本使用相同的版本
随机数生成器为random()和srandom(),因此低阶
位应与高阶位一样随机。但是,在较老的
rand()实现,以及当前在不同实现上的实现
系统中,低阶位的随机性远低于高阶位
顺序位。不要在打算用于
需要良好的随机性时可移植。
现在,这可能已经成为历史,但是很有可能仍然有一个糟糕的rand()实现隐藏在堆栈中的某个位置。在这种情况下,它仍然很适用。
要做的事情是实际上使用一个好的随机数库(给出好的随机数),然后在所需范围内索取随机数。
良好的随机数位代码示例(从链接的视频中的13:00开始)
#include <iostream>
#include <random>
int main() {
std::mt19937 mt(1729); // yes, this is a fixed seed
std::uniform_int_distribution<int> dist(0, 99);
for (int i = 0; i < 10000; i++) {
std::cout << dist(mt) << " ";
}
std::cout << std::endl;
}
比较一下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));
for (int i = 0; i < 10000; i++) {
printf("%d ", rand() % 100);
}
printf("\n");
}
运行这两个程序,并比较输出中出现(或不出现)某些数字的频率。
相关视频:rand()被认为有害
rand()的某些历史方面会导致Nethack中的错误,您应该在自己的实现中进行观察和考虑:
Nethack RNG问题
Rand()是Nethack随机数生成的非常基本的功能。Nethack使用它的方式是错误的,或者有人可能认为lrand48()会产生糟糕的伪随机数。(但是,lrand48()是使用已定义的PRNG方法的库函数,任何使用该函数的程序都应考虑该方法的弱点。)
漏洞是Nethack依赖于lrand48()结果的低位(有时仅与rn(2)中的情况一样)。因此,整个游戏中的RNG效果不佳。在用户动作引入进一步的随机性之前(即在角色生成和一级创建中),这一点尤其明显。
尽管上述内容来自2003年,但仍应牢记这一点,因为并非所有运行您预期游戏的系统都是具有良好rand()函数的最新Linux系统。
如果您只是为自己执行此操作,则可以通过编写一些代码并使用ent测试输出来测试随机数生成器的性能。
关于随机数的性质
对于“随机”还有其他的解释,它们并不是完全随机的。在随机数据流中,很可能两次获得相同的数字。如果您掷硬币(随机),很有可能连续获得两个头。或掷两次骰子,并连续两次获得相同的数字。或旋转轮盘并在那里获得两次相同的数字。
数字分布
播放歌曲列表时,人们希望“随机”表示不会连续播放同一首歌曲或歌手。让播放列表连续播放两次《甲壳虫乐队》被视为“不是随机的”(尽管它是随机的)。对于四首歌曲的播放列表总共播放了八次的感觉:
1 3 2 4 1 2 4 3
比以下内容更“随机”:
1 3 3 2 1 4 4 2
有关歌曲“随机播放 ”的更多信息:如何随机播放歌曲?
重复值
如果您不想重复值,则应考虑使用其他方法。生成所有可能的值,然后将它们洗牌。
如果您正在呼叫rand()
(或任何其他随机数生成器),则将其替换。您总是可以两次获得相同的号码。一种选择是一次又一次地扔掉这些值,直到您选择了一个符合要求的值为止。我将指出,这具有不确定的运行时,并且有可能您会陷入无限循环的情况,除非您开始进行更复杂的回溯。
列出并选择
另一种选择是生成所有可能有效状态的列表,然后从该列表中选择一个随机元素。查找房间中所有符合条件的空位,然后从该列表中随机选择一个。然后一次又一次地做,直到完成。
随机播放
另一种方法是重新整理,就像是一副纸牌。首先处理房间中的所有空白点,然后开始处理空白点,一次一次地分配给每个要求空白点的规则/流程,开始分配它们。当卡片用完或事情不再需要它们时,您就完成了。