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


150

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

#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érera un nombre aléatoire différent à chaque fois, pas le même 5 fois de suite. Y a-t-il une commande qui fera cela?


56
Mis à part les problèmes de distribution, gardez à l'esprit qu'avec des nombres aléatoires, il est possible d'obtenir le même résultat plusieurs fois de suite. Si on vous garantissait de ne pas obtenir le même nombre deux fois de suite, les résultats ne seraient pas vraiment aléatoires, n'est-ce pas?
cdhowie

5
Qu'est-ce qui vous fait penser que ces chiffres ne sont pas aléatoires? Jetez un dé pour de vrai et vous pourriez très bien obtenir ce résultat. S'il était garanti qu'ils soient différents entre chaque lancer, ce ne serait pas vraiment aléatoire.
mattjgalloway

2
Lisez également eternallyconfuzzled.com/arts/jsw_art_rand.aspx pourquoi l'utilisation de l'opérateur module n'est pas souvent une bonne idée.
Benjamin Bannier

4
Vous vous méprenez sur bien plus que ce que l'on peut insérer dans un commentaire ou même une réponse. Vous devez vous renseigner, de manière indépendante, sur les générateurs de nombres pseudo-aléatoires, sur les graines, sur l'importance de choisir une graine vraiment aléatoire et sur les distributions uniformes.
Kerrek SB

20
Lorsque vous semez avec le temps. Cela signifie également que si vous exécutez votre programme plus d'une fois par seconde, vous obtiendrez le même numéro.
Martin York

Réponses:


79

Le problème le plus fondamental de votre application de test est que vous appelez srandune fois, puis randune fois et que vous quittez.

Le but de la srandfonction 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 à sranddans deux applications différentes (avec le même srand/ randimplémentation), vous obtiendrez exactement la même séquence de rand()valeurs lues par la suite dans les deux applications.

Cependant, dans votre exemple d'application, la séquence pseudo-aléatoire se compose d'un seul élément - le premier élément d'une séquence pseudo-aléatoire générée à partir de la valeur de départ égale à l'heure de secondprécision actuelle. Qu'attendez-vous alors de la sortie?

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

En fait, vous devriez appeler srand(seed)une fois, puis appeler rand() plusieurs fois et analyser cette séquence - elle devrait paraître aléatoire.

ÉDITER:

Oh je comprends. Apparemment, la description verbale ne suffit pas (peut-être la barrière de la langue ou quelque chose du genre ... :)).

D'ACCORD. Exemple de code C à l'ancienne basé sur les mêmes srand()/rand()/time()fonctions que celles utilisées 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;
}

^^^ CETTE séquence d'une seule exécution du programme est censée paraître aléatoire.

EDIT2:

Lorsque vous utilisez une bibliothèque standard C ou C ++, il est important de comprendre qu'à partir de maintenant, il n'y a pas une seule fonction ou classe standard produisant des données réellement aléatoires de manière définitive (garantie par le standard). Le seul outil standard qui aborde ce problème est std :: random_device qui malheureusement ne fournit toujours pas de garanties d'aléa réel.

En fonction de la nature de l'application, vous devez d'abord décider si vous avez vraiment besoin de données vraiment aléatoires (imprévisibles). Le cas notable où vous avez certainement besoin d'un vrai hasard est la sécurité de l'information - 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 distinct.

Dans la plupart des cas, le générateur de nombres pseudo-aléatoires est suffisant - 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 requise - par exemple, dans les jeux, vous pouvez choisir de générer exactement les mêmes cartes au moment de l'exécution pour éviter de stocker beaucoup de données.

La question originale et la multitude récurrente de questions identiques / similaires (et même de nombreuses «réponses» erronées à celles-ci) 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 dans le premier endroit ET pour réaliser que les générateurs de nombres pseudo-aléatoires ne sont PAS utilisés de la même manière que vous pourriez utiliser de vrais générateurs de nombres aléatoires.

Intuitivement lorsque vous demandez un nombre aléatoire - le résultat retourné ne devrait pas dépendre des valeurs précédemment renvoyées et ne devrait pas dépendre si quelqu'un a demandé quelque chose avant et ne devrait pas dépendre à quel moment et par quel processus et sur quel ordinateur et de quel générateur et dans quelle galaxie il a été demandé. C'est ce que signifie le mot «aléatoire» après tout - être imprévisible et indépendant de quoi que ce soit - sinon ce n'est plus aléatoire, non? Avec cette intuition, il est naturel de rechercher sur le Web des sorts magiques à lancer pour obtenir un tel nombre aléatoire dans n'importe quel contexte possible.

