275 votes

Comment générer un nombre aléatoire en C++ ?

J'essaye de faire un jeu avec des dés, et j'ai besoin d'avoir des nombres aléatoires dedans (pour simuler les faces du dé. Je sais comment les faire entre 1 et 6). J'utilise

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

using namespace std;

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

ne fonctionne pas très bien, car lorsque j'exécute le programme plusieurs fois, voici le résultat que j'obtiens :

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

Je veux donc une commande qui génère un différents un numéro aléatoire à chaque fois, pas le même 5 fois de suite. Existe-t-il une commande qui permette de faire cela ?

329voto

Cornstalks Points 9261

L'utilisation de modulo peut introduire un biais dans les nombres aléatoires, selon le générateur de nombres aléatoires. Voir cette question pour plus d'informations. Bien sûr, il est parfaitement possible d'obtenir des nombres répétitifs dans une séquence aléatoire.

Essayez certaines fonctionnalités de C++11 pour une meilleure distribution :

#include <random>
#include <iostream>

int main()
{
    std::random_device dev;
    std::mt19937 rng(dev());
    std::uniform_int_distribution<std::mt19937::result_type> dist6(1,6); // distribution in range [1, 6]

    std::cout << dist6(rng) << std::endl;
}

Voir cette question/réponse pour plus d'informations sur les nombres aléatoires C++11. La méthode ci-dessus n'est pas la seule façon de procéder, mais c'en est une.

153voto

Serge Dundich Points 1682

Le problème le plus fondamental de votre application de test est que vous appelez srand une fois et ensuite appeler rand une fois et quitter.

Le but de srand est d'initialiser la séquence de nombres pseudo-aléatoires avec une graine aléatoire.

Cela signifie que si vous passez la même valeur a srand dans deux applications différentes (avec la même srand / rand ) alors vous obtiendrez exactement la même séquence de rand() valeurs lues ensuite dans les deux applications.

Cependant, dans votre exemple d'application, la séquence pseudo-aléatoire ne comprend qu'un seul élément - le premier élément d'une séquence pseudo-aléatoire générée à partir d'une graine égale à l'heure actuelle de 1 sec précision. Qu'espérez-vous voir à la sortie alors ?

Évidemment, lorsque vous exécutez une application à la même seconde, vous utilisez la même valeur d'ensemencement, et le résultat est donc le même, bien entendu (comme Martin York l'a déjà mentionné dans un commentaire sur la question).

En fait, vous devriez appeler srand(seed) une fois et ensuite appeler rand() plusieurs fois et analyser cette séquence - elle devrait avoir l'air aléatoire.

AMENDEMENT 1 - exemple de code :

OK, j'ai compris. Apparemment, une description verbale ne suffit pas (peut-être la barrière de la langue ou autre chose... :) ).

Exemple de code C à l'ancienne basé sur la même srand()/rand()/time() qui a été utilisé dans la question :

#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;
}

^^^ QUE d'une seule exécution du programme est censée être aléatoire.

Veuillez noter que je ne recommande pas d'utiliser rand / srand en production pour les raisons expliquées ci-dessous et je ne recommande absolument pas d'utiliser la fonction time comme une graine aléatoire pour les raisons qui, selon l'OMI, devraient déjà être assez évidentes. Ils sont très bien à des fins éducatives et pour illustrer le point parfois, mais pour toute utilisation sérieuse, ils sont pratiquement inutiles.

AMENDEMENT 2 - explication détaillée :

