特別な状況下での乱数の種

前に述べた、乱数生成器の初期化関数srandの引数にtime関数の戻り値を与えているプログラムを、
1秒は超えるがさほど長くない時間間隔で繰り返し実行した時にrand関数が最初に返す数値が、
msvcrtやBorlandにより提供されている標準関数を使うとある程度簡単に予測がつくものになるという問題。

乱数の種が近い数値なら最初に得られる乱数が近い数値になることが問題なので、
シンプルな解決は、timeを呼び出した時刻が近くてもtimeの戻り値が大きく離れた数値になるように変換すればいい。

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

int main(void)
{
    srand(time(NULL));
    printf("%d\n", rand());
    return 0;
}

上述のような条件下でmsvcrtのsrand-rand系を何度か呼び出すと、

$ ./get_rnd
15894
$ ./get_rnd
15898
$ ./get_rnd
15907

そこで、以下のようにsrandに渡す種を変更する。

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

static unsigned int uint_bits_count(void)
{
    unsigned int x, c = 0;
    for (x = UINT_MAX; x != 0; x >>= 1) c++;
    return c;
}

int main(void)
{
    unsigned int seed = time(NULL), c = uint_bits_count();
    srand(seed >> (c / 2) | seed << (c - c / 2));
    printf("%d\n", rand());
    return 0;
}

要は、timeの戻り値の上位半分と下位半分を入れ替えただけである。
例えばunsigned int型のサイズが32ビットで決まっているのであれば、

    srand(seed >> 16 | seed << 16);

のように上位16ビットと下位16ビットを入れ替える書き方でもいいと思う。
変更版を実行すると、

$ ./get_rnd
32532
$ ./get_rnd
5890
$ ./get_rnd
25337

BCC32でコンパイルしてみると、

C:\foo> get_rnd
15318
C:\foo> get_rnd
27080
C:\foo> get_rnd
6074