C++经典代码之:产生“大随机数”rand()+srand() Rand()函数能生成[0,32767]间的随机整数,对大随机分布不优秀
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 <iostream>
using namespace std;

#include <STRING>
#include <algorithm>
#include <time.h>

// 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
真正的随机数我想可以在以下地方找到:
电压瞬间值
城市车辆/人瞬间值
天气各地温度+湿度+气温+阴晴值
河水高度精确值
创作不易,支持我们就点击下方广告用优惠券买东西吧 :)

百事知道 版权所有,禁止转载,除非给出本网网址。