Il est important de comprendre qu'à l'heure actuelle, il y a aucun Les fonctions standard C ou C++ (fonctions ou classes de bibliothèque) produisant des données réellement aléatoires de manière définitive (c'est-à-dire garanties par la norme comme étant réellement aléatoires). La seule fonction standard qui s'approche de ce problème est std::random_device qui, malheureusement, ne garantit toujours pas un caractère aléatoire réel.

Selon la nature de l'application, vous devez d'abord décider si vous avez vraiment besoin de données véritablement aléatoires (imprévisibles). Cas notables alors que vous avez certainement besoin d'un véritable hasard est la sécurité des informations - par exemple, la génération de clés symétriques, de clés privées asymétriques, de valeurs de sel, de jetons de sécurité, etc.

Cependant, les nombres aléatoires de niveau de sécurité constituent une industrie distincte qui mérite un article séparé. Je les aborde brièvement dans cette réponse de la mienne.

Dans la plupart des cas Générateur de nombres pseudo-aléatoires est suffisante - par exemple pour les simulations scientifiques ou les jeux. Dans certains cas, une séquence pseudo-aléatoire définie de manière cohérente est même nécessaire - par exemple, dans les jeux, vous pouvez choisir de générer exactement les mêmes cartes en cours d'exécution pour éviter de stocker beaucoup de données dans votre pack d'installation.

La question initiale et la multitude de questions identiques/similaires (et même de nombreuses "réponses" erronées à ces questions) indiquent qu'il est avant tout important de distinguer les nombres aléatoires des nombres pseudo-aléatoires ET de comprendre ce qu'est une séquence de nombres pseudo-aléatoires en premier lieu ET de réaliser que les générateurs de nombres pseudo-aléatoires ne sont PAS utilisés de la même manière que les vrais générateurs de nombres aléatoires.

Intuitivement lorsque vous demandez un nombre aléatoire, le résultat renvoyé ne doit pas dépendre des valeurs renvoyées précédemment et ne doit pas dépendre si si quelqu'un a demandé quelque chose avant et ne devrait pas dépendre de l'heure à laquelle et par quel processus et sur quel ordinateur et à partir de quel générateur et dans quelle galaxie il a été demandé. C'est ce que le mot "aléatoire" signifie après tout - être imprévisible et indépendant de tout - sinon ce n'est plus du hasard, n'est-ce pas ? Avec cette intuition, il est naturel de chercher sur Internet des formules magiques à lancer pour obtenir un tel nombre aléatoire dans n'importe quel contexte. un tel nombre aléatoire dans n'importe quel contexte possible.

^^^ Ce genre d'attentes intuitives est très mauvais et nuisible. dans toutes les affaires impliquant Générateurs de nombres pseudo-aléatoires - bien qu'elle soit raisonnable pour les vrais nombres aléatoires.

Si la notion de "nombre aléatoire" existe (en quelque sorte), celle de "nombre pseudo-aléatoire" n'existe pas. A Générateur de nombres pseudo-aléatoires produit en fait un nombre pseudo-aléatoire séquence .

La séquence pseudo-aléatoire est en fait toujours déterministe (prédéterminé par son algorithme et ses paramètres initiaux) - c'est-à-dire qu'il n'y a en fait rien d'aléatoire.

Lorsque les experts parlent de la qualité des PRNG, ils parlent en fait des propriétés statistiques de la séquence générée (et de ses sous-séquences notables). Par exemple, si vous combinez deux PRNG de haute qualité en les utilisant tous les deux à tour de rôle, vous risquez de produire une mauvaise séquence résultante, bien qu'ils aient généré de bonnes séquences chacun de leur côté (ces deux bonnes séquences peuvent simplement se corréler l'une à l'autre et donc se combiner mal).

Plus précisément rand() / srand(s) Cette paire de fonctions fournit une séquence de nombres pseudo-aléatoires unique par processus et non sécurisée par les threads ( !), générée à l'aide d'un algorithme défini par l'implémentation. Fonction rand() produit des valeurs dans l'intervalle [0, RAND_MAX] .

Citation de la norme C11 (ISO/IEC 9899:2011) :

El srand utilise l'argument comme une graine pour une nouvelle séquence de pseudo-aléatoires qui seront retournés par les appels ultérieurs à la fonction rand . Si srand est ensuite appelé avec la même valeur de semence, la séquence de nombres pseudo-aléatoires est répétée. Si rand est appelé avant tout appels à srand ont été effectués, la même séquence est générée que lorsque srand est d'abord appelé avec une valeur d'amorçage de 1.

Beaucoup de gens s'attendent raisonnablement à ce que rand() produirait une séquence de nombres semi-indépendants uniformément distribués dans l'intervalle 0 a RAND_MAX . Il devrait certainement l'être (sinon il est inutile) mais malheureusement, non seulement la norme ne l'exige pas, mais il y a même une clause de non-responsabilité explicite qui stipule ce qui suit "il n'y a aucune garantie quant à la qualité de la séquence aléatoire produite" . Dans certains cas historiques rand / srand La mise en œuvre était en effet de très mauvaise qualité. Même si les implémentations modernes sont probablement assez bonnes, la confiance est rompue et il n'est pas facile de la rétablir. En outre, sa nature non thread-safe rend son utilisation sûre dans les applications multi-threads délicate et limitée (toujours possible - vous pouvez juste les utiliser depuis un thread dédié).

Nouveau modèle de classe std::mersenne_twister_engine<> (et ses typedefs de commodité - std::mt19937 / std::mt19937_64 avec une bonne combinaison de paramètres de modèle) fournit par objet générateur de nombres pseudo-aléatoires défini dans la norme C++11. Avec les mêmes paramètres de modèle et les mêmes paramètres d'initialisation, différents objets généreront exactement la même séquence de sortie par objet sur n'importe quel ordinateur dans n'importe quelle application construite avec la bibliothèque standard C++11. L'avantage de cette classe est la haute qualité prévisible de la séquence de sortie et la cohérence totale entre les implémentations.

Il existe également d'autres moteurs PRNG définis dans la norme C++11. std::moteur_congruentiel_linéaire<> (historiquement utilisé comme qualité équitable srand/rand dans certaines implémentations de la bibliothèque standard C) et std::subtract_with_carry_engine<> . Ils génèrent également des séquences de sortie par objet, entièrement définies et dépendantes des paramètres.

Exemple moderne de C++11 remplaçant le code C obsolète ci-dessus :

#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;
}