^^^ CE type d'attentes intuitives EST TRÈS FAUX et nuisibles dans tous les cas impliquant des générateurs de nombres pseudo-aléatoires - bien qu'ils soient raisonnables pour de vrais nombres aléatoires.

Bien que la notion significative de "nombre aléatoire" existe, il n'y a pas de "nombre pseudo-aléatoire". Un générateur de nombres pseudo-aléatoires produit en fait une séquence de nombres pseudo-aléatoires .

Lorsque les experts parlent de la qualité du 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 pouvez produire une mauvaise séquence résultante - bien qu'ils génèrent de bonnes séquences chacune séparément (ces deux bonnes séquences peuvent simplement se corréler l'une à l'autre et donc se combiner mal).

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

Spécifiquement rand()/ srand(s)paire de fonctions fournit une séquence de nombres pseudo-aléatoires singulière par processus non thread-safe (!) Générée avec un algorithme défini par l'implémentation. La fonction rand()produit des valeurs dans la plage [0, RAND_MAX].

Citation de la norme C11:

La srandfonction utilise l'argument comme valeur de départ pour une nouvelle séquence de nombres pseudo-aléatoires à renvoyer par les appels ultérieurs à rand. Si srandest alors appelé avec la même valeur de départ, la séquence de nombres pseudo-aléatoires doit être répétée. Si randest appelé avant que des appels srandaient été effectués, la même séquence doit être générée que lors du srandpremier appel avec une valeur de départ de 1.

