Générer des nombres aléatoires à l'aide de la bibliothèque aléatoire C ++ 11

135

Comme le titre l'indique, j'essaie de trouver un moyen de générer des nombres aléatoires en utilisant la nouvelle <random>bibliothèque C ++ 11 . Je l'ai essayé avec ce code:

std::default_random_engine generator;
std::uniform_real_distribution<double> uniform_distance(1, 10.001);

Le problème avec le code que j'ai est que chaque fois que je le compile et l'exécute, il génère toujours les mêmes nombres. Ma question est donc de savoir quelles autres fonctions de la bibliothèque aléatoire peuvent accomplir cela tout en étant vraiment aléatoires?

Pour mon cas d'utilisation particulier, j'essayais d'obtenir une valeur dans la plage [1, 10]

smac89
la source
3
Cette question frôle dangereusement «principalement basée sur l'opinion». Si vous pouvez vous débarrasser de la sollicitation d'avis, je vois que cette question est très utile (si elle n'a pas déjà été posée).
John Dibling
4
Je suggère d'utiliser un std::mt19937comme moteur à moins que vous n'ayez une bonne raison de ne pas le faire. Et la distribution est un intervalle fermé aux deux extrémités.
chris
2
@chris la distribution n'est pas fermée des deux côtés, vérifiez ce lien ou ce lien
memo1288
1
@ memo1288, Merci, je pensais que l'OP utilisait un std::uniform_int_distribution, qui est fermé aux deux extrémités.
chris

Réponses:

191

Stephan T. Lavavej (stl) de Microsoft a fait une conférence à Going Native sur la façon d'utiliser les nouvelles fonctions aléatoires C ++ 11 et pourquoi pas les utiliser rand(). Il y a inclus une diapositive qui résout essentiellement votre question. J'ai copié le code de cette diapositive ci-dessous.

Vous pouvez voir son exposé complet ici: http://channel9.msdn.com/Events/GoingNative/2013/rand-Consemed-Harmful

#include <random>
#include <iostream>

int main() {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_real_distribution<double> dist(1.0, 10.0);

    for (int i=0; i<16; ++i)
        std::cout << dist(mt) << "\n";
}

Nous utilisons random_deviceune fois pour semer le générateur de nombres aléatoires nommé mt. random_device()est plus lent que mt19937, mais il n'a pas besoin d'être amorcé car il demande des données aléatoires à votre système d'exploitation (qui proviendra de divers endroits, comme RdRand par exemple).


En regardant cette question / réponse , il semble que uniform_real_distributionrenvoie un nombre dans la plage [a, b), où vous voulez [a, b]. Pour ce faire, notre uniform_real_distibutiondevrait ressembler à:

std::uniform_real_distribution<double> dist(1, std::nextafter(10, DBL_MAX));
Bill Lynch
la source
3
Étant donné que la question demande le moyen le plus général de générer des nombres aléatoires que vous voudrez peut-être simplement utiliser default_random_engine, selon l'amorce de c ++, c'est celui que l'implémentation a jugé le plus utile
aaronman
2
@aaronman: Je suis le discours de STL, où il n'aime pas explicitement que cela default_random_engineexiste.
Bill Lynch
5
@chris nous connaissons tous la différence entre un vecteur et une carte, tout le monde ne connaît pas la différence entre mt19937 et ranlux24, si quelqu'un réussit à devenir programmeur sans savoir ce que sont un vecteur et un dictionnaire, peut-être qu'ils devraient avoir un std::default_container, espérons qu'il n'y en a pas les gens se considérant comme des programmeurs qui ne connaissent pas les différences, de nombreux langages de script ont une structure de type de carte par défaut, qui pourrait être implémentée de différentes manières que l'utilisateur ne connaît peut-être pas
aaronman
21
L' nextafterappel est excessif pour la plupart des applications. Les chances d'un doubleatterrissage aléatoire exactement sur le point final sont si minuscules qu'il n'y a aucune différence pratique entre l'inclure et l'exclure.
Mark Ransom
3
@chris Sans rapport (mais vous avez ouvert la porte), votre std::vectoranalogie ne fonctionne pas ici car std::vector c'est en fait un bon défaut en raison de la mise en cache du processeur. Il surpasse même std::listpour l'insertion au milieu. C'est vrai même si vous comprenez tous les conteneurs et pouvez prendre une décision éclairée basée sur la complexité algorithmique.
void.pointer
24

Ma bibliothèque 'random' fournit un wrapper très pratique autour des classes aléatoires C ++ 11. Vous pouvez faire presque toutes choses avec une simple méthode «get».

Exemples:

  1. Nombre aléatoire dans une plage

    auto val = Random::get(-10, 10); // Integer
    auto val = Random::get(10.f, -10.f); // Float point
    
  2. Booléen aléatoire

    auto val = Random::get<bool>( ) // 50% to generate true
    auto val = Random::get<bool>( 0.7 ) // 70% to generate true
    
  3. Valeur aléatoire d'une std :: initilizer_list

    auto val = Random::get( { 1, 3, 5, 7, 9 } ); // val = 1 or 3 or...
  4. Itérateur aléatoire de la plage d'itérateur ou de tous les conteneurs

    auto it = Random::get( vec.begin(), vec.end() ); // it = random iterator
    auto it = Random::get( vec ); // return random iterator
    

Et encore plus de choses! Consultez la page github:

https://github.com/effolkronium/random

Ilya Polishchuk
la source
4

J'ai rouge tout ce qui précède, environ 40 autres pages contenant du c ++ comme ça et j'ai regardé la vidéo de Stephan T. Lavavej "STL" et je n'étais toujours pas sûr du fonctionnement des nombres aléatoires dans la praxis, alors j'ai pris un dimanche complet pour comprendre de quoi il s'agit et comment il fonctionne et peut être utilisé.

À mon avis, STL a raison de "ne plus utiliser srand" et il l'a bien expliqué dans la vidéo 2 . Il recommande également d'utiliser:

a) void random_device_uniform()- pour une génération chiffrée mais plus lente (d'après mon exemple)

b) les exemples avec mt19937- plus rapide, possibilité de créer des graines, non chiffrés


J'ai sorti tous les livres c ++ 11 revendiqués auxquels j'ai accès et j'ai découvert que des auteurs allemands comme Breymann (2015) utilisent toujours un clone de

srand( time( 0 ) );
srand( static_cast<unsigned int>(time(nullptr))); or
srand( static_cast<unsigned int>(time(NULL))); or

juste avec <random>au lieu de <time> and <cstdlib>#includings - alors faites attention à n'apprendre qu'à partir d'un seul livre :).

Cela signifie - cela ne devrait pas être utilisé depuis C ++ 11 car:

Les programmes ont souvent besoin d'une source de nombres aléatoires. Avant la nouvelle norme, C et C ++ reposaient sur une simple fonction de bibliothèque C nommée rand. Cette fonction produit des entiers pseudo-aléatoires qui sont uniformément distribués dans la plage de 0 à une valeur maximale dépendante du système qui est d'au moins 32767. La fonction rand pose plusieurs problèmes: de nombreux programmes, sinon la plupart, ont besoin de nombres aléatoires dans une plage différente de celle un produit par rand. Certaines applications nécessitent des nombres à virgule flottante aléatoires. Certains programmes ont besoin de nombres qui reflètent une distribution non uniforme. Les programmeurs introduisent souvent un caractère non aléatoire lorsqu'ils tentent de transformer la plage, le type ou la distribution des nombres générés par rand. (citation de Lippmans C ++ primer cinquième édition 2012)


J'ai finalement trouvé la meilleure explication parmi 20 livres dans les plus récents de Bjarne Stroustrups - et il devrait connaître son affaire - dans «A tour of C ++ 2019», «Programming Principles and Practice Using C ++ 2016» et «The C ++ Programming Language 4ème édition 2014 "et aussi quelques exemples dans" Lippmans C ++ primer cinquième édition 2012 ":

Et c'est vraiment simple car un générateur de nombres aléatoires se compose de deux parties: (1) un moteur qui produit une séquence de valeurs aléatoires ou pseudo-aléatoires. (2) une distribution qui mappe ces valeurs dans une distribution mathématique dans une plage.


Malgré l'avis du gars de Microsofts STL, Bjarne Stroustrups écrit:

Dans, la bibliothèque standard fournit des moteurs de nombres aléatoires et des distributions (§24.7). Par défaut, utilisez le default_random_engine, qui est choisi pour une large applicabilité et un faible coût.

L' void die_roll()exemple est de Bjarne Stroustrups - bonne idée de génération de moteur et de distribution avec using (plus de détails ici) .


Pour pouvoir utiliser concrètement les générateurs de nombres aléatoires fournis par la bibliothèque standard, voici un code exécutable avec différents exemples réduits au minimum nécessaire qui, espérons-le, vous feront gagner du <random> temps et de l'argent:

    #include <random>     //random engine, random distribution
    #include <iostream>   //cout
    #include <functional> //to use bind

    using namespace std;


    void space() //for visibility reasons if you execute the stuff
    {
       cout << "\n" << endl;
       for (int i = 0; i < 20; ++i)
       cout << "###";
       cout << "\n" << endl;
    }

    void uniform_default()
    {
    // uniformly distributed from 0 to 6 inclusive
        uniform_int_distribution<size_t> u (0, 6);
        default_random_engine e;  // generates unsigned random integers

    for (size_t i = 0; i < 10; ++i)
        // u uses e as a source of numbers
        // each call returns a uniformly distributed value in the specified range
        cout << u(e) << " ";
    }

    void random_device_uniform()
    {
         space();
         cout << "random device & uniform_int_distribution" << endl;

         random_device engn;
         uniform_int_distribution<size_t> dist(1, 6);

         for (int i=0; i<10; ++i)
         cout << dist(engn) << ' ';
    }

    void die_roll()
    {
        space();
        cout << "default_random_engine and Uniform_int_distribution" << endl;

    using my_engine = default_random_engine;
    using my_distribution = uniform_int_distribution<size_t>;

        my_engine rd {};
        my_distribution one_to_six {1, 6};

        auto die = bind(one_to_six,rd); // the default engine    for (int i = 0; i<10; ++i)

        for (int i = 0; i <10; ++i)
        cout << die() << ' ';

    }


    void uniform_default_int()
    {
       space();
       cout << "uniform default int" << endl;

       default_random_engine engn;
       uniform_int_distribution<size_t> dist(1, 6);

        for (int i = 0; i<10; ++i)
        cout << dist(engn) << ' ';
    }

    void mersenne_twister_engine_seed()
    {
        space();
        cout << "mersenne twister engine with seed 1234" << endl;

        //mt19937 dist (1234);  //for 32 bit systems
        mt19937_64 dist (1234); //for 64 bit systems

        for (int i = 0; i<10; ++i)
        cout << dist() << ' ';
    }


    void random_seed_mt19937_2()
    {
        space();
        cout << "mersenne twister split up in two with seed 1234" << endl;

        mt19937 dist(1234);
        mt19937 engn(dist);

        for (int i = 0; i < 10; ++i)
        cout << dist() << ' ';

        cout << endl;

        for (int j = 0; j < 10; ++j)
        cout << engn() << ' ';
    }



    int main()
    {
            uniform_default(); 
            random_device_uniform();
            die_roll();
            random_device_uniform();
            mersenne_twister_engine_seed();
            random_seed_mt19937_2();
        return 0;
    }

Je pense que cela ajoute tout cela et comme je l'ai dit, il m'a fallu beaucoup de lecture et de temps pour le décrire à ces exemples - si vous avez d'autres informations sur la génération de nombres, je suis heureux d'en entendre parler via pm ou dans la section des commentaires et l'ajoutera si nécessaire ou modifiera cet article. Booléen

Ivanovic
la source
0

Voici quelque chose que je viens d'écrire dans ce sens:

#include <random>
#include <chrono>
#include <thread>

using namespace std;

//==============================================================
// RANDOM BACKOFF TIME
//==============================================================
class backoff_time_t {
  public:
    random_device                      rd;
    mt19937                            mt;
    uniform_real_distribution<double>  dist;

    backoff_time_t() : rd{}, mt{rd()}, dist{0.5, 1.5} {}

    double rand() {
      return dist(mt);
    }
};

thread_local backoff_time_t backoff_time;


int main(int argc, char** argv) {
   double x1 = backoff_time.rand();
   double x2 = backoff_time.rand();
   double x3 = backoff_time.rand();
   double x4 = backoff_time.rand();
   return 0;
}

~

Bill Moore
la source
0

Voici quelques ressources que vous pouvez lire sur le générateur de nombres pseudo-aléatoires.

https://en.wikipedia.org/wiki/Pseudorandom_number_generator

Fondamentalement, les nombres aléatoires dans l'ordinateur ont besoin d'une graine (ce nombre peut être l'heure système actuelle).

Remplacer

std::default_random_engine generator;

Par

std::default_random_engine generator(<some seed number>);
Ngọc Khánh Nguyễn
la source
-3

Vous avez deux situations courantes. La première est que vous voulez des nombres aléatoires et que vous n'êtes pas trop préoccupé par la qualité ou la vitesse d'exécution. Dans ce cas, utilisez la macro suivante

#define uniform() (rand()/(RAND_MAX + 1.0))

cela vous donne p compris entre 0 et 1 - epsilon (à moins que RAND_MAX ne soit plus grand que la précision d'un double, mais vous en préoccupez lorsque vous y arrivez).

int x = (int) (uniforme () * N);

Donne maintenant un entier aléatoire sur 0 à N -1.

Si vous avez besoin d'autres distributions, vous devez transformer p. Ou parfois, il est plus facile d'appeler plusieurs fois uniform ().

Si vous voulez un comportement répétable, amorcez avec une constante, sinon amorcez avec un appel à time ().

Maintenant, si vous êtes préoccupé par la qualité ou les performances d'exécution, réécrivez uniform (). Mais sinon, ne touchez pas au code. Gardez toujours uniform () sur 0 à 1 moins epsilon. Vous pouvez maintenant envelopper la bibliothèque de nombres aléatoires C ++ pour créer un meilleur uniform (), mais c'est une sorte d'option de niveau moyen. Si les caractéristiques du RNG vous préoccupent, il vaut également la peine d'investir un peu de temps pour comprendre le fonctionnement des méthodes sous-jacentes, puis de fournir une. Vous avez donc un contrôle complet du code et vous pouvez garantir qu'avec la même graine, la séquence sera toujours exactement la même, quelle que soit la plate-forme ou la version de C ++ vers laquelle vous vous liez.

Malcolm McLean
la source
3
Sauf que ce n'est pas uniforme (0 à N-1). La raison est simple, supposons N = 100 et RAND_MAX = 32758. Il n'y a pas de moyen de mapper uniformément 32758 éléments (RAND_MAX) à 100 entrées. La méthode unique est de définir une limite sur 32000 et de réexécuter rand () si sort des limites
amchacon
1
Si N est 100, votre RNG doit être extrêmement bon pour pouvoir détecter l'écart par rapport à une distribution plate.
Malcolm McLean