La version du code précédent qui utilise 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;
}

32voto

Chaque fois que vous faites une recherche basique sur le web pour random number generation dans le langage de programmation C++, cette question est généralement la première à se poser ! Je veux jeter mon chapeau dans l'arène pour espérer mieux clarifier le concept de génération de nombres pseudo-aléatoires en C++ pour les futurs codeurs qui chercheront inévitablement cette même question sur le web !

L'essentiel

La génération de nombres pseudo-aléatoires implique le processus d'utilisation d'un algorithme déterministe qui produit une séquence de nombres dont les propriétés ressemblent approximativement à nombres aléatoires . Je dis ressemblent approximativement à parce que le vrai hasard est plutôt mystère insaisissable en mathématiques et en informatique. D'où la raison pour laquelle le terme pseudo-aléatoire est utilisé pour être plus pédamment correct !

Avant que vous puissiez réellement utiliser un PRNG, c'est-à-dire, pseudo-random number generator vous devez fournir à l'algorithme une valeur initiale, souvent appelée "valeur de référence". graines . Toutefois, la semence ne doit être mise en place que une fois avant en utilisant l'algorithme lui-même !

/// Proper way!
seed( 1234 ) /// Seed set only once...
for( x in range( 0, 10) ):
  PRNG( seed ) /// Will work as expected

/// Wrong way!
for( x in rang( 0, 10 ) ):
  seed( 1234 ) /// Seed reset for ten iterations!
  PRNG( seed ) /// Output will be the same...

Ainsi, si vous voulez une bonne séquence de nombres, vous devez fournir une graine suffisante au PRNG !

L'ancienne méthode C

