プロが教えるわが家の防犯対策術!

perlスクリプトで疑似乱数ではなくCPUの演算回数を種にしてそこからランダム値の0か1を発生させるプログラムを作りました。
しかし、結果は片方に偏っています。どのようにすれば修正できますか?
動作環境はWindows 7+ActivePerlです。


$| = 1;

for(;;){
my ($c,$a);
for(;;){
$a++;
if( (time + length("qMiWcHyV") + length("aWyvVbeK") ) % 2) {

$c++;
}
my ($sec) = localtime(time);if($sec == 0 || $sec == 30 || $sec % 10 == 0){last;}
}
my ( $sec, $min, $hour, $mday, $mon, $year, $wno ) = localtime(time);
my $nowtime = sprintf( "%02d_%02d_%02d__%02d_%02d_%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec );

open( F , ">>Result.txt" );
print F "$nowtime\tResult\t" , $c/$a , "\n";
print "$nowtime\tResult\t" , $c/$a , "\n";
close(F);
sleep 1;
}

=======================
結果はとなり偏っています。
2018_10_30__23_33_50 Result 0.550934067688598
2018_10_30__23_34_00 Result 0.545030700601732
2018_10_30__23_34_10 Result 0.556725318480547
2018_10_30__23_34_20 Result 0.549527231125661
2018_10_30__23_34_30 Result 0.55964066863353

A 回答 (6件)

むしろ、値が揃い過ぎていて乱数としての使えないように思います。


1の後は0になりやすい
1が続いた後は0の割合が高くなる
といった傾向がありそうです。
    • good
    • 0

ANo.3です。


本当のところは何が欲しいのかです。
真の乱数と暗号論的乱数の違いは、実際の乱数列をみても簡単に識別できるものではありません。なので真の乱数に拘るなら、その作り方から真の乱数になるとロジカルに証明することが必要です。
そうでなく単にそれっぽい乱数を作るだけなら、単純にカウンタを暗号論的ハッシュ関数に通せば十分なものが作れるでしょう。

それで物理乱数生成器ですけど、ANo.3のリンク先はIvy Bridgeに物理乱数生成器を搭載したという記事です。何で最近のIntel CPUなら真の乱数に近い乱数をCPU命令で生成できるという理解で良いですね。
perlでインラインアセンブラしたり、アセンブラ関数をライブラリとしてリンクすることができるか知りませんけど、そういう方法で乱数生成命令を呼び出せば解決できます。
あとLinuxの/dev/randomですけど、Linuxを使えといっているのではなくて、そのソースを参考にすれば近い性能のものを作れるでしょうということです。
# カーネルで実装されているものなのでアプリでは再現できない可能性はありますが
    • good
    • 0

>また、もしよろしければ、真にランダム(0と1の2値)な数字を簡単に発生させる方法はありますでしょうか?


たぶん、ないと思います。
あなたの考え方の延長で、現在時刻をマイクロ秒まで取得し、その最後の桁(1の位)、(又は10の位)が奇数なら、カウントする
という考え方もありかと。
以下は、マイクロ秒の10の位が奇数の場合にカウントする方法です。
マクロ秒の取得は下記を参照。
http://d.hatena.ne.jp/hitotec/20121203/1354548250
マクロ秒は6桁の数値です。こちらで確認した限りでは、
1の位で判定よりは、10の位で判定したほうが若干50%の確率に近いです。

use Time::HiRes qw/ gettimeofday /;
$| = 1;
for(;;){
my ($c,$a);
$a = 0;
$c = 0;
for(;;){
$a++;
my ($epocsec, $microsec) = gettimeofday();
#if($microsec%2){ #1の位が奇数の場合
if(int($microsec/10)%2){ #10の位が奇数の場合
$c++;
}

my ($sec) = localtime();
if($sec == 0 || $sec == 30 || $sec % 10 == 0){last;}
}
my ( $sec, $min, $hour, $mday, $mon, $year, $wno ) = localtime();
my $nowtime = sprintf( "%02d_%02d_%02d__%02d_%02d_%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec );

open( F , ">>Result.txt" );
print F "$nowtime\tResult\t" , $c/$a , "\n";
print "$nowtime\tResult\t" , $c/$a , "\n";
print "$c $a \n";
close(F);
sleep 1;
}
    • good
    • 0

疑似乱数ではなく真の乱数を作りたい。

処理時間のゆらぎを利用して真の乱数を作ろうとしている。
ということで良いですか?
マジメにやるなら色々と複雑なことをやったから大丈夫という話ではないので、素直に物理乱数生成器に頼った方が安心と思いますよ。あるいはLinuxの/dev/randomとか。
# https://news.mynavi.jp/article/20110921-ivy_brid …
    • good
    • 0
この回答へのお礼

回答いただきありがとうございます。
その通りです。処理時間についてはかなりの揺らぎがあるような気がしますのでこれを利用できない物かと思っています。
このコードでもsleep の部分を2に変更するとかなり精度の良い物が得られているような気もしています。
また、物理乱数生成器は憧れますが店頭で見たことが無く恐らくとても高価な物かと思います。
また、Windows+Perlの組み合わせで連携させるのが難しそうなため断念しています。
LinuxOSを使う方法については廉価に本物の乱数が手に入りそうですが、このプログラムのためだけに
こちらを用意するのは大変ですので出来ればWindowsで実装できればと思っています。
他にもネットを利用してその処理時間を種に使う方法も考えたのですが大量のアクセスをするわけにもいきませんしアイデア止まりになっています。

お礼日時:2018/10/31 18:48

No1です。


>なぜ、このようなことが起きるのでしょうか?
>もしよろしければ教えて下さい。

あなたの提示した
if( (time + length("qMiWcHyV") + length("aWyvVbeK") ) % 2) {
$c++;
}
の箇所ですが、
まず、length("qMiWcHyV")は8であり、length("aWyvVbeK")も8です。
従って、
if( (time + 8 + 8 ) % 2) {
$c++;
}
と同じであり、timeの値が奇数か否かを判定する場合に、特に8+8を追加する必要はありません。
(なぜ、このようにしたのか不明ですが、それは置いといて・・・)

奇数秒の場合に$cに1加算する処理を繰り返し、
秒数が10の倍数(0,10,20,30,40,50)になれば終了しています。
次回は1秒スリープに行いますので、1,11,21,31,41,5秒が開始秒です。
1秒が開始秒の場合を例にとると、
1秒台のループ・・・1回目
2秒台のループ
3秒台のループ・・・2回目
4秒台のループ
5秒台のループ・・・3回目
6秒台のループ
7秒台のループ・・・4回目
8秒台のループ
9秒台のループ・・・5回目
まで、ループが行われます。
ここで、奇数秒になるのは、
1,3,4,7,9秒台のループです。これは、5回発生します。
これは2,4,6,8秒台のループが4回に対して、5回なので、奇数秒と判断される確率が5/9になります。
その為、あなたが提示された現象が発生します。

一方、スリープを2秒にすると、
開始時刻は2秒からになるので
2秒台のループ
3秒台のループ・・・1回目
4秒台のループ
5秒台のループ・・・2回目
6秒台のループ
7秒台のループ・・・3回目
8秒台のループ
9秒台のループ・・・4回目
となり、奇数秒と偶数秒は同じ確立になります。
    • good
    • 0
この回答へのお礼

回答いただきありがとうございます。

ずっと考えていたのですが全く気づきませんでした!説明頂きありがとうございます!!
if( (time + length("qMiWcHyV") + length("aWyvVbeK") ) % 2)
に付きまして、実際のコードはもう少し複雑にしています。ここで様々な処理をさせればCPUの演算能力を無駄に消費します。CPUで他の作業をやっていたり場合によっては電圧や熱の変化など外部からの全く予期できない物が種になり、真にランダムな値を生成できるのではないかと思い行っています。
また、もしよろしければ、真にランダム(0と1の2値)な数字を簡単に発生させる方法はありますでしょうか?
上記のコードで簡単にできると思っていたのですが、深く考えると想像以上に難しい気がしてまいりました。

なお、my ($sec) = localtime(time);if($sec == 0 || $sec == 30 || $sec % 10 == 0){last;}
の部分で$sec % 10 == 0が混じっているのは当初0と30で判定していたのですが偏りがあることに気付きそれを調査するためにとりあえず付け加えたコードになります。

お礼日時:2018/10/31 16:05

sleep 1;を


sleep 2;
に変えてみてはいかがでしょうか。結果が良くなると思います。
    • good
    • 0
この回答へのお礼

回答いただきありがとうございます。
14時間回していた現在でも
2018_10_31__14_52_10 Result 0.553409240805363
2018_10_31__14_52_20 Result 0.558287022728528
2018_10_31__14_52_30 Result 0.548569603435258
2018_10_31__14_52_40 Result 0.558731758661291
2018_10_31__14_52_50 Result 0.560720398556925
2018_10_31__14_53_00 Result 0.566833446961911
2018_10_31__14_53_10 Result 0.564483207403289
2018_10_31__14_53_20 Result 0.565308799725402
と悪いですが、

sleep 2;にすると途端に結果が改善しました。
2018_10_31__14_53_20 Result 0.499576150031129
2018_10_31__14_53_30 Result 0.484293791668217
2018_10_31__14_53_40 Result 0.495102641228422
2018_10_31__14_53_50 Result 0.503479995321015
2018_10_31__14_54_00 Result 0.503593531454864
2018_10_31__14_54_10 Result 0.497375609192218

なぜ、このようなことが起きるのでしょうか?
もしよろしければ教えて下さい。

お礼日時:2018/10/31 14:55

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!