Beaucoup de gens s'attendent raisonnablement à ce que rand()cela produise une séquence de nombres semi-indépendants uniformément distribués dans l'intervalle 0de RAND_MAX. Eh bien, il devrait très certainement (sinon c'est inutile) mais malheureusement non seulement la norme ne l'exige pas - il existe même une clause de non-responsabilité explicite qui déclare "il n'y a aucune garantie quant à la qualité de la séquence aléatoire produite" . Dans certains cas historiques rand/ la srandmise en œuvre était en effet de très mauvaise qualité. Même si dans les implémentations modernes, il est très probablement assez bon - mais la confiance est brisée et pas facile à récupérer. Outre sa nature non thread-safe, son utilisation en toute sécurité dans les applications multi-threads est délicate et limitée (toujours possible - vous pouvez simplement les utiliser à partir d'un thread dédié).

Le nouveau modèle de classe std :: mersenne_twister_engine <> (et ses typedefs pratiques - std::mt19937/ std::mt19937_64avec une bonne combinaison de paramètres de modèle) fournit un générateur de nombres pseudo-aléatoires par objet 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 une bibliothèque standard compatible C ++ 11. L'avantage de cette classe est sa séquence de sortie de haute qualité prévisible et sa cohérence totale entre les implémentations.

Il y a aussi plus de moteurs PRNG définis dans le standard C ++ 11 - std :: linear_congruential_engine <> (historiquement utilisé comme srand/randalgorithme de qualité équitable dans certaines implémentations de bibliothèques standard C) et std :: subtract_with_carry_engine <> . Ils génèrent également des séquences de sortie par objet entièrement définies dépendant des paramètres.

Exemple de remplacement moderne C ++ 11 du 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;
}

J'ai posé une question similaire dans le lien ici mais je n'ai toujours pas trouvé de réponse claire pour le moment. Pouvez-vous s'il vous plaît démontrer "En fait, vous devriez appeler srand (seed) une fois et ensuite appeler rand ()" avec des codes parce que j'ai déjà fait ce que vous dites mais cela ne fonctionne pas correctement.
bashburak

2
@bashburak Il semble que vous ayez totalement manqué le but de cette réponse. Pourquoi exactement avez-vous coupé mon devis? J'ai dit dans ma réponse littéralement "En fait, vous devriez appeler srand (seed) une fois, puis appeler rand () plusieurs fois et analyser cette séquence - cela devrait paraître aléatoire." Avez-vous remarqué que vous devriez appeler rand () plusieurs fois après un seul appel srand (...)? Votre question dans votre lien est une copie exacte de cette question avec exactement le même malentendu.
Serge Dundich

C'est une vieille réponse, mais elle apparaît lorsque vous recherchez sur Google "Génération de nombres aléatoires C ++". C'est un mauvais conseil pour les programmeurs C ++, car il vous conseille d'utiliser rand()et srand(). Pouvez-vous le mettre à jour?
Yakk - Adam Nevraumont le

@ Yakk-AdamNevraumont Il ne conseille pas réellement d'utiliser rand()et srand(). En fait, il répond simplement à la question avec une description fournie. Il est évident d'après la description (qui utilise rand/ srand) que les concepts de base de la génération de nombres pseudo-aléatoires doivent être expliqués - comme la signification même de la séquence pseudo-aléatoire et de sa graine. Je suis en train de faire exactement cela et utiliser le plus simple et familier rand/ srandcombinaison. Ce qui est drôle, c'est que certaines autres réponses - même avec une très grande cote - souffrent des mêmes malentendus que l'auteur de la question.
Serge Dundich le

@ Yakk-AdamNevraumont J'ai suivi votre conseil et modifié ma réponse avec quelques informations sur les derniers ajouts C ++. Bien que je considère cela un peu hors sujet - mais votre suggestion ainsi que d'autres réponses indiquent que les std::rand/std::srandfonctionnalités de la bibliothèque C ++ comme std::random_device<>, std :: mersenne_twister_engine <> et la multitude de distributions aléatoires nécessitent quelques explications.
Serge Dundich le

216

L'utilisation de modulo peut introduire un biais dans les nombres aléatoires, en fonction du 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étés dans une séquence aléatoire.

Essayez quelques fonctionnalités 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. Ce qui précède n'est pas le seul moyen de le faire, mais c'est un moyen.


7
La quantité de biais introduite par l'utilisation %6est extrêmement faible. Peut-être significatif si vous écrivez un jeu de craps à utiliser à Las Vegas, mais sans conséquence dans presque tous les autres contextes.
Hot Licks

9
HotLicks: d'accord, mais si vous utilisez une version de C ++ qui prend en charge random_deviceet mt19937déjà, il n'y a littéralement aucune raison de ne pas tout faire et d'utiliser la norme uniform_int_distributionaussi.
Quuxplusone

4
Tous les programmeurs devraient conseiller aux gens d'éviter le modulo comme la peste car il utilise la division et cela coûte des centaines de cycles d'horloge et peut gâcher le timing de votre application et / ou brûler beaucoup d'énergie de la batterie.

3
Est-ce que rng signifie "range"?
Christoffer

4
@ ChristofferHjärtström: Il est pour r andom n umber g enerator.
Cornstalks

11

Si vous utilisez des bibliothèques boost, 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;
    }
}

Où la fonction current_time_nanoseconds()donne l'heure actuelle en nanosecondes qui est utilisée comme graine.


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

1
Maintenant que nous avons aléatoire dans le cadre de la norme, je déconseillerais l'utilisation de la version boost à moins que vous n'utilisiez un compilateur vraiment ancien.
Martin York le

9
#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


Quelle est la différence avec le code de l'auteur de la question? (Sauf que vous n'utilisez pas %6.) Et si vous avez décidé d'utiliser l' std::randAPI C ++ de la randfonction de bibliothèque C alors pourquoi ne pas utiliser std::timeet std::srandpour des raisons de cohérence de style C ++?
Serge Dundich

4

Peut obtenir le Randomercode de classe complet pour générer des nombres aléatoires à partir d'ici!

Si vous avez besoin de nombres aléatoires dans différentes parties du projet, vous pouvez créer une classe distincte Randomerpour incorporer tout randomce qui s'y trouve.

Quelque chose comme ca:

class Randomer {
    // random seed by default
    std::mt19937 gen_;
    std::uniform_int_distribution<size_t> dist_;

public:
    /*  ... some convenient ctors ... */ 

    Randomer(size_t min, size_t max, unsigned int seed = std::random_device{}())
        : gen_{seed}, dist_{min, max} {
    }

    // if you want predictable numbers
    void SetSeed(unsigned int seed) {
        gen_.seed(seed);
    }