La bibliothèque standard du C, rétrocompatible, dont dispose le C++, utilise ce qu'on appelle une générateur de congruence linéaire trouvé dans le cstdlib fichier d'en-tête ! Ce PRNG fonctionne grâce à une fonction discontinue par morceaux qui utilise l'arithmétique modulaire, c'est-à-dire un algorithme rapide qui aime utiliser la fonction modulo operator '%' . Voici l'utilisation courante de ce PRNG, en rapport avec la question originale posée par @Predictability :

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

int main( void )
{
  int low_dist  = 1;
  int high_dist = 6;
  std::srand( ( unsigned int )std::time( nullptr ) );
  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << low_dist + std::rand() % ( high_dist - low_dist ) << std::endl;
  return 0;
}

L'utilisation commune du PRNG de C abrite une foule de problèmes tels que :

  1. L'interface globale de std::rand() n'est pas très intuitif pour la génération correcte de nombres pseudo-aléatoires entre une gamme donnée, par exemple, produire des nombres entre [1, 6] comme le voulait @Predictability.
  2. L'usage courant de std::rand() élimine la possibilité d'un distribution uniforme de nombres pseudo-aléatoires, à cause de la Le principe du pigeonnier .
  3. La méthode commune std::rand() est semé par std::srand( ( unsigned int )std::time( nullptr ) ) techniquement, ce n'est pas correct, car time_t est considéré comme un type restreint . Par conséquent, la conversion de time_t a unsigned int n'est pas garantie !

Pour des informations plus détaillées sur les problèmes généraux liés à l'utilisation du PRNG de C et sur la manière de les contourner, veuillez consulter le site suivant Utilisation de rand() (C/C++) : Conseils pour la fonction rand() de la bibliothèque standard du C !

La méthode standard du C++

Depuis la publication de la norme ISO/CEI 14882:2011, c'est-à-dire C++11, le système de gestion de l'information de l'entreprise a été modifié. random fait partie du langage de programmation C++ depuis un certain temps déjà. Cette bibliothèque est équipée de multiple PRNG, et différents types de distribution comme : distribution uniforme , distribution normale , distribution binomiale etc. L'exemple de code source suivant démontre une utilisation très basique de la fonction random bibliothèque, en ce qui concerne la question originale de @Predictability :

#include <iostream>
#include <cctype>
#include <random>

using u32    = uint_least32_t; 
using engine = std::mt19937;

int main( void )
{
  std::random_device os_seed;
  const u32 seed = os_seed();

  engine generator( seed );
  std::uniform_int_distribution< u32 > distribute( 1, 6 );

  for( int repetition = 0; repetition < 10; ++repetition )
    std::cout << distribute( generator ) << std::endl;
  return 0;
}

Le système 32 bits Twister de Mersenne moteur, avec un distribution uniforme de entier a été utilisé dans l'exemple ci-dessus. (Le nom du moteur dans le code source semble bizarre, car son nom provient de son période de 2^19937-1 ). L'exemple utilise également std::random_device pour ensemencer le moteur, qui obtient sa valeur du système d'exploitation (si vous utilisez un système Linux, alors std::random_device renvoie une valeur de /dev/urandom ).

Notez bien que vous n'êtes pas obligé d'utiliser std::random_device à la semence tout moteur . Vous pouvez utiliser constantes ou même le chrono bibliothèque ! Vous n'êtes pas non plus obligé d'utiliser la version 32 bits de la bibliothèque std::mt19937 moteur, il y a autres options ! Pour plus d'informations sur les capacités du random bibliothèque, veuillez vous référer à cplusplus.com

En somme, les programmeurs C++ ne devraient pas utiliser std::rand() plus, pas parce que son mauvais mais parce que la norme actuelle offre de meilleures solutions de rechange qui sont davantage direct y fiable . J'espère que vous serez nombreux à trouver cette information utile, en particulier ceux qui ont récemment effectué des recherches sur le web. generating random numbers in c++ !

12voto

madx Points 159

Si vous utilisez booster libs, vous pouvez obtenir un générateur aléatoire de cette manière :

