C言語でxorshiftの乱数を使う
1. はじめに
C言語でxorshiftという乱数発生を使うコードを作成しました。
前回のeastasian
と同じように、nptから抜き出したものです。
配布はここ。
https://github.com/nptcl/hypd/tree/main/develop/random
最初にひどいことを言っておきますが、
私はこの分野の専門家ではないので、
このモジュールを使う利点は一切ないと思います。
こういう自分に自信のない書き込みというのはとても良くないとは思うのですが、
数値計算に関係するものならはっきり言っておきます。
何か問題が起きてからではどうしようもありません。
使うなら十分注意してくださいね。
使用するファイルは下記の通り。
- random.c
- random.h
- md5encode.c
- md5encode.h
md5encode.cは、単にMD5を計算するコードです。
xorshiftの初期値を計算するときのためだけに登録しました。
贅沢ですね。
2. コンパイル
コンパイルの方法は少し面倒です。
乱数の初期値を取得するために、OSを指定しなければなりません。
Unix, Linux, FreeBSDの場合は、次のようにしてください。
$ cc -DHYPD_UNIX -c random.c $ cc -DHYPD_LINUX -c random.c $ cc -DHYPD_FREEBSD -c random.c
上記3行は全部同じであり、
乱数の初期値を設定する際に/dev/urandom
を読み込もうとします。
一方、Windows版も用意されています。
HYPD_WINDOWS
をdefine
してください。
# シェルでの実行例 $ cc -DHYPD_WINDOWS -c random.c
こちらは、advapi32.dll
のSystemFunction036
という関数、
通称RtlGenRandom
を使用して乱数の初期値を指定するようになります。
それ以外の場合でもコンパイルはできます。
$ cc -c random.c
ただし初期状態は、現在の時刻であったり、関数のポインターであったり、
極力努力はしますが、全然信頼できない初期値になりますので注意してください。
一応/dev/urandom
を読み込もうとはしますが、
無かったら諦めた上で成功したことにしますので注意。
3. 使い方
使い方を説明します。
uint64_t x; struct random_state state; random_seed_string(&state, "Hello"); x = random_number_64bit(&state);
これで、変数x
には乱数が返却されます。
変数state
には、乱数の内部状態が格納されます。
まずはrandom_seed_string
関数を用いて、
Hello
という文字列で内部状態を初期化します。
ここの文字列は何でもいいです。
そのあと、Hello
に設定された内部状態を用いて、
random_number_64bit
により64bitの乱数を計算して返却します。
返却値を見てみましょう。
int main(void) { uint64_t x; struct random_state state; random_seed_string(&state, "Hello"); x = random_number_64bit(&state); printf("0x%" PRIX64 "\n", x); x = random_number_64bit(&state); printf("0x%" PRIX64 "\n", x); x = random_number_64bit(&state); printf("0x%" PRIX64 "\n", x); return 0; }
出力結果は下記の通り。
0x6D16DA894C444233 0x7C8C640A1AD04940 0xBB5BBF5B4EF005AE
この結果は、どの環境でも同じになると思います。
何度実行しても同じ値になります。
別の乱数列を生成させたい場合は内部状態の初期値を変更します。
random_seed_string(&state, "AAABBB"); x = random_number_64bit(&state);
これで、内部状態AAABBB
に対応する乱数列が生成されるようになります。
内部状態の設定は、文字列以外でも設定できます。
バッファを直接流し込んでみましょう。
random_seed_buffer(&state, "Hello", 5);
第1引数に内部状態、
第2引数にバッファのポインタ、
第3引数にバッファのサイズを指定します。
これは、
random_seed_string(&state, "Hello");
と同じですね。
あとは、OS依存になるのですが、 でたらめな内部状態を設定することもできます。
if (random_seed_randomly(&state)) { fprintf(stderr, "random_seed_randomly error.\n"); exit(1); }
このように設定すると、変数state
にはいつも違った値が設定されます。
つまり、実行するたびに違う乱数値が生成されるようになります。
ただしこれが利用できるのは一部の環境のみです。
詳しくはコンパイルの方法で説明しました。
乱数が生成されたのはいいのですが、
値の範囲が大きすぎて使いづらくて仕方がありません。
例えば、0
から10
までの値を取得したいときはどうしたらいいでしょうか。
間違っても%
で余りを求めたらいけませんよ。
下記のように実行します。
x = random_equal_64bit(&state, 10);
ちなみに、次のようにも書けます。
x = random_less_64bit(&state, 11);
境界の10
と11
が含まれるか含まれないかの違いなので、
どちらを使ってもいいです。
最後に浮動小数の乱数生成について説明します。
float a; double b; long double c; random_seed_string(&state, "Hello"); a = float_random_64bit(&state); b = double_random_64bit(&state); c = long_random_64bit(&state);
変数a
, b
, c
には、
それぞれの型に対応する乱数が代入されます。
値の範囲は、0.0
(含む)から1.0
(含まれない)までです。
つまり0.0~0.9999...
の乱数値です。
何でこんな範囲になったのかはANSI Common Lispの都合だと思います。
4. その他の情報
関数の名前が_64bit
で終わっているものがあるのですが、
_32bit
も用意されています。
違いは色々あるのですが些細なことなので、
全部_64bit
だけでいいと思います。
たぶん返却値の型がuint32_t
とuint64_t
という
違いだけなのでしょう。
変数state
は乱数の内部状態であり、
128bit長の整数が保存されています。
関数random_seed_string
とrandom_seed_buffer
は、
その引数からMD5を経由してstate
の値を決定します。
何も文字列やバッファなんか使わなくても、 直接値を格納する方法もあります。
struct random_state state; state.seed.u64[0] = 100; state.seed.u64[1] = 200;
あるいはこんな感じ。
struct random_state state; state.seed.u32[0] = 300; state.seed.u32[1] = 400; state.seed.u32[2] = 500; state.seed.u32[3] = 600;
逆に言うなら、これらの値を保存しておけば、 いつでもその時の乱数列を再現できるというわけです。