msvcrtにおける乱数生成器の性質

「わたし、気になります」
前日のtest_nodeの実行例から整列前のリストの状態を抜粋した。

...snip
$ ./test_node
711 938 807 302 893
...snip
$ ./test_node
732
...snip
$ ./test_node
738 669
...snip
$ ./test_node
744 958 630
...snip
$ ./test_node
748 714 454 871 129 326 581 849 178 632
...snip

3桁の乱数列がリストに格納されているのだが、何やら乱数らしからぬ秩序が……
リストの最初の数に注目すると、

711
732
738
744
748

実行順に数が昇順に並んでいるのだ。
5回の実行は同時期(ただし少なくとも1秒以上の間隔はある)に行ったものである。
つまり、srand(time(NULL))によって異なる乱数の種で初期化しているにも関わらず、
rand()が最初に返す乱数が似通っているという結果となっている。

この実行例のtest_nodeはWindows XP上で実行しており、
srandおよびrandはWindowsが提供しているmsvcrt.dll内のものを呼び出している。
この結果から推測するに、msvcrtのsrandは近い値の種を与えると、
最初のrandの呼び出しで得られる値も近いものになるという性質があると思われる。

簡単な試験コード。
乱数の種と乱数生成器初期化直後に最初に得られる乱数との関係を、
種を1234567890から1ずつ増加させてみてみる。

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

int main(void)
{
    unsigned int i, b = 1234567890;
    printf("RAND_MAX=%d\n", RAND_MAX);
    for (i = 0; i < 100; i++) {
        srand(i + b);
        printf(" %d", rand());
    }
    putchar('\n');
    return 0;
}

Windows XPのmsvcrtを利用。

RAND_MAX=32767
 1178 1181 1184 1188 1191 1194 1197 1201 1204 1207 1211 1214 1217 1220 1224 1227
 1230 1233 1237 1240 1243 1246 1250 1253 1256 1260 1263 1266 1269 1273 1276 1279
 1282 1286 1289 1292 1295 1299 1302 1305 1308 1312 1315 1318 1322 1325 1328 1331
 1335 1338 1341 1344 1348 1351 1354 1357 1361 1364 1367 1371 1374 1377 1380 1384
 1387 1390 1393 1397 1400 1403 1406 1410 1413 1416 1420 1423 1426 1429 1433 1436
 1439 1442 1446 1449 1452 1455 1459 1462 1465 1469 1472 1475 1478 1482 1485 1488
 1491 1495 1498 1501

Ubuntu 12.04のglibcを利用。

RAND_MAX=2147483647
 1727406014 354831104 41989193 1881700851 514530309 200473502 2038819621 662773045 1432358259 1129115410
 827650238 1601021038 226367566 993648737 685749216 1457546943 1150558409 853901737 531881516 239036254
 2087123140 1784671780 396655320 1173768755 1928590765 566575877 253906795 2100885599 725220940 425348014
 111584912 1953050482 1656439393 1347960022 2126575394 1819229723 1513494568 1200313535 908989684 593097985
 1364666229 1060332570 1833007066 1531568713 1226297796 1998897771 618882458 1383083570 10272653 782720857
 1551196899 170420038 939319062 641914187 333366397 23932459 790074137 500016143 1271289201 961451802
 1725763322 355869741 1125735161 811867385 505653977 1287586715 2053318542 1741464643 365047910 1134739217
 825907241 1602257828 1297257920 1003515630 1767381713 1469875549 88937642 1926903922 555843080 240253500
 1021991228 718598327 1487467697 96543808 1948223224 1643717317 1335803225 1039025019 1803106714 1496987793
 123468823 1964895202 1649970218 284396405 1047966813 1828300071 1517261343 144190043 913412077 609209868

また、Ubuntuで前回のコードをDEBUGもN_DATAも指定無しでコンパイルしたものを、
Windowsで行ったのと同じ程度の適当な時間間隔で実行すると、

$ ./test_node
477 384 899 493 163 921 841 413 889
163 384 413 477 493 841 889 899 921
$ ./test_node
993 146 363 455 431 163 913 755 360
146 163 360 363 431 455 755 913 993
$ ./test_node
610 812 281 418 703 760 537 652 735
281 418 537 610 652 703 735 760 812
$ ./test_node
353 653 822 542 131 704 733 279 830
131 279 353 542 653 704 733 822 830
$ ./test_node
999 946 918 669 463 202 935 811 932
202 463 669 811 918 932 935 946 999
$ ./test_node
364 108 934 763 620 301 313 792 962
108 301 313 364 620 763 792 934 962

glibcでは乱数の種が1異なるだけで簡単には予測できない異なる値が得られるという期待通りの結果だったが、
msvcrtのsrand-rand系は明らかに局所的には乱数の種と最初の乱数の間に強い正の相関というより線形性がある。

元々randで得られる乱数の性質は悪くないとは言い難いものだったので、
ごく簡易的なコードを手っ取り早くコーディングする用途以外ではあまり使用したことがなかったが、
msvcrtのsrandやrandにまさかこんな性質があろうとは思わなかった。
もちろんこの性質はmsvcrtの乱数生成器が使い物にならないということを示しているのでは当然ない。
srandで一回初期化した後、複数回のrandで得られる数列自体はrandに対して期待している程度の乱数列になっている。