web-dev-qa-db-ja.com

srandを初期化する推奨方法は?

C++で擬似乱数ジェネレーターを初期化する「良い」方法が必要です。 記事 を見つけました:

乱数のような数を生成するために、srandは通常、実行時間に関連する値のような、何らかの特徴的な値に初期化されます。たとえば、関数timeによって返される値(ヘッダーctimeで宣言)は毎秒異なります。これは、ほとんどのランダム化のニーズに十分に特有です。

Unixtimeは、私のアプリケーションにとって十分な特徴ではありません。これを初期化するより良い方法は何ですか?移植性があればボーナスポイントですが、コードは主にLinuxホストで実行されます。

Pid/unixtimeの計算を行ってintを取得するか、/dev/urandomからデータを読み取ることを考えていました。

ありがとう!

[〜#〜] edit [〜#〜]

はい、実際にアプリケーションを1秒間に複数回起動していますが、衝突が発生しました。

61
Gary Richardson

最良の答えは、Boost乱数を使用することです。または、C++ 11にアクセスできる場合は、 _<random>_ ヘッダーを使用します。

しかし、Rand()srand()について話している場合
最善の方法は、time()を使用することです。

_int main()
{
    srand(time(NULL));

    ...
}
_

Rand()を呼び出すたびにではなく、プログラムの最初にこれを行うようにしてください。

起動するたびに、time()は一意の値を返します(アプリケーションを1秒間に複数回起動しない限り)。 32ビットシステムでは、60年ごとにしか繰り返されません。

あなたは時間は十分にユニークだとは思わないが、信じることは難しいと思う。しかし、私は間違っていることが知られています。

アプリケーションの多くのコピーを同時に開始する場合は、より細かい解像度のタイマーを使用できます。しかし、その後、値が繰り返されるまでの期間が短くなるリスクがあります。

OK、だからあなたが本当に1秒間に複数のアプリケーションを起動していると思うなら。
次に、タイマーの粒度を細かくします。

_ int main()
 {
     struct timeval time; 
     gettimeofday(&time,NULL);

     // microsecond has 1 000 000
     // Assuming you did not need quite that accuracy
     // Also do not assume the system clock has that accuracy.
     srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

     // The trouble here is that the seed will repeat every
     // 24 days or so.

     // If you use 100 (rather than 1000) the seed repeats every 248 days.

     // Do not make the MISTAKE of using just the tv_usec
     // This will mean your seed repeats every second.
 }
_
52
Martin York

これは、頻繁に(1秒間に複数回)実行できる小さなコマンドラインプログラムに使用したものです。

unsigned long seed = mix(clock(), time(NULL), getpid());

ミックスは次のとおりです。

// http://www.concentric.net/~Ttwang/tech/inthash.htm
unsigned long mix(unsigned long a, unsigned long b, unsigned long c)
{
    a=a-b;  a=a-c;  a=a^(c >> 13);
    b=b-c;  b=b-a;  b=b^(a << 8);
    c=c-a;  c=c-b;  c=c^(b >> 13);
    a=a-b;  a=a-c;  a=a^(c >> 12);
    b=b-c;  b=b-a;  b=b^(a << 16);
    c=c-a;  c=c-b;  c=c^(b >> 5);
    a=a-b;  a=a-c;  a=a^(c >> 3);
    b=b-c;  b=b-a;  b=b^(a << 10);
    c=c-a;  c=c-b;  c=c^(b >> 15);
    return c;
}
62
Jonathan Wright

より良い乱数ジェネレータが必要な場合は、libc Randを使用しないでください。代わりに、単に/dev/randomまたは/dev/urandom直接(intから直接読み取るか、そのようなものから)。

Libc Randの唯一の本当の利点は、シードが与えられると、予測が可能であり、デバッグに役立つことです。

15
Evan Teran

C++ 11 random_device

適切な品質が必要な場合は、最初にRand()を使用しないでください。 <random>ライブラリを使用する必要があります。さまざまな品質/サイズ/パフォーマンスのトレードオフ、再入可能性、事前に定義された配布用のさまざまなエンジンなど、多くの優れた機能を提供します。実装に応じて、非決定的なランダムデータ(/ dev/randomなど)に簡単にアクセスできる場合もあります。

#include <random>
#include <iostream>

int main() {
    std::random_device r;
    std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
    std::mt19937 eng(seed);

    std::uniform_int_distribution<> dist{1,100};

    for (int i=0; i<50; ++i)
        std::cout << dist(eng) << '\n';
}

engはランダム性のソースであり、ここではメルセンヌツイスターの組み込み実装で​​す。 random_deviceを使用してシードします。これは、適切な実装では非決定的RNGであり、seed_seqは32ビットを超えるランダムデータを結合します。たとえば、libc ++では、random_deviceはデフォルトで/ dev/urandomにアクセスします(ただし、アクセスする別のファイルを指定できます)。

次に、ランダム性のソースが与えられると、分布を繰り返し呼び出して、1から100までのintの均一な分布を生成するような分布を作成します。次に、分布を繰り返し使用して結果を出力します。

8
bames53

Windowsの場合:

_srand(GetTickCount());
_

ミリ秒単位であるため、time()よりも優れたシードを提供します。

8
shoosh

最善の方法は、別の疑似乱数ジェネレーターを使用することです。メルセンヌツイスター(およびWichmann-Hill)は私の推奨です。

http://en.wikipedia.org/wiki/Mersenne_twister

7
user39307

moixコードでunix_random.cファイルを参照することをお勧めします。 (mozilla/security/freebl /であると推測します...)freeblライブラリにあるはずです。

そこで、システムコール情報(pwd、netstat ....など)を使用して、乱数のノイズを生成します。これは、ほとんどのプラットフォームをサポートするように記述されています(ボーナスポイント:Dを取得できます)。

7
FL4SOF

あなたが自問しなければならない本当の質問は、あなたが必要とするランダム性の質です。

libcランダムは [〜#〜] lcg [〜#〜]

ランダム性の品質は、入力したものが何であれ低くなります。

異なるインスタンスが異なる初期化を確実に行う必要がある場合は、プロセスID(getpid)、スレッドID、タイマーを混在させることができます。結果をxorと混合します。エントロピーは、ほとんどのアプリケーションに十分なはずです。

例:

struct timeb tp;
ftime(&tp);   
srand(static_cast<unsigned int>(getpid()) ^ 
static_cast<unsigned int>(pthread_self()) ^ 
static_cast<unsigned int >(tp.millitm));

ランダム品質を向上させるには、/ dev/urandomを使用します。 boost :: threadおよびboost :: date_timeを使用して、上記のコードを移植可能にすることができます。

6
Edouard A.

c++11 Jonathan Wrightによる上位投票投稿のバージョン:

#include <ctime>
#include <random>
#include <thread>

...

const auto time_seed = static_cast<size_t>(std::time(0));
const auto clock_seed = static_cast<size_t>(std::clock());
const size_t pid_seed =
      std::hash<std::thread::id>()(std::this_thread::get_id());

std::seed_seq seed_value { time_seed, clock_seed, pid_seed };

...
// E.g seeding an engine with the above seed.
std::mt19937 gen;
gen.seed(seed_value);
3
swalog
#include <stdio.h>
#include <sys/time.h>
main()
{
     struct timeval tv;
     gettimeofday(&tv,NULL);
     printf("%d\n",  tv.tv_usec);
     return 0;
}

tv.tv_usecはマイクロ秒単位です。これは許容できる種でなければなりません。

2
user39307

Visual Studioを使用している場合は、さらに別の方法があります。

#include "stdafx.h"
#include <time.h>
#include <windows.h> 

const __int64 DELTA_Epoch_IN_MICROSECS= 11644473600000000;

struct timezone2 
{
  __int32  tz_minuteswest; /* minutes W of Greenwich */
  bool  tz_dsttime;     /* type of dst correction */
};

struct timeval2 {
__int32    tv_sec;         /* seconds */
__int32    tv_usec;        /* microseconds */
};

int gettimeofday(struct timeval2 *tv/*in*/, struct timezone2 *tz/*in*/)
{
  FILETIME ft;
  __int64 tmpres = 0;
  TIME_ZONE_INFORMATION tz_winapi;
  int rez = 0;

  ZeroMemory(&ft, sizeof(ft));
  ZeroMemory(&tz_winapi, sizeof(tz_winapi));

  GetSystemTimeAsFileTime(&ft);

  tmpres = ft.dwHighDateTime;
  tmpres <<= 32;
  tmpres |= ft.dwLowDateTime;

  /*converting file time to unix Epoch*/
  tmpres /= 10;  /*convert into microseconds*/
  tmpres -= DELTA_Epoch_IN_MICROSECS; 
  tv->tv_sec = (__int32)(tmpres * 0.000001);
  tv->tv_usec = (tmpres % 1000000);


  //_tzset(),don't work properly, so we use GetTimeZoneInformation
  rez = GetTimeZoneInformation(&tz_winapi);
  tz->tz_dsttime = (rez == 2) ? true : false;
  tz->tz_minuteswest = tz_winapi.Bias + ((rez == 2) ? tz_winapi.DaylightBias : 0);

  return 0;
}


int main(int argc, char** argv) {

  struct timeval2 tv;
  struct timezone2 tz;

  ZeroMemory(&tv, sizeof(tv));
  ZeroMemory(&tz, sizeof(tz));

  gettimeofday(&tv, &tz);

  unsigned long seed = tv.tv_sec ^ (tv.tv_usec << 12);

  srand(seed);

}

少しやり過ぎかもしれませんが、短い間隔でうまく機能します。 gettimeofday関数が見つかりました here

編集:さらに調査すると、Rand_sはVisual Studioの優れた代替品になる可能性があります。これは安全なRand()であるだけでなく、まったく異なるものであり、srandのシードを使用しません。私はそれがランドとほとんど同じであると推測していました。

Rand_sを使用するには、stdlib.hがインクルードされる前に_CRT_Rand_Sを#defineすることを忘れないでください。

0
colin lamarre

プログラムがLinuxでのみ実行されている限り(およびプログラムがELF実行可能ファイルである限り)、カーネルがプロセスにELF auxベクトルの一意のランダムシードを提供することが保証されます。カーネルは、プロセスごとに異なる16のランダムバイトを提供し、getauxval(AT_RANDOM)で取得できます。これらをsrandに使用するには、それらのintのみを使用します。

#include <sys/auxv.h>

void initrand(void)
{
    unsigned int *seed;

    seed = (unsigned int *)getauxval(AT_RANDOM);
    srand(*seed);
}

これは、他のELFベースのシステムにも変換される可能性があります。 Linux以外のシステムにどのaux値が実装されているのかわかりません。

0
Dolda2000

次のようなシグネチャを持つ関数があるとします:

int foo(char *p);

ランダムシードのエントロピーの優れたソースは、次のハッシュです。

  • 下位ビットを捨てずにclock_gettime(秒およびナノ秒)の完全な結果-それらは最も価値があります。
  • pの値、uintptr_tにキャスト。
  • pのアドレス。uintptr_tにキャストされます。

少なくとも3番目、場合によっては2番目も、システムのASLRからエントロピーを取得します(利用可能な場合、初期スタックアドレス、したがって現在のスタックアドレスは多少ランダムです)。

また、グローバル状態に触れないために、Rand/srandを完全に使用することも避けたいため、使用されるPRNGをさらに制御できます。ただし、上記の手順は、使用するPRNGに関係なく、多くの作業をせずに適切なエントロピーを取得するのに適した(そしてかなり移植性の高い)方法です。

0
R..