1. ホーム
  2. c++

[解決済み】C++で乱数を生成する方法は?

2022-04-11 08:51:17

質問

サイコロを使ったゲームを作ろうとしているのですが、その中に乱数を入れる必要があります(サイコロの面を模したもの。 1から6の間にする方法は知っています)。 使用方法

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

using namespace std;

int main() 
{ 
    srand((unsigned)time(0)); 
    int i;
    i = (rand()%6)+1; 
    cout << i << "\n"; 
}

というのも、このプログラムを何度か実行すると、次のような出力が得られるからです。

6
1
1
1
1
1
2
2
2
2
5
2

を生成するコマンドが欲しいのです。 異なる 5回連続で同じ乱数ではなく、毎回同じ乱数です。 これを実行するコマンドはありますか?

解決方法は?

テスト・アプリケーションの最も根本的な問題は、あなたが srand を一度呼び出し、その後 rand を1回実行して終了します。

のポイントは srand 関数は 擬似乱数列 をランダムシードとする。

を渡すと 同じ値 srand を2つの異なるアプリケーションで(同じ srand / rand の実装) であれば という文字列が表示されます。 rand() の後に読み込まれる値は、どちらのアプリケーションでも同じです。

しかし、この例のアプリケーションでは、擬似乱数列は1つの要素だけで構成されています。 1 sec の精度です。では、どのような出力が期待できるのでしょうか?

もちろん、同じ秒数でアプリケーションを実行する場合、同じシード値を使用するため、結果はもちろん同じです(Martin Yorkが質問に対するコメントですでに述べているとおりです)。

実際には srand(seed) を1回呼び出した後 rand() 何度も その配列を解析すると、ランダムに見えるはずです。

AMENDMENT 1 - コード例です。

わかったよ。 どうやら口頭での説明では不十分なようです(言葉の壁か何かでしょうか... :))。

をベースにした昔ながらのC言語のコード例です。 srand()/rand()/time() の関数は、問題で使用されたものです。

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

int main(void)
{
    unsigned long j;
    srand( (unsigned)time(NULL) );

    for( j = 0; j < 100500; ++j )
    {
        int n;

        /* skip rand() readings that would make n%6 non-uniformly distributed
          (assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
        while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
        { /* bad value retrieved so get next one */ }

        printf( "%d,\t%d\n", n, n % 6 + 1 );
    }

    return 0;
}

^^^ あれ の配列はランダムに見えるはずです。

ご注意ください を使用することはお勧めしません。 rand / srand を使用することはお勧めしません。 time というのも、この理由はすでに明らかなはずです。これらは教育的な目的や、時々ポイントを説明するのには良いのですが、真剣に使うにはほとんど役に立ちません。

AMENDMENT 2 - 詳細な説明。

今現在は、以下のようなものがあることを理解することが重要です。 なし CやC++の標準機能(ライブラリ関数やクラス)は、実際にランダムなデータを決定的に生成します(つまり、実際にランダムであることが標準によって保証されています)。この問題にアプローチする唯一の標準機能は std::random_device が、残念ながらまだ実際のランダム性を保証するものではありません。

アプリケーションの性質によっては、本当にランダムな(予測不可能な)データが必要なのかどうかを最初に判断する必要があります。主な事例 真のランダム性が最も必要な場合 対称鍵、非対称秘密鍵、ソルト値、セキュリティトークンなどの生成などです。

しかし、セキュリティグレードの乱数は、別の記事に値する別業界のものです。私は、この乱数について この回答 私の

ほとんどの場合 擬似乱数発生器 科学的なシミュレーションやゲームなどでは、これで十分です。場合によっては、一貫して定義された疑似乱数列が必要なこともあります。例えば、ゲームでは、配布物に多くのデータを保存するのを避けるために、実行時に全く同じマップを生成することを選択することができます。

元の質問と、それに類似した多くの質問(およびそれに対する多くの誤った回答)から、何よりもまず、乱数と疑似乱数を区別すること、そもそも疑似乱数列とは何かを理解すること、疑似乱数発生器は本当の乱数発生器と同じようには使われないことを認識することが重要であることがわかります。

直感的に 乱数を要求したとき、返される結果は以前に返された値に依存してはいけません。 また、どのような瞬間に、どのようなものを要求したかに依存すべきではない。 どのようなプロセスで、どのようなコンピュータで、どのような発電機から、どのような どの銀河系で要求されたのか。それが ランダム というのは 結局のところ、予測不可能であり、何からも独立している - 。 そうでなければ、もはやランダムではないでしょう?この直感があれば を得るための魔法の呪文をウェブで検索するのは当然のことです。 このような乱数は、どのような状況でも発生します。

^^^ そのような直感的な期待は、非常に間違っており、有害である。 を含むすべてのケースで 疑似乱数発生器 - 真の乱数には合理的であるにもかかわらず。

乱数という概念は(一応)存在しますが、擬似乱数というものは存在しません。A 擬似乱数発生器 実際に擬似乱数を発生させる シーケンス .

擬似乱数列は、実際には常に 決定論的 (アルゴリズムと初期パラメータによってあらかじめ決まっている)、つまり、実際には何もランダムなものはないのです。

専門家がPRNGの品質について語るとき、実際には生成されるシーケンス(とその注目すべきサブシーケンス)の統計的特性について話しているのです。例えば,2つの高品質のPRNGを交互に使って組み合わせた場合,それぞれ良いシーケンスを生成しているにもかかわらず,結果として悪いシーケンスを生成してしまうかもしれません(その2つの良いシーケンスは単に互いに相関しているため,悪い組み合わせになってしまうかもしれません).

具体的には rand() / srand(s) は、実装で定義されたアルゴリズムで生成された、プロセス単位の非スレッドセーフな(!)疑似乱数列を提供します。関数 rand() の範囲にある値を生成します。 [0, RAND_MAX] .

C11規格(ISO/IEC 9899:2011)より引用。

<ブロッククオート

srand 関数は、引数を新しい一連の を呼び出すと、擬似乱数が返されます。 rand . もし srand が同じシード値で呼び出された場合、一連の 擬似乱数を繰り返すものとする。もし rand が呼び出される前に を呼び出します。 srand と同じシーケンスが生成されるものとする。 時 srand が最初に呼ばれ、シード値は1である。

多くの人は、合理的に考えて rand() の範囲にある半独立の一様分布数列を生成します。 0 から RAND_MAX . まあ、確かにそうすべきなのですが(そうでなければ意味がない)、残念ながら標準がそれを要求していないだけでなく、次のような明確な免責事項さえあるのです。 生成される乱数列の品質については保証されません。 . 歴史的な事例では rand / srand の実装は、実に質の悪いものでした。最近の実装では十分な品質である可能性が高いのですが、信頼は失われており、回復するのは簡単ではありません。 さらに、スレッドセーフでないため、マルチスレッドアプリケーションでの安全な使用は厄介で制限されています(それでも可能です - 1つの専用スレッドから使用するだけかもしれません)。

新しいクラステンプレート std::mersenne_twister_engine<> (とその便利な型定義 -)。 std::mt19937 / std::mt19937_64 をテンプレートパラメータとうまく組み合わせて)提供します。 オブジェクト単位 C++11標準規格で定義された擬似乱数生成器です。同じテンプレート パラメータと同じ初期化パラメータを使用すれば、C++11 準拠の標準ライブラリで構築されたアプリケーションのどのコンピュータ上でも、異なるオブジェクトがまったく同じオブジェクト単位の出力列を生成することになります。このクラスの利点は、予測可能な高品質の出力シーケンスと、実装間での完全な一貫性にあります。

また、C++11 標準では、さらに多くの PRNG エンジンが定義されています。 std::linear_congruential_engine<> (歴史的に公正な品質として使用される srand/rand アルゴリズム)、および std::subtract_with_carry_engine<> . また、完全に定義されたパラメータ依存のオブジェクトごとの出力シーケンスが生成されます。

上記の時代遅れのCコードに代わる、現代のC++11の例。

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

int main()
{
    std::random_device rd;
    // seed value is designed specifically to make initialization
    // parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
    // different across executions of application
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);

    for( unsigned long j = 0; j < 100500; ++j )
    /* ^^^Yes. Generating single pseudo-random number makes no sense
       even if you use std::mersenne_twister_engine instead of rand()
       and even when your seed quality is much better than time(NULL) */    
    {
        std::mt19937::result_type n;
        // reject readings that would make n%6 non-uniformly distributed
        while( ( n = gen() ) > std::mt19937::max() -
                                    ( std::mt19937::max() - 5 )%6 )
        { /* bad value retrieved so get next one */ }

        std::cout << n << '\t' << n % 6 + 1 << '\n';
    }

    return 0;
}

を使用する以前のコードのバージョンです。 std::uniform_int_distribution<>

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

int main()
{
    std::random_device rd;
    std::mt19937::result_type seed = rd() ^ (
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::system_clock::now().time_since_epoch()
                ).count() +
            (std::mt19937::result_type)
            std::chrono::duration_cast<std::chrono::microseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
                ).count() );

    std::mt19937 gen(seed);
    std::uniform_int_distribution<unsigned> distrib(1, 6);

    for( unsigned long j = 0; j < 100500; ++j )
    {
        std::cout << distrib(gen) << ' ';
    }

    std::cout << '\n';
    return 0;
}