首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言隨機數生成完全指南:rand()、srand() 與時間種子最佳實踐

     編輯:關於C語言

前言:偽隨機數的本質

rand() 產生的是偽隨機數列 — 由確定性演算法根據初始種子生成。相同種子產生相同序列。這在除錯時是優點 (可重現),在正式環境是缺點 (可預測)。本文涵蓋標準用法、常見錯誤、進階技巧與現代替代方案。

基礎用法

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

int main(void) {
    int m, n;
    time_t t;
    
    // 1. 初始化種子 (僅呼叫一次!)
    srand((unsigned)time(&t));
    
    // 2. 產生隨機數
    m = rand();           // 0 ~ RAND_MAX (至少 32767)
    n = rand() % 10;      // 0 ~ 9
    
    printf("m = %d\nn = %d\n", m, n);
    return 0;
}

關鍵函數深度解析

time() — 取得當前時間

time_t time(time_t *timer);
  • 回傳從 Unix Epoch (1970-01-01 00:00:00 UTC) 至今的秒數
  • 傳入 NULL 可忽略輸出參數:time(NULL)
  • 精度僅到秒,同一秒內多次執行種子相同

srand() — 設定種子

void srand(unsigned int seed);
  • 全程式只呼叫一次,通常在 main() 開頭
  • 常見種子來源:
    • time(NULL) — 最常用
    • getpid() — 進程 ID,同一程式多次執行不同
    • time(NULL) ^ getpid() — 結合時間與 PID,熵值較高
    • rdtsc() / clock_gettime() — 高精度時間 (平台相關)

rand() — 取得偽隨機數

  • 回傳 0 ~ RAND_MAX (標準保證 ≥ 32767,實際常為 2147483647)
  • 線性同餘法 (LCG) 典型實作:next = (next * 1103515245 + 12345) & 0x7fffffff
  • 非執行緒安全,多執行緒需用 rand_r() 或 C++11 <random>

常見範圍轉換技巧

需求寫法備註
[0, N-1]rand() % N簡單但有模數偏差
[M, N]M + rand() % (N - M + 1)包含邊界
[0.0, 1.0)(double)rand() / RAND_MAX浮點數
無偏差均勻分布見下文拒絕採樣法

模數偏差問題與解決

rand() % N 當 RAND_MAX+1 不能整除 N 時,低數值機率略高。

// 無偏差版本:拒絕採樣
int rand_unbiased(int n) {
    int limit = RAND_MAX - (RAND_MAX % n);
    int r;
    do {
        r = rand();
    } while (r >= limit);
    return r % n;
}

進階:執行緒安全版本 rand_r()

int rand_r(unsigned int *seedptr);
  • 種子由呼叫者管理,無全域狀態
  • 適合多執行緒環境,每執行緒維護自己的種子

現代 C++ 替代方案:<random> (C++11)

#include <random>
#include <iostream>

int main() {
    // 1. 隨機裝置 (硬體熵源,非確定性)
    std::random_device rd;
    
    // 2. 梅森旋轉演算法引擎 (高品質、週期 2^19937-1)
    std::mt19937 gen(rd());
    
    // 3. 分布適配器
    std::uniform_int_distribution<> dis(1, 100);  // [1, 100] 均勻分布
    std::normal_distribution<> ndis(0.0, 1.0);    // 常態分布
    
    // 4. 使用
    for (int i = 0; i < 5; ++i) {
        std::cout << dis(gen) << ' ';
    }
    // 輸出範例:42 87 13 95 6
}

為什麼棄用 rand()?

  • 品質差:週期短、低位元隨機性差、已知演算法可預測
  • 非執行緒安全
  • 分布控制困難
  • 密碼學應用嚴禁使用 — 需用 std::random_device 或 OS 提供的 /dev/urandom, CryptGenRandom

最佳實踐總結

  1. C 語言專案:srand(time(NULL) ^ getpid()) + rand(),注意模數偏差
  2. 多執行緒 C 專案:使用 rand_r() 或獨立種子
  3. C++11+ 專案:強制使用 <random>,拒絕 rand()
  4. 密碼學/安全相關:std::random_device 或平台 CSPRNG
  5. 可重現除錯:固定種子 srand(12345) 或 std::mt19937 gen(12345)

結語

rand()/srand() 是 C 語言的歷史遺產,理解其「偽隨機、全域狀態、品質有限」本質至關重要。現代 C++ 專案請毫不猶豫拥抱 <random>,獲得統計品質優異、執行緒安全、分布靈活的隨機數生成能力。記住:**永遠不要用 rand() 做密碼學用途**。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved