全範囲にわたって一様に乱数を生成する


93

指定された間隔[max; min]内で乱数を生成する必要があります。

また、乱数は、特定のポイントに配置されるのではなく、間隔全体に均一に分布する必要があります。

Currenly私は次のように生成しています:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

私のテストでは、乱数は1点のみで生成されます。

Example
min = 3604607;
max = 7654607;

生成される乱数:

3631594
3609293
3630000
3628441
3636376
3621404

以下の回答から:OK、RAND_MAXは32767です。私はC ++ Windowsプラットフォームを使用しています。一様分布で乱数を生成する他の方法はありますか?


2
Dice-O-Maticを構築する:gamesbyemail.com/News/DiceOMatic
Jarett Millard

1
C ++ rand()が統一されているとは思いもしませんでした。どのライブラリを使用していますか?cstdlib.hさんはrand()一様ではない:cplusplus.com/reference/cstdlib/rand
マイク・ウォーレン

3
いいえ、rand()は統一されています(初期のバグのある実装を除いて)。均一でないのは、範囲を制限するために係数「%」演算子を使用していることです。適切な解決策については、stackoverflow.com / questions / 2999075 /…を参照してください。または、「arc4random_uniform」が利用可能な場合は、それを直接使用することもできます。
John Meacham 2013年

@ Alien01:受け入れられた回答を「靴」による回答に変更することを検討しますか(「なぜランドが悪いのか」など)。私の答えは本当に時代遅れであり、私がそれに対する賛成票を得るたびに、誰かが間違った通路を走っていると感じています。
peterchen

c ++ 11のランダムに関する素晴らしいホワイトペーパー
Pupsik

回答:


153

なぜrand悪い考えですか

ここで得た答えのほとんどは、rand関数とモジュラス演算子を利用しています。この方法では、数値が均一に生成されない場合があります(範囲との値によって異なりますRAND_MAX)。そのため、お勧めしません。

C ++ 11と範囲での生成

C ++ 11では、他の複数のオプションが増えています。そのうちの1つは、範囲内の乱数を生成するための要件に非常によく適合しますstd::uniform_int_distribution。次に例を示します。

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

そして、ここでの「実行中の一例です。

その他のランダムジェネレーター

<random>ヘッダは、ベルヌーイ、ポアソンと正常含む分布の異なる種類の無数の他の乱数発生器を提供します。

コンテナをシャッフルするにはどうすればよいですか?

標準はstd::shuffle、次のように使用できるを提供します。

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

アルゴリズムは、線形の複雑さで要素をランダムに並べ替えます。

Boost.Random

C ++ 11 +コンパイラにアクセスできない場合のもう1つの方法は、Boost.Randomを使用することです。そのインターフェースはC ++ 11のものと非常によく似ています。


22
この回答ははるかに近代的であるため、この回答には注意を払ってください。
gsamaras 14

これが正解です。ありがとう!それでも、そのコードのすべてのステップのより詳細な説明を見たいと思います。たとえば、mt19937タイプとは何ですか?
アポロ

@Apolloドキュメントには、「松本と西村による32ビットのメルセンヌツイスター、1998年」と書かれています。疑似乱数を生成するアルゴリズムだと思います。
2015年

@Shoeは、指定された範囲に対して、同じ順序で数値を生成します1 9 6 2 8 7 1 4 7 7。プログラムを実行するたびにこれをランダム化する方法を教えてください。

1
@リチャード代替は何ですか?
2016

59

[編集] 警告:rand()統計、シミュレーション、暗号化など、深刻なものには使用しないでください。

急いで、典型的な人間の数ランダムに見せるために十分です。

より適切なオプションについては@Jefffreyの返信を参照してください。暗号で保護された乱数についてはこの回答をご覧ください。


通常、上位ビットは下位ビットよりも優れた分布を示すため、単純な目的で範囲の乱数を生成するための推奨される方法は次のとおりです。

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

:RAND_MAX + 1がオーバーフローしないことを確認してください(Demiに感謝)!

