大家在寫 C/C++ 程序時(shí),難免會(huì)遇到要求獲取某個(gè)范圍內(nèi)的隨機(jī)數(shù),我查閱了一些資料后,總結(jié)如下。本文分兩部分,先介紹 C 語(yǔ)言中與隨機(jī)數(shù)相關(guān)的兩個(gè)函數(shù) srand 和 rand,后介紹 C++ 中的 random 庫(kù),每一部分最后會(huì)給出生成特定范圍內(nèi)的隨機(jī)數(shù)模板供參考。
1 C 語(yǔ)言中的 srand 和 rand
1.1 實(shí)現(xiàn)
下面是 VC 的實(shí)現(xiàn),GCC 的實(shí)現(xiàn)比 VC 的復(fù)雜,但基本原理是一樣的。
- #define RAND_MAX 32767 // in <stdlib.h>
-
- unsigned long _Randseed = 1; // global seed
-
- void srand(unsigned int seed) {
- _Randseed = seed;
- }
-
- int rand(void) {
- _Randseed = _Randseed * 1103515245 + 12345;
-
- return ((unsigned int)(_Randseed >> 16) & RAND_MAX);
- }
第一次接觸 C 語(yǔ)言中的隨機(jī)數(shù)時(shí),很疑惑為什么有種子這個(gè)玩意,只提供一個(gè)產(chǎn)生隨機(jī)數(shù)的函數(shù)不就行了嗎,看了上面的源碼后,就明白了,因?yàn)?span style="color:#ff0000">計(jì)算機(jī)不能產(chǎn)生真正的隨機(jī)數(shù),只能靠數(shù)學(xué)的方法產(chǎn)生偽隨機(jī)數(shù)。srand 函數(shù)的作用是設(shè)置種子,如果不設(shè)置的話種子(上面的 _Randseed)則默認(rèn)初始是1,種子是全局變量。rand 的實(shí)現(xiàn)就跟數(shù)論有關(guān)了,上面的實(shí)現(xiàn)用的是線性同余法??梢钥吹剿姆祷刂捣秶?[0, RAND_MAX]。
1.2 time
既然計(jì)算機(jī)不能產(chǎn)生真正的隨機(jī)數(shù),那怎么才能使程序每次運(yùn)行的結(jié)果不同呢?總得有個(gè)隨機(jī)的東西,那就借助 time 這個(gè)函數(shù)產(chǎn)生種子,引入一個(gè)新東西又會(huì)帶來(lái)一些坑,我早年寫過(guò)這種程序:
- // 生成十個(gè)隨機(jī)數(shù)(錯(cuò)誤用法)
- for (int i = 0; i < 10; ++i) {
- srand((unsigned int)time(NULL));
- printf("%d: %d\n", i, rand());
- }
它將產(chǎn)生10個(gè)相同的數(shù)。要解釋這個(gè)問(wèn)題,就得弄懂 time 這個(gè)函數(shù),它的函數(shù)原型如下:
- time_t time(time_t *timer);
它返回“當(dāng)前時(shí)間”,這個(gè)“時(shí)間“的類型是 time_t,在 VC 中被 typedef 為 unsigned long,標(biāo)準(zhǔn)中只規(guī)定它是個(gè)算數(shù)類型,至于它是如何表示時(shí)間的未定義。一般是返回 UNIX 時(shí)間戳,定義為從格林威治時(shí)間1970年01月01日00時(shí)00分00秒起至現(xiàn)在的總秒數(shù)。上面的程序執(zhí)行時(shí)很快,在一秒內(nèi)完成循環(huán),所以它產(chǎn)生了相同的隨機(jī)數(shù)。
1.3 my rand
下面提供兩個(gè)生成隨機(jī)數(shù)的模板。
- int g_is_first = 1;
-
- /*
- ** return a random integer in the interval
- ** [a, b]
- */
- int uniform_int(int a, int b) {
- if (g_is_first) {
- g_is_first = 0;
- srand((unsigned int)time(NULL));
- }
-
- return (int)((double)rand() / ((RAND_MAX + 1.0) / (b - a + 1.0)) + a);
- }
-
- /*
- ** return a random real in the interval
- ** [a, b] (also [a, b))
- */
- double uniform_real(double a, double b) {
- if (g_is_first) {
- g_is_first = 0;
- srand((unsigned int)time(NULL));
- }
-
- return (double)rand() / ((double)RAND_MAX / (b - a)) + a;
- }
為了保證 srand 函數(shù)只執(zhí)行一次,這里用了全局標(biāo)志 g_is_first。其實(shí)最好在頭文件中定義接口,在源文件中實(shí)現(xiàn),這里為了使用方便就全放一起了。當(dāng)要求的隨機(jī)數(shù)范圍過(guò)大時(shí),uniform_int 和 uniform_real 貌似有 bug。
2 C++ 中的 random 庫(kù)
在 random 庫(kù)中有隨機(jī)數(shù)發(fā)生器(random engine/generator)和分布(distribution),它們的具體用法我就不在這說(shuō)了。我個(gè)人認(rèn)為 engine 存儲(chǔ)了種子,將 C 語(yǔ)言中的全局種子封裝起來(lái)了。uniform distribution 中只存儲(chǔ)了最大值和最小值(即平均分布的兩個(gè)參數(shù))。還有個(gè)真正的 engine 叫 std::random_device,它根據(jù)機(jī)器的各種實(shí)時(shí)參數(shù)產(chǎn)生隨機(jī)數(shù),標(biāo)準(zhǔn)規(guī)定它的實(shí)現(xiàn)是可選的,有的編譯器(如 MinGW)目前不支持,產(chǎn)生的還是偽隨機(jī)數(shù),不過(guò) VC 及 Linux 平臺(tái)上的 GCC 是支持的,下面的程序假設(shè)用戶的編譯器支持。
- /*
- ** return a random integer in the interval [a, b]
- */
- int uniform_int(int a, int b) {
- static std::default_random_engine e{std::random_device{}()}; // avoid "Most vexing parse"
- static std::uniform_int_distribution<int> u;
-
- return u(e, decltype(u)::param_type(a, b));
- }
-
- /*
- ** return a random real in the interval [a, b] (also [a, b))
- */
- double uniform_real(double a, double b) {
- static std::default_random_engine e{std::random_device{}()};
- static std::uniform_real_distribution<double> u;
-
- return u(e, decltype(u)::param_type(a, b));
- }
最后,還有個(gè)問(wèn)題沒(méi)搞懂:能否始終用 random_device 代替 default_random_engine ?它們的區(qū)別是什么?
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。