Rand()函数能生成[0,32767]间的随机整数,相当于15个随机的二进制位。
若需生成更大的整数,最佳方案应该是进行两次rand(),将两次rand()的值按二进制位拼接起来。
big_randnum=(rand()<<15)+rand();
our_number=big_random%1000000;
此处<<为二进制左移运算,末位补0。第一次rand得到前15个二进制位,第二次rand得到后15个二进制位,用加法拼接得到30个二进制位,即范围为[0,2^30-1]=[0,1073741823][0,2^{30}-1]=[0,1073741823][0,2^30-1]=[0,1073741823]的大随机数。
其数学本质为:
大随机数=第一次rand()值×32768+第二次rand()值大随机数=第一次rand()值\times32768+第二次rand()值大随机数=第一次rand()值×32768+第二次rand()值
rand()∈[0,32767]rand()\in[0,32767]rand()∈[0,32767]
∴大随机数∈[0,1073741823]且分布均匀\therefore大随机数\in[0,1073741823]且分布均匀∴大随机数∈[0,1073741823]且分布均匀
然后把取得的大随机数对实际需要的范围取膜、调整即可。
可以发现,比如我们实际运用中需要的范围是[0,9999][0,9999][0,9999],而我们用rand()%10000来生成,就会导致概率不均:
rand()∈[0,32767]rand()\in[0,32767]rand()∈[0,32767]
[0,32767]=[0,9999]∪[10000,19999]∪[20000,29999]∪[30000,32767][0,32767]=[0,9999]\cup[10000,19999]\cup[20000,29999]\cup[30000,32767][0,32767]=[0,9999]∪[10000,19999]∪[20000,29999]∪[30000,32767]
∴P([0,2767])>P([2768,9999])\thereforeP([0,2767])\gtP([2768,9999])∴P([0,2767])>P([2768,9999])
令我们想要的范围为[0,N][0,N][0,N],当N远小于RAND_MAX的时候,比如[0,233]时,这种不均可以忽略。但比如范围达到[0,10000]时,概率就很不均匀了,使用本文上述的生成大随机数的方法,用更大的随机数对想要取的范围取膜,会使分布更加均匀,提高随机数的质量。
当然另一个思路是用判断语句强行舍弃赘余的部分,若发现取得的值在赘余部分就舍弃,能保证概率绝对均匀,其实对程序速度影响也不大(常数级别)但会令强迫症极度不爽。我们拼接成大数能减少赘余部分占比,因为也能优化这种舍弃赘余的思路。
很明显,这种方法还可以无限拼接扩展:
superbig_randnum=(rand()<<30)+(rand()<<15)+rand();
veryverysuperbig_randnum=(rand()<<45)+(rand()<<30)+(rand()<<15)+rand();
//依次类推
superbigrandnum∈[0,2^45-1]=[0,35184372088831]
veryverysuperbigrandnum∈[0,2^60-1]=[0,1152921504606846975]veryverysuperbigrandnum\in[0,2^{60}-1]=[0,1152921504606846975]veryverysuperbigrandnum∈[0,2^60-1]=[0,1152921504606846975]
亦可与高精度运算技巧结合使用。
一些其它方法的问题:
(int) ((double)rand() / RAND_MAX) * 1000000;
这样能保证分布大体均匀,但[0,1000000]之间很多数会是取不到的。
RAND_MAX为C++内置常量,值为32767,表示rand()函数能取到的最大值。1000000乘1/32767约等于31,明显27、28、29这些数字是永远随机不出来的,而且能随机出来的数字都位于1000000乘32767分之N的位置,绝对不符合随机数的要求。
而且也违背了祖师爷香农的信息守恒。数字本来就是以二进制方式储存的,何苦非得用十进制运算一位一位来拼凑。
这里给出代码:
#include
using namespace std;
#include
#include
#include
// Rand()函数能生成[0,32767]间的随机整数,相当于15个随机的二进制位。
// 若需生成更大的整数,最佳方案应该是进行两次 rand(),将两次rand()的值按二进制位拼接起来。
long f_rand_big(long lngMin, long lngMax)
{
long lngBigRand = (rand() << 15) + rand();
// 此处<<为二进制左移运算,末位补0。第一次rand得到前15个二进制位,第二次rand得到后15个二进制
// 由于每次rand都有可能平均分布,所以两个rand连接起来也会是整体平均分布
lngBigRand = lngBigRand % lngMax + lngMin;
return lngBigRand;
}
int main()
{
// 如果lngMin=0(默认),lngMax=100,就是[0,99]的意思,如果要[1,100]就 rand() % 100 + 1;
long lngTime = (long)time(NULL);
cout << "lngTime=" << lngTime << endl;
string strTime = to_string(lngTime); // 先转成字符串
reverse(strTime.begin(), strTime.end());
// 字符串反转下,免得被黑客猜出来(如果是彩票中心计算机,万一时间的毫秒被人一致上,
// 而代码被黑客拿到,就能算出一样的数据,因为随机函数公式得到的结果都一样,每次中大奖。
// 当然还能做更多处理,比如各种令牌数字的添加换位啥的,这里就不写了。
cout << "strReverse=" << strTime << endl;
long long lnglngSeed = atoll(strTime.c_str());
// 随机种子很重要。由于有10位,long不够用,这里采用long long格式才不会不同。
cout << "lnglngSeed=" << (lnglngSeed) << endl << endl;
srand(lnglngSeed); // 毕竟计算机速度快,每次时间都一样。所以数据也一样,只用一次就不会重复。
for (int i = 0; i < 1000; i++)
{
cout << f_rand_big(1, 100) << " "; //[1,100]的意思
}
cout << endl;
system("pause");
return 0;
}
得出结果:
lngTime=1673282134
strReverse=4312823761
lnglngSeed=4312823761
45 76 35 12 35 44 25 38 55 ...
PS:
C/C++中 int、long、long long等取值范围
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:18446744073709551615
真正的随机数我想可以在以下地方找到:
电压瞬间值
城市车辆/人瞬间值
天气各地温度+湿度+气温+阴晴值
河水高度精确值