除算は[0、1]の間隔で乱数を生成します。これを必要な範囲に「ストレッチ」します。max-min + 1がRAND_MAXに近づく場合にのみ、Mark Ransomによって投稿されたような「BigRand()」関数が必要です。

これにより、数値をさらに悪化させる可能性があるモジュロによるスライスの問題も回避されます。


組み込みの乱数ジェネレーターは、統計シミュレーションに必要な品質を保証していません。数値が人間に「ランダムに見える」ことは問題ありませんが、深刻なアプリケーションでは、何かを改善するか、少なくともその特性を確認する必要があります(均一分布は通常良好ですが、値は相関する傾向があり、シーケンスは確定的です)。Knuthは、乱数ジェネレータに関する優れた(読みにくい場合)論文を持っています。最近、LFSRは、そのプロパティがあなたにとってOKであることを考えると、優れており、実装が非常に簡単であることがわかりました。


4
BigRandは、目的の範囲がRAND_MAXを超えない場合でも、より良い結果を提供できます。RAND_MAXが32767であり、32767の可能な値が必要な場合を検討してください。これらの32768の乱数(ゼロを含む)の2つは同じ出力にマップされ、他の2倍の確率で発生します。ほとんど理想的なランダムプロパティではありません!
マークランサム

7
(RAND_MAX + 1)は悪い考えです。これはロールオーバーして負の値を与える可能性があります。((double)RAND_MAX)+ 1.0
デミ