    size_t operator()() {
        return dist_(gen_);
    }
};

Une telle classe serait utile plus tard:

int main() {
    Randomer randomer{0, 10};
    std::cout << randomer() << "\n";
}

Vous pouvez consulter ce lien comme exemple sur la manière dont j'utilise une telle Randomerclasse pour générer des chaînes aléatoires. Vous pouvez également utiliser Randomersi vous le souhaitez.


Ne voudriez-vous pas réutiliser le générateur pour tous vos objets Randomer? D'autant plus qu'il est relativement coûteux de créer une initialisation et de maintenir son état.
Martin York le

4

Chaque fois que vous effectuez une recherche Web de base random number generationdans le langage de programmation C ++, cette question est généralement la première à apparaître! Je veux jeter mon chapeau dans le ring pour, espérons-le, 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!

Les bases

La génération de nombres pseudo-aléatoires implique l'utilisation d'un algorithme déterministe qui produit une séquence de nombres dont les propriétés ressemblent approximativement à des nombres aléatoires . Je dis à peu près ressembler , car le vrai hasard est un mystère assez insaisissable en mathématiques et en informatique. Par conséquent, pourquoi le terme pseudo-aléatoire est utilisé pour être plus pédantiquement correct!

Avant de pouvoir utiliser réellement un PRNG, c'est-à-dire, pseudo-random number generatorvous devez fournir à l'algorithme une valeur initiale souvent appelée aussi la graine . Cependant, la graine ne doit être définie qu'une seule fois avant d' utiliser 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 amorce suffisante au PRNG!

L'ancienne voie C

La bibliothèque standard rétrocompatible de C que C ++ a, utilise ce qu'on appelle un générateur congruentiel linéaire trouvé dans le cstdlibfichier 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 le modulo operator '%'. Ce qui suit est l'utilisation courante de ce PRNG, en ce qui concerne la question initiale 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'usage courant du PRNG de C contient toute une série de problèmes tels que:

  1. L'interface globale de std::rand()n'est pas très intuitive pour la génération appropriée de nombres pseudo-aléatoires entre une plage donnée, par exemple, la production de nombres entre [1, 6] comme le souhaitait @Predictability.
  2. L'utilisation courante de std::rand()élimine la possibilité d'une distribution uniforme de nombres pseudo-aléatoires, en raison du principe de Pigeonhole .
  3. La voie commune std::rand()s'ensemencée par le plan std::srand( ( unsigned int )std::time( nullptr ) )technique n'est pas correct, car time_test considéré comme un type restreint . Par conséquent, la conversion de time_ten 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 Utilisation de rand () (C / C ++): Conseils pour la fonction rand () de la bibliothèque standard C !

La méthode C ++ standard

Depuis la publication de la norme ISO / CEI 14882: 2011, c'est-à-dire C ++ 11, la randombibliothèque est en dehors du langage de programmation C ++ depuis un certain temps. Cette bibliothèque est équipée de plusieurs PRNGs, et différents types de distribution tels que: distribution uniforme , la distribution normale , distribution binomiale , etc. L'exemple de code source suivant illustre une utilisation très basique de la randombibliothèque, en ce qui concerne @ question initiale Prévisibilité:

#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 moteur Mersenne Twister 32 bits , avec une distribution uniforme des valeurs entières , a été utilisé dans l'exemple ci-dessus. (Le nom du moteur dans le code source semble bizarre, car son nom vient de sa période de 2 ^ 19937-1). L'exemple utilise également std::random_devicepour amorcer le moteur, qui obtient sa valeur du système d'exploitation (si vous utilisez un système Linux, std::random_devicerenvoie une valeur à partir de /dev/urandom).

Prenez note que vous n'avez pas besoin d'utiliser std::random_devicepour semer un moteur . Vous pouvez utiliser des constantes ou même la chronobibliothèque! Vous n'avez pas non plus besoin d'utiliser la version 32 bits du std::mt19937moteur, il existe d' autres options ! Pour plus d'informations sur les capacités de la randombibliothèque, veuillez consulter cplusplus.com

Dans l'ensemble, les programmeurs C ++ ne devraient plus utiliser std::rand(), non pas parce que c'est mauvais , mais parce que la norme actuelle offre de meilleures alternatives, plus simples et plus fiables . Espérons que beaucoup d'entre vous trouvent cela utile, en particulier ceux d'entre vous qui ont récemment effectué une recherche sur le Web generating random numbers in c++!


3

Générez un nombre aléatoire différent à chaque fois, pas le même six fois de suite.

Scénario de cas d'utilisation

J'ai comparé le problème de la prévisibilité à un sac de six morceaux de papier, chacun avec une valeur de 0 à 5 écrite dessus. Un morceau de papier est tiré du sac chaque fois qu'une nouvelle valeur est requise. Si le sac est vide, les numéros sont remis dans le sac.

... à partir de là, je peux créer une sorte d'algorithme.

Algorithme

Un sac est généralement un Collection. J'ai choisi un bool[](autrement connu sous le nom de tableau booléen, plan de bits ou carte de bits) pour prendre le rôle du sac.

La raison pour laquelle j'ai choisi un bool[]est que l'index de chaque article est déjà la valeur de chaque morceau de papier. Si les papiers exigeaient autre chose, j'aurais utilisé un Dictionary<string, bool>à la place. La valeur booléenne est utilisée pour savoir si le numéro a déjà été dessiné ou non.

Un compteur appelé RemainingNumberCountest initialisé à 5ce compte à rebours comme un nombre aléatoire est choisi. Cela nous évite d'avoir à compter le nombre de morceaux de papier qui restent chaque fois que nous souhaitons dessiner un nouveau nombre.

Pour sélectionner la valeur aléatoire suivante, j'utilise a for..looppour parcourir le sac d'index, et un compteur pour décompter quand un indexest falseappelé NumberOfMoves.

NumberOfMovesest utilisé pour choisir le prochain numéro disponible. NumberOfMovesest d'abord défini comme une valeur aléatoire entre 0et 5, car il y a 0..5 étapes disponibles que nous pouvons faire à travers le sac. À l'itération suivante, la valeur NumberOfMovesest définie au hasard entre 0et 4, car il y a maintenant 0..4 étapes que nous pouvons faire à travers le sac. Au fur et à mesure que les nombres sont utilisés, les nombres disponibles diminuent, nous utilisons donc rand() % (RemainingNumberCount + 1)à la place pour calculer la valeur suivante pour NumberOfMoves.

Lorsque le NumberOfMovescompteur atteint zéro, le for..loopdevrait comme suit:

  1. Définissez la valeur actuelle pour qu'elle soit la même que celle for..loopde l'index.
  2. Définissez tous les numéros du sac sur false.
  3. Pause du for..loop.

Code

Le code de la solution ci-dessus est le suivant:

(placez les trois blocs suivants dans le fichier principal .cpp l'un après l'autre)

#include "stdafx.h"
#include <ctime> 
#include <iostream>
#include <string>

class RandomBag {
public:
    int Value = -1;

    RandomBag() {
        ResetBag();

    }

    void NextValue() {
        int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);

        int NumberOfMoves = rand() % (RemainingNumberCount + 1);

        for (int i = 0; i < BagOfNumbersLength; i++)            
            if (BagOfNumbers[i] == 0) {
                NumberOfMoves--;

                if (NumberOfMoves == -1)
                {
                    Value = i;

                    BagOfNumbers[i] = 1;

                    break;

                }

            }



        if (RemainingNumberCount == 0) {
            RemainingNumberCount = 5;

            ResetBag();

        }
        else            
            RemainingNumberCount--; 

    }

    std::string ToString() {
        return std::to_string(Value);

    }

private:
    bool BagOfNumbers[6]; 

    int RemainingNumberCount;

    int NumberOfMoves;

    void ResetBag() {
        RemainingNumberCount = 5;

        NumberOfMoves = rand() % 6;

        int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);

        for (int i = 0; i < BagOfNumbersLength; i++)            
            BagOfNumbers[i] = 0;

    }

};

Une classe de console

Je crée cette classe Console car elle facilite la redirection de la sortie.

Ci-dessous dans le code ...

Console::WriteLine("The next value is " + randomBag.ToString());

... peut être remplacé par ...

std::cout << "The next value is " + randomBag.ToString() << std::endl; 

... et cette Consoleclasse peut être supprimée si vous le souhaitez.

class Console {
public:
    static void WriteLine(std::string s) {
        std::cout << s << std::endl;

    }

};

Méthode principale

Exemple d'utilisation comme suit:

int main() {
    srand((unsigned)time(0)); // Initialise random seed based on current time

    RandomBag randomBag;

    Console::WriteLine("First set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nSecond set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nThird set of six...\n");

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    randomBag.NextValue();

    Console::WriteLine("The next value is " + randomBag.ToString());

    Console::WriteLine("\nProcess complete.\n");

    system("pause");

}

Exemple de sortie

Lorsque j'ai exécuté le programme, j'ai obtenu la sortie suivante:

First set of six...

The next value is 2
The next value is 3
The next value is 4
The next value is 5
The next value is 0
The next value is 1

Second set of six...

The next value is 3
The next value is 4
The next value is 2
The next value is 0
The next value is 1
The next value is 5

Third set of six...

The next value is 4
The next value is 5
The next value is 2
The next value is 0
The next value is 3
The next value is 1

Process complete.

Press any key to continue . . .

Déclaration de clôture

Ce programme a été écrit en utilisant Visual Studio 2017 , et j'ai choisi d'en faire un Visual C++ Windows Console Applicationprojet en utilisant .Net 4.6.1.

Je ne fais rien de particulièrement spécial ici, donc le code devrait également fonctionner sur les versions antérieures de Visual Studio.


S'il s'agit de VS 2017, vous devez utiliser la version la plus récente de la bibliothèque standard: en.cppreference.com/w/cpp/numeric/random . Actuellement, cet exemple utilise les fonctions de bibliothèque aléatoire C et "Il n'y a aucune garantie quant à la qualité de la séquence aléatoire produite".
Robert Andrzejuk

2

Voici une solution. Créez une fonction qui renvoie le nombre aléatoire et placez-la en dehors de la fonction principale pour la rendre globale. J'espère que cela t'aides

#include <iostream>
#include <cstdlib>
#include <ctime>
int rollDie();
using std::cout;
int main (){
    srand((unsigned)time(0));
    int die1;
    int die2;
    for (int n=10; n>0; n--){
    die1 = rollDie();
    die2 = rollDie();
    cout << die1 << " + " << die2 << " = " << die1 + die2 << "\n";
}
system("pause");
return 0;
}
int rollDie(){
    return (rand()%6)+1;
}

2

Ce code produit des nombres aléatoires de nà m.

int random(int from, int to){
    return rand() % (to - from + 1) + from;
}

exemple:

int main(){
    srand(time(0));
    cout << random(0, 99) << "\n";
}

2
Cela ne répond pas vraiment à la question.
HolyBlackCat

1
Vous ne l'avez pas réparé. Le point de la question est que si vous exécutez le programme plusieurs fois par seconde, il génère les mêmes valeurs aléatoires. Votre code le fait aussi.
HolyBlackCat

1
@HolyBlackCat Je l'ai vérifié pour plusieurs courses, cela fonctionne. Avez-vous déjà ajouté srand(time(0))à la fonction principale random(n, m)?
Amir Fo

1
Vous devez ajouter srand(time(0))à la fonction principale et non à la boucle for ou à l'intérieur de l'implémentation de la fonction.
Amir Fo

1
J'ai copié votre code textuellement. L'avez-vous exécuté plusieurs fois par seconde ?
HolyBlackCat

1

pour chaque fichier RUN au hasard

size_t randomGenerator(size_t min, size_t max) {
    std::mt19937 rng;
    rng.seed(std::random_device()());
    //rng.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<std::mt19937::result_type> dist(min, max);

    return dist(rng);
}

1
Vous n'êtes pas censé créer le générateur plusieurs fois. Il maintient un tas d'états afin qu'il génère une séquence de nombres aléatoires qui a la distribution appropriée (pour le faire paraître aléatoire).
Martin York le

-2

Voici un générateur aléatoire simple avec env. probabilité égale de générer des valeurs positives et négatives autour de 0:

  int getNextRandom(const size_t lim) 
  {
        int nextRand = rand() % lim;
        int nextSign = rand() % lim;
        if (nextSign < lim / 2)
            return -nextRand;
        return nextRand;
  }


   int main()
   {
        srand(time(NULL));
        int r = getNextRandom(100);
        cout << r << endl;
        return 0;
   }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.