在開發程式的時候常常會需要產生亂數,然而,電腦並不存在「真正隨機」的亂數,它只能夠透過一些機制,利用額外的參考數值(如時間等)來模擬出看起來是隨機的亂數。



如果想要驗證電腦的亂數並不是「真正隨機」的話,可以執行幾次以下的C語言程式:

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

int main() { 
    int rnd1 = rand();
    int rnd2 = rand();

    printf("%d\n", rnd1);
    printf("%d\n", rnd2);
    return 0;
}

以上程式呼叫了兩次標準函式庫提供的rand函數來產生出兩個隨機整數。執行結果如下:

random-number

從執行結果我們可以看到,兩次呼叫rand函數所得到的值的確是不一樣的,乍看之下是亂數沒錯。但是當我們多次執行這個程式時,會發現每次執行程式所產生出來的兩個隨機整數都是同樣的那兩個整數,似乎不怎麼隨機。

為了要讓rand函數看起來更「接近隨機」,我們通常會將時間設為亂數產生的「亂數種子」,亂數種子即為亂數產生的參考值。程式如下:

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

int main() {
    srand(time(NULL));
    
    int rnd1 = rand();
    int rnd2 = rand();

    printf("%d\n", rnd1);
    printf("%d\n", rnd2);
    return 0;
}

執行結果如下:

random-number

由於每次執行程式時,程式執行到第3行的時間點都不一樣,等於每次執行程式時都使用不同的亂數種子,所以rand函數回傳的結果就更「接近隨機」了!

那麼,Rust程式語言要怎麼樣產生出隨機亂數呢?在先前介紹過的猜數字程式中,我們使用了rand這個套件來產生亂數。但由於前面提到過,「隨機的」亂數其實需要透過一些機制來模擬出來,而rand這個套件算是比較低階用法的套件,可以套用不同的機制來產生亂數,因此使用起來不是那麼的方便。例如,要產生範圍在1到100中(包含1和100)的隨機整數,程式需寫成以下這樣:

extern crate rand;

use rand::Rng;
 
fn main() {
    let rnd = rand::thread_rng().gen_range(1, 101);

    println!("{}", rnd);
}

以上程式看起來還算簡單,傳入至gen_range函數中的數值型別,即為回傳數值的型別。當然也可以利用Rust的型別推論機制,讓回傳的值在被使用或是被明確定義的時候才去決定好型別。但如果我們想要產生出範圍在5255之間(包含5255)的隨機u8型別整數,該怎麼做呢?嘗試撰寫以下程式:

extern crate rand;

use rand::Rng;

fn main() {
    let rnd: u8 = rand::thread_rng().gen_range(5, 256);

    println!("{}", rnd);
}

編譯以上程式,會發現程式無法通過編譯!這是因為我們為了要讓gen_range函數能夠回傳5255的整數,而將5256帶進它的參數,但參數的型別和回傳值的型別必須要是相同的,256無法被編譯器轉成u8,所以造成編譯錯誤。

當然,這個問題也不是不能解決,只是要把程式寫法改成以下這樣:

extern crate rand;

use rand::distributions::{Distribution, Uniform};

fn main() {
    let uniform = Uniform::new_inclusive(5, 255);

    let rnd: u8 = uniform.sample(&mut rand::thread_rng());

    println!("{}", rnd);
}

或者也可以用範圍(range)語法:

extern crate rand;

use rand::distributions::{Distribution, Uniform};

fn main() {
    let uniform = Uniform::from(5..=255);

    let rnd: u8 = uniform.sample(&mut rand::thread_rng());

    println!("{}", rnd);
}

不過這樣的寫法跟直接用隨機產生器(RNG, random number generator)提供的gen_range方法實在是差太多了,而且也不是很容易使用。像是如果要產生隨機大於等於5以上的整數,就不能夠省略那個最大值。例如以下程式會編譯失敗:

extern crate rand;

use rand::distributions::{Distribution, Uniform};

fn main() {
    let uniform = Uniform::from(5..);

    let rnd: u8 = uniform.sample(&mut rand::thread_rng());

    println!("{}", rnd);
}

當然,最小值也是不能省的,不過這個問題比較還好,因為當我們想省略最小值時,幾乎都是因為要從0開始。

Random Number

「Random Number」是筆者開發的套件,能夠以便利的寫法來產生隨機數值。

Crates.io

https://crates.io/crates/random-number

Cargo.toml

random-number = "*"

巨集的使用

random

random巨集的基本用法如下:

#[macro_use]
extern crate random_number;

let n: u8 = random!();
println!("{}", n); // 0 ~ 255

let n: f64 = random!();
println!("{}", n); // 0.0 ~ 1.0

let n: u8 = random!(..=10);
println!("{}", n); // 0 ~ 10

let n: u8 = random!(..=9);
println!("{}", n); // 0 ~ 9

let n: u8 = random!(10..);
println!("{}", n); // 10 ~ 255

let n: i8 = random!(-2..=12);
println!("{}", n); // -2 ~ 12

let n: u8 = random!(12, 20);
println!("{}", n); // 12 ~ 20

let n: u8 = random!(20, 12);
println!("{}", n); // 12 ~ 20

如果想要重複使用某個隨機產生器(RNG),可以直接將其添加至random巨集的最後一個參數。

#[macro_use]
extern crate random_number;

let mut rng = random_number::rand::thread_rng();

let n: u8 = random!(rng);
println!("{}", n); // 0 ~ 255

let n: u8 = random!(..=10, rng);
println!("{}", n); // 0 ~ 10

let n: u8 = random!(20, 12, rng);
println!("{}", n); // 12 ~ 20
random_fill

random_fill巨集的語法和random巨集差不多,只不過要在第一個參數多傳一個可變的切片,這個巨集會用隨機數值填滿這個切片。

#[macro_use]
extern crate random_number;

let mut a = [0i8; 32];
random_fill!(a, -2..=12);

println!("{:?}", a);

random_rangedrandom_fill_ranged函數

由於效能上的考量,上面介紹的randomrandom_fill巨集若是使用範圍語法來決定隨機數值範圍的話,範圍語法會在編譯階段就被處理。因此要在這兩個巨集使用範圍語法,就真的只能以定數(literal)的方式來使用。

例如以下程式,我們將範圍用一個變數來儲存,再傳給random_fill巨集使用,就會造成編譯失敗:

#[macro_use]
extern crate random_number;

let var_range = 1..=10;

let n: u8 = random!(var_range); // compile error

為了解決這樣的問題,就要改用random_rangedrandom_fill_ranged函數,在程式執行階段才去處理範圍。如下:

extern crate random_number;

let var_range = 1..=10;

let n: u8 = random_number::random_ranged(var_range);

println!("{:?}", a);