3
@peterchen:デミの言ったことを誤解していると思います。彼女はこれを意味しました:( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min 変換を2倍に移動し、問題を回避します。
Mooing Duck 2013

3
また、これは、分布を範囲内の下位32767値から範囲内の均一に分布した32767値に変更するだけであり、残りの4017233値はこのアルゴリズムによって選択されることはありません。
Mooing Duck 2013

1
与えられた答えは1ずれています。正しい式は次のとおりです:((double)rand()/(RAND_MAX + 1.0))*(max-min)+ min「max-min + 1」は、%not *を使用するときに使用されます* 。min = 0、max = 1を実行すると、理由がわかります。peterchenまたは@ peter-mortensenが修正できます。
davepc 2013年

17

Angry Shoeとpeterchenの優れた答えを、2015年の最新技術の概要で補足したいと思います。

いくつかの良い選択肢

randutils

randutilsライブラリー(プレゼンテーション)は、シンプルなインターフェイスと(宣言)堅牢なランダム機能を提供、面白いノベルティです。それはあなたのプロジェクトへの依存を追加するという不利な点を持っています、そして、新しいので、それは広範囲にテストされていません。とにかく、無料(MITライセンス)でヘッダーのみなので、試してみる価値はあると思います。

最小限のサンプル:サイコロ

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

ライブラリに興味がない場合でも、ウェブサイト(http://www.pcg-random.org/)には、一般的な乱数生成のテーマ、特にC ++ライブラリに関する興味深い記事が多数掲載されています。

Boost.Random

Boost.Random (ドキュメント)インスピレーションを得たライブラリであるC++11のを<random>インタフェースの多くを共有して誰と、。理論的には外部依存関係でもBoostありますが、「準標準」ライブラリのステータスになりました。そのRandomモジュールは、良質な乱数生成の古典的な選択肢と見なすことができます。C++11ソリューションに関して2つの利点があります。

  • 移植性が高く、C ++ 03のコンパイラサポートが必要なだけ
  • そのrandom_device優れた品質の播種提供するために使用するシステム固有の方法

唯一の小さな欠点は、提供されるモジュールrandom_deviceがヘッダーのみではなく、コンパイルしてリンクする必要があることboost_randomです。

最小限のサンプル:サイコロ

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

最小限のサンプルはうまく機能しますが、実際のプログラムは2つの改善点を使用する必要があります。

  • make mt19937a thread_local:ジェネレータはかなりふくよか(> 2 KB)で、スタックに割り当てない方がよい
  • mt19937複数の整数のシード:Mersenne Twisterは大きな状態にあり、初期化中により多くのエントロピーを利用できます

あまり良くない選択

C ++ 11ライブラリ

最も慣用的なソリューションである<random>ライブラリは、基本的なニーズであっても、そのインターフェースの複雑さと引き換えに多くを提供しません。欠点はstd::random_device次のとおりです。Standardは、(entropy()returnsである限り0)出力の最低限の品質を義務付けていません。2015年の時点では、MinGW(最も使用頻度の高いコンパイラではありませんが、難解な選択ではありません)は常に4最小限のサンプルで印刷します。

最小限のサンプル:サイコロ

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

実装が腐っていない場合、このソリューションはBoostのソリューションと同等であり、同じ提案が適用されます。

Godotのソリューション

最小限のサンプル:サイコロ

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

これはシンプルで効果的かつきちんとしたソリューションです。C ++ 17が予定どおりにリリースされ、実験的randint機能が新しい標準に承認された場合、コンパイルに時間がかかるため、問題が発生します。約2年かかります。おそらくその頃には、播種品質の保証も向上するでしょう。

さらに悪い-で、よりよい解決策

最小限のサンプル:サイコロ

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

古いCのソリューションは有害であると考えられており、それには十分な理由があります(ここでの他の回答またはこの詳細な分析を参照してください)。それでも、それには利点があります:シンプルで、移植性があり、高速で正直です。ある意味で、得られる乱数はまともなものではないことがわかっているため、深刻な目的でそれらを使用したくありません。

会計トロールソリューション

最小限のサンプル:サイコロ

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

9は通常のダイスロールではやや珍しい結果ですが、このソリューションでは優れた品質の優れた組み合わせを賞賛する必要があります。9を4に置き換えることで、シンボルが豊富な値1、2、3を回避しながら、あらゆる種類のダンジョンとドラゴンズダイスに最適なジェネレーターを手に入れます。唯一の小さな欠点は、ディルバートの会計トロールの気性が悪いためこのプログラムは実際には未定義の動作を引き起こします。


randutilsライブラリは現在、PCGと呼ばれています。
tay10r

11

RAND_MAXが32767の場合、ビット数を簡単に2倍にすることができます。

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

私はこれがうまくいくとは思いません。疑似乱数ジェネレータは通常、確定的です。たとえば、最初のrand呼び出しが返され0x1234て2番目の呼び出しが返されると0x5678、が返されます0x12345678。それはあなたが得ることができる唯一の数です。0x1234次の数は常になるから0x5678です。32ビットの結果が得られますが、可能な数は32768しかありません。
user694733

@ user694733優れた乱数ジェネレータは、生成できる出力の数よりも大きい周期を持っているため、0x1234の後に常に 0x5678が続くとは限りません。
Mark Ransom

9

可能であれば、Boostを使用してください。私は彼らのランダムなライブラリで幸運を祈っています

uniform_int あなたがしたいことをする必要があります。


私は、merseinneツイスターを使用して、uniform_intについていくつかの作業を行いましたが、残念ながら、特定の範囲では、uniform_intによって返される値は、期待したほど均一ではありません。たとえば、uniform_int <>(0、3)は、1または2よりも0を多く生成する傾向があります
ScaryAardvark

@ScaryAardvarkはuniform_int当時の悪い実装のように聞こえます。公平な出力を生成するのは非常に簡単です。この方法を示す複数の質問がここにあります。
Mark Ransom

@Mark Ransom。はい、完全に同意します。
ScaryAardvark

8

速度ではなくランダム性に関心がある場合は、安全な乱数生成方法を使用する必要があります。これを行うにはいくつかの方法があります... OpenSSLの Random Number Generatorを使用するのが最も簡単な方法です

暗号化アルゴリズム(AESなど)を使用して独自に作成することもできます。シードとIVを選択し、暗号化関数の出力を継続的に再暗号化する。OpenSSLを使用する方が簡単ですが、男らしさが少なくなります。


サードパーティのライブラリは使用できませんか?C ++のみに制限されています。
anand 2008年

次に、男らしいルートに進み、AESまたはその他の暗号化アルゴリズムを実装します。
SoapBox 2008年

2
RC4はコードを作成するのは簡単であり、すべての実用的な目的に十分にランダムです(WEPを除くが、これは完全にRC4の欠点ではありません)。つまり、信じられないほど簡単なコードです。20行ぐらい。ウィキペディアのエントリに疑似コードがあります。
スティーブジェソップ

4
サードパーティのコードを使用できないのはなぜですか?これが宿題の質問である場合は、そうする必要があります。多くの人は、この場合の完全な解決策を提供するのではなく、役立つヒントを提供するためです。宿題ではない場合は、「サードパーティのコードはありません」と言っている男をバカにしてください。
DevSolar 2010

OpenSSL rand()関数ドキュメントへのより直接的なリンク:openssl.org/docs/crypto/rand.html#
DevSolar

5

RAND_MAX特定のコンパイラ/環境を確認する必要があります。がrand()ランダムな16ビット数を生成する場合、これらの結果が表示されると思います。(32ビットの数値になると想定しているようです)。

これが正解であるとは約束できませんが、の値とRAND_MAX、環境についてもう少し詳しく投稿してください。


3

RAND_MAXシステムの内容を確認してください-たった16ビットで、範囲が大きすぎると思います。

その上で、この説明を参照してください:望ましい範囲内のランダム整数の生成C rand()関数の使用(または使用しない)に関する注記。


OK RAND_MAXは32767です。C++ Windowsプラットフォームを使用しています。均一な分布で乱数を生成する他の方法はありますか?
anand 2008年

2

これはコードではありませんが、このロジックが役立つ場合があります。

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

数値を範囲全体に均一に分布させる場合は、範囲を、必要なポイント数を表すいくつかの等しいセクションに分割する必要があります。次に、各セクションの最小/最大の乱数を取得します。

別のメモとしてrand()、実際に乱数を生成するのはあまり得意ではないので、おそらく使用しないでください。あなたが実行しているプラ​​ットフォームはわかりませんが、のように呼び出すことができるより良い関数があると思いrandom()ます。


1

これにより[low, high)、全体の範囲がRAND_MAX未満である限り、フロートを使用せずに範囲全体に均一な分布が提供されます。

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

RAND_MAXより大きい値の場合、次のようなものが必要です

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

これはおおよそstd :: uniform_int_distributionが行うことです。


0

その性質上、乱数の小さなサンプルは均一に分布する必要はありません。結局のところ、それらはランダムです。乱数ジェネレーターが常にグループ化されているように見える数を生成している場合は、おそらく何かが間違っていることに同意します。

ただし、ランダム性は必ずしも均一ではないことに注意してください。

編集:明確にするために「小さなサンプル」を追加しました。


「均一に分散」は明確に定義された意味を持ち、通常、標準のランダムジェネレーターが近くなります。
peterchen 2008年

はい、そうです、乱数ジェネレーターは、時間の経過とともに分布がほぼ均一になる出力を生成する必要があります。私のポイントは、少数のインスタンス(例に示すように6つ)では、出力が常に均一になるとは限らないということです。
Kluge

クルーゲは正しいです。小さなサンプルの均一な分布は、サンプルが明らかにランダムではないことを示しています。
トカゲに請求する

1
ビル、それはそのようなことを示していません。小さなサンプルはほとんど意味がありませんが、RNGが均一で出力が均一であると想定されている場合、不均一な小さなサンプルよりも悪いのはなぜですか?
Dan Dyer、

2
いずれかの方法で有意な分布がランダムでないことを示しています。ビルは、6つの等間隔の結果も疑わしいということを意味していると思います。OPでは、6つの値が32k / 4Mの範囲、つまり目的の範囲の1%未満です。これが誤検出である確率は小さすぎて議論できません。
スティーブジェソップ

0

1から10までの数値に対してman 3 randが与える解は、次のとおりです。

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

あなたの場合、それは:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

もちろん、他のいくつかのメッセージが指摘しているように、これは完全なランダム性や均一性ではありませんが、ほとんどの場合これで十分です。


1
これは、単にするために配布並べ替えて表示されるより均一に、しかし、それも(たとえば、OPの場合のように)大きな範囲についてはこれ以上、実際にはありません
ダックMooing

0

@解決 ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

警告:ストレッチと精度エラーの可能性があるため(RAND_MAXが十分に大きい場合でも)忘れないでください。[min、max]のすべての数値ではなく、均等に分散された「ビン」のみを生成できます。


@ソリューション:Bigrand

警告:これによりビットが2倍になりますが、一般的には範囲内のすべての数値を生成することはできません。つまり、BigRand()がその範囲内のすべての数値を生成するとは限りません。


情報:rand()の範囲が間隔の範囲を超え、rand()が「均一」である限り、アプローチ(モジュロ)は「良好」です。最大で最初の最大-最小数のエラーは1 /(RAND_MAX +1)です。

また、C ++ 11でも新しいランダムパッケージ eに切り替えることをお勧めします。これにより、rand()よりも優れたさまざまな実装が可能になります。


0

これは私が思いついた解決策です:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

これはバケットソリューションであり、0〜1 rand() / RAND_MAXの浮動小数点範囲を取得し、それをバケットに丸めるために使用するソリューションに概念的に似ています。ただし、これは純粋に整数の計算を使用し、整数除算フローリングを利用して値を最も近いバケットに切り捨てます。

それはいくつかの仮定をします。まず、RAND_MAX * (max - min + 1)が常に内に収まることを前提としていますint32_t。場合はRAND_MAX32767と32ビットのint型の計算が使用されている、あなたが持つことができる最大範囲は32767です。あなたの実装がRAND_MAXより大きな多くを持っている場合は、より大きな整数(などを使用してこれを克服することができますint64_t計算のため)。次に、int64_tが使用されているがRAND_MAX32767のままである場合、より大きな範囲RAND_MAXでは、可能な出力数に「穴」ができ始めます。これはおそらく、スケーリングから派生したソリューションの最大の問題rand()です。

それにもかかわらず、膨大な数の反復をテストすると、この方法は狭い範囲で非常に均一であることがわかります。ただし、数学的にはこれに小さなバイアスがあり、範囲が近づくと問題が発生する可能性があります(可能性が高い)RAND_MAX。自分でテストして、ニーズに合っているかどうかを判断してください。


-1

もちろん、次のコードでは乱数は得られず、疑似乱数が得られます。次のコードを使用します

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

例えば:

int myRand = QUICK_RAND(10, 20);

電話する必要があります

srand(time(0));  // Initialize random number generator.

そうでない場合、数値はほぼランダムになりません。


1
問題は、均一な分布を求めることです。この提案されたソリューションは、均一な分布を生成しません。標準C ++ライブラリには、疑似乱数生成機能があります。要求があれば、それら均一な配布を提供します。
IInspectable

-3

私はインターネットでこれを見つけました。これはうまくいくはずです:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

あなたがそれらを必要とするものを明確にしてください、そこにはPRNGのためのアルゴリズムがたくさんあります。また、返信を投稿するのではなく、メインの質問を編集する方が簡単です。
peterchen 2008年

これは私にとって最も効果的に機能します...この式を使用すると、より良い分布の乱数を得ることができます
。– anand

4
範囲がRAND_MAXを超えると、結果が均一にならない可能性があります。つまり、関数を呼び出す回数に関係なく、表されない範囲の値があります。
dmckee ---元モデレーターの子猫

4
また、maxとminがどちらも符号なし整数で、minが0で、maxがMAX_UINTの場合、((max)-(min)+1)は0になり、結果は常に0になります。このタイプの数学を行うオーバーフローに注意してください!dmckeeによって指摘されているように、これは宛先範囲の分布を拡大しますが、RAND_MAXを超える一意の値を保証するものではありません。
jesup
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.