#include <iostream>
#include <string>

// Used in randomization
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>

using namespace std;
using namespace boost;

int current_time_nanoseconds(){
    struct timespec tm;
    clock_gettime(CLOCK_REALTIME, &tm);
    return tm.tv_nsec;
}

int main (int argc, char* argv[]) {
    unsigned int dice_rolls = 12;
    random::mt19937 rng(current_time_nanoseconds());
    random::uniform_int_distribution<> six(1,6);

    for(unsigned int i=0; i<dice_rolls; i++){
        cout << six(rng) << endl;
    }
}

Lorsque la fonction current_time_nanoseconds() donne le temps actuel en nanosecondes qui est utilisé comme semence.


Voici une classe plus générale pour obtenir des entiers et des dates aléatoires dans une plage :

#include <iostream>
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/date_time/gregorian/gregorian.hpp"

using namespace std;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;

class Randomizer {
private:
    static const bool debug_mode = false;
    random::mt19937 rng_;

    // The private constructor so that the user can not directly instantiate
    Randomizer() {
        if(debug_mode==true){
            this->rng_ = random::mt19937();
        }else{
            this->rng_ = random::mt19937(current_time_nanoseconds());
        }
    };

    int current_time_nanoseconds(){
        struct timespec tm;
        clock_gettime(CLOCK_REALTIME, &tm);
        return tm.tv_nsec;
    }

    // C++ 03
    // ========
    // Dont forget to declare these two. You want to make sure they
    // are unacceptable otherwise you may accidentally get copies of
    // your singleton appearing.
    Randomizer(Randomizer const&);     // Don't Implement
    void operator=(Randomizer const&); // Don't implement

public:
    static Randomizer& get_instance(){
        // The only instance of the class is created at the first call get_instance ()
        // and will be destroyed only when the program exits
        static Randomizer instance;
        return instance;
    }
    bool method() { return true; };

    int rand(unsigned int floor, unsigned int ceil){
        random::uniform_int_distribution<> rand_ = random::uniform_int_distribution<> (floor,ceil);
        return (rand_(rng_));
    }

    // Is not considering the millisecons
    time_duration rand_time_duration(){
        boost::posix_time::time_duration floor(0, 0, 0, 0);
        boost::posix_time::time_duration ceil(23, 59, 59, 0);
        unsigned int rand_seconds = rand(floor.total_seconds(), ceil.total_seconds());
        return seconds(rand_seconds);
    }

    date rand_date_from_epoch_to_now(){
        date now = second_clock::local_time().date();
        return rand_date_from_epoch_to_ceil(now);
    }

    date rand_date_from_epoch_to_ceil(date ceil_date){
        date epoch = ptime(date(1970,1,1)).date();
        return rand_date_in_interval(epoch, ceil_date);
    }

    date rand_date_in_interval(date floor_date, date ceil_date){
        return rand_ptime_in_interval(ptime(floor_date), ptime(ceil_date)).date();
    }

    ptime rand_ptime_from_epoch_to_now(){
        ptime now = second_clock::local_time();
        return rand_ptime_from_epoch_to_ceil(now);
    }

    ptime rand_ptime_from_epoch_to_ceil(ptime ceil_date){
        ptime epoch = ptime(date(1970,1,1));
        return rand_ptime_in_interval(epoch, ceil_date);
    }

    ptime rand_ptime_in_interval(ptime floor_date, ptime ceil_date){
        time_duration const diff = ceil_date - floor_date;
        long long gap_seconds = diff.total_seconds();
        long long step_seconds = Randomizer::get_instance().rand(0, gap_seconds);
        return floor_date + seconds(step_seconds);
    }
};

10voto

arley Points 473
#include <iostream>
#include <cstdlib>
#include <ctime>

int main() {
    srand(time(NULL));
    int random_number = std::rand(); // rand() return a number between 0 and RAND_MAX
    std::cout << random_number;
    return 0;
}

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X