web-dev-qa-db-ja.com

範囲から乱数を生成する

与えられた範囲(境界値を含む)にランダムな整数を生成する関数が必要です。私は不当な品質/無作為性の要件はありません、私は4つの要件があります。

  • 速くする必要があります。私のプロジェクトは数百万(時には数千万)の乱数を生成する必要があり、私の現在のジェネレータ関数はボトルネックになっています。
  • 私はそれが適度に均一である必要があります(Rand()の使用は完全に素晴らしいです)。
  • 最小値と最大値の範囲は、<0、1>から<-32727、32727>までです。
  • それは播種可能でなければなりません。

私は現在以下のC++コードを持っています。

output = min + (Rand() * (int)(max - min) / Rand_MAX)

問題は、それが実際には一様ではないということです。maxは、Rand()= Rand_MAXの場合にのみ返されます(Visual C++の場合、1/32727です)。これは、最後の値がほとんど返されない<-1、1>のような小さな範囲に対する大きな問題です。

それで私はペンと紙をつかみ、次の公式を思いついた(それは(int)(n + 0.5)整数丸めトリックに基づく):

enter image description here

しかし、それでもまだ一様分布にはなりません。 10000個のサンプルを用いた反復実験は、値−1、0.1について37:50:13の比を与える。

もっと良い処方を教えてください。 (または疑似乱数生成関数全体)

144
Matěj Zábský

速く、あなたよりもいくらか優れていますが、それでも適切に統一されていない分散ソリューションは、

output = min + (Rand() % static_cast<int>(max - min + 1))

範囲のサイズが2の累乗である場合を除いて、このメソッドは、品質に関係なく 偏った不均等分布を生成します。 Rand()の説明。このメソッドの品質の包括的なテストについては、 こちらを読んでください をご覧ください。

94
Mark B

最も単純な(したがって最良の)C++(2011標準を使用)の答えは、

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

ホイールを作り直す必要はありません。バイアスについて心配する必要はありません。ランダムシードとして時間を使うことを心配する必要はありません。

268
Walter

コンパイラがC++ 0xをサポートしており、それを使用することが選択肢である場合は、新しい標準の<random>ヘッダがニーズを満たす可能性があります。それは高品質のuniform_int_distributionを持ち、それは最小値と最大値を(あなたが必要とするものを含めて)受け入れます、そしてあなたはその分布にプラグインするために様々な乱数ジェネレータの中から選ぶことができます。

これは[-57、365]で一様に配布された何百万ものランダムなintを生成するコードです。パフォーマンスがあなたにとって大きな関心事であるとあなたが述べたように、私はそれを時間を計るために新しいstd <chrono>機能を使用しました。

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_Rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

私(2.8 GHzのIntel Core i5)の場合、これは印刷されます。

2.10268e + 07 1秒あたりの乱数。

そのコンストラクタにintを渡すことでジェネレータをシードできます。

    G g(seed);

intがあなたのディストリビューションに必要な範囲をカバーしていないことが後でわかった場合は、uniform_int_distributionを(例えばlong longに)変更することで解決できます。

    typedef std::uniform_int_distribution<long long> D;

後でminstd_Randが十分に高品質のジェネレータではないことがわかった場合は、それも簡単に交換できます。例えば。:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

乱数発生器を個別に制御すること、および乱数分布をかなり自由にすることができます。

また、この分布の最初の4つの「モーメント」(minstd_Randを使用)を計算し(図示せず)、それらを 理論値 と比較して分布の質を定量化しました。

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

x_接頭辞は "expected"を意味します)

60
Howard Hinnant

問題を2つの部分に分けてみましょう。

  • 0から(max-min)までの範囲の乱数nを生成します。
  • その数に分を追加

最初の部分は明らかに最も困難です。 Rand()の戻り値が完全に一様であると仮定しましょう。 moduloを使用すると、最初の(Rand_MAX + 1) % (max-min+1)番号にバイアスが追加されます。そのため、Rand_MAXRand_MAX - (Rand_MAX + 1) % (max-min+1)に魔法のように変更することができれば、それ以上の偏りはなくなります。

私たちのアルゴリズムの実行時間に疑似非決定性を許しても構わないと思っているなら、この直感を使うことができることがわかります。 Rand()が大きすぎる数を返すときはいつでも、十分に小さい数を得るまで、単に別の乱数を求めます。

実行時間は 幾何学的に分散された となり、期待値は1/pになります。ここで、pは最初の試行で十分に小さい数になる確率です。 Rand_MAX - (Rand_MAX + 1) % (max-min+1)は常に(Rand_MAX + 1) / 2より小さいので、p > 1/2であることがわかります。したがって、予想される反復回数はどの範囲でも常に2未満になります。この手法を使えば、標準のCPU上で1秒以内に数千万の乱数を生成することが可能になるはずです。

編集:

上記は技術的には正しいですが、DSimonの回答は実際にはおそらくもっと便利です。あなたはこのものを自分で実装するべきではありません。私は拒絶サンプリングの多くの実装を見ました、そして、それが正しいかどうかを確かめることはしばしば非常に難しいです。

15
Jørgen Fogh

Mersenne Twister はどうですか。後押し実装はかなり使いやすく、多くの実際のアプリケーションで十分にテストされています。私は人工知能や進化的アルゴリズムのようないくつかの学術プロジェクトでそれを自分で使いました。

ここで彼らが6面サイコロを振るための簡単な機能を作る彼らの例があります:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

ああ、そしてあなたが非常に劣ったRand()の上にそれを使うべきだと確信していない念のために、これはこのジェネレータのもう少しの圧倒です:

Mersenne Twisterは、松本誠と西村拓司によって発明された「乱数」発生器です。彼らのウェブサイトはアルゴリズムの多数の実装を含んでいます。

基本的に、Mersenne Twisterは非常に大きな線形フィードバックシフトレジスタです。このアルゴリズムは、324ビットの符号なし整数の624要素の配列に格納されている19,937ビットのシードを操作します。 2 ^ 19937-1という値はメルセンヌ素数です。シードを操作するためのテクニックは、より古い "ねじれ"アルゴリズムに基づいています - それ故に "Mersenne Twister"という名前です。

Mersenne Twisterの魅力的な点は、時間のかかる乗算とは対照的に、数値を生成するためにバイナリ演算を使用することです。このアルゴリズムには、非常に長い期間と優れた精度があります。暗号化以外のアプリケーションでは、高速で効果的です。

13
Aphex
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)Rand() / (Rand_MAX+1) * (nMax-nMin+1));
}

これは、32768整数から(nMax-nMin + 1)整数へのマッピングです。 (あなたの要件のように)(nMax-nMin + 1)が小さければ、マッピングはかなり良くなるでしょう。ただし、(nMax-nMin + 1)が大きい場合、マッピングは機能しません(たとえば、32768の値を30000の値に同じ確率でマッピングすることはできません)。このような範囲が必要な場合は、15ビットのRand()の代わりに32ビットまたは64ビットのランダムソースを使用するか、または範囲外のRand()の結果を無視してください。

11
Lior Kogan

これは[low, high]に数字を生成する公平なバージョンです。

int r;
do {
  r = Rand();
} while (r < ((unsigned int)(Rand_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

範囲が適度に小さい場合は、比較の右側をdoループにキャッシュする必要はありません。

4

私は Boost.Randomライブラリ をお勧めします。これは非常に詳細で十分に文書化されています。実際のところ は典型的なCライブラリRandの実装よりも より優れています。

3
DSimon

最小値と最大値がint値であると仮定します。[and]はこの値を含むことを意味し、(and)はこの値を含まないことを意味します。

参照:()[]を定義するには、次のURLにアクセスしてください。

https://en.wikipedia.org/wiki/Interval_(数学)

randおよびsrand関数またはRand_MAX定義については、次のURLにアクセスしてください。

http://en.cppreference.com/w/cpp/numeric/random/Rand

[最小、最大]

int randNum = Rand() % (max - min + 1) + min

(最小、最大]

int randNum = Rand() % (max - min) + min + 1

[最小、最大)

int randNum = Rand() % (max - min) + min

(最小、最大)

int randNum = Rand() % (max - min - 1) + min + 1
1
Huang Kun

このスレッドリジェクションサンプリングでは既に議論されていますが、Rand() % 2^somethingは既に上で述べたようにバイアスを導入しないという事実に基づく最適化を提案したいと思いました。

アルゴリズムはとても簡単です。

  • 区間長よりも大きい2の最小乗数を計算する
  • その「新しい」区間で1つの数をランダム化する
  • 元の区間の長さより短い場合はその数を返す
    • そうでなければ拒絶する

これが私のサンプルコードです:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = Rand() % ceilingPowerOf2; //this is "as uniform as Rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

これは、2のべき乗が実際の間隔の長さに「近い」ため、ミスの数が少なくなるため、特に狭い間隔でうまく機能します。

PS
再帰を避けた方が明らかに効率的です(ログの上限を超えて計算する必要はありません)。ただし、この例では読みやすくなっていると思いました。

0
Pado