Nombre aléatoire

Le problème exposé dans ce sujet a été résolu.

(Re)Bonjour,

j'ai suivi ce tuto sur les nombres aléatoires. J'aimerai obtenir un nombre aléatoire entre 0 et 1 (chaque nombre ayant la même probabilité) afin de le transformer ensuite dans l'intervalle voulu. J'ai fait ça:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
#include <random>
#include <chrono>
#include <cmath>

int main() {
    auto const seed = std::time(nullptr);          // génération de la graine
    std::default_random_engine engin { seed };     // générateur aléatoire
    std::normal_distribution<float> normal(0, 1);  // générateur de distribution
    double useless;
    std::cout << abs(static_cast<int>(modf(normal(engin), &useless) * 2)) << std::endl;       // génération
}

Mais c'est vraiment pas joli. Comment faut-il faire pour faire comme il faut ?

Salut,

Tu devrais utiliser std::uniform_real_distribution si tu veux que chaque nombre ait la même probabilité.

Normalement ton code ne marche pas. Déjà tu as une distribution normale (de float qui plus est, pourquoi ne pas utiliser double ?) ce qui n'est pas ce que tu veux si tu veux que tous les nombres soient équiprobables. Ton hack avec modf ne règle rien, si on fait quelques tests tu verras que la distribution n'est pas uniforme malgré tout (essaie de faire des stats sur quelques milliers de tests puis utilise un diagramme en bâton).

En plus au dernier moment tu convertis en int (pourquoi ?) et tu prends la valeur absolue. À ce stade tu ne devrais obtenir que des 0 ou des 1 et même pas de façon uniforme.

Preuve:

Avec :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
#include <random>
#include <chrono>
#include <cmath>

int main() {
    auto const seed = std::time(nullptr);          // génération de la graine
    std::default_random_engine engin { seed };     // générateur aléatoire
    std::normal_distribution<float> normal(0, 1);  // générateur de distribution
    int tab[2] = {0, 0};
    for (int i = 0; i < 1000; ++i) {
        double useless;
        tab[abs(static_cast<int>(modf(normal(engin), &useless) * 2))] += 1;       // génération
    }
    std::cout << tab[0] << " " << tab[1] << "\n";
}

J'obtiens :

1
609 391

comme sortie. Même si de légers écarts sont normaux, là c'est carrément problématique.

Enfin une précision : tu n'auras jamais chaque nombre avec une probabilité uniforme car tu ne peux pas représenter tous les réels en machine. De toute façon tirer un réel au hasard de façon uniforme ne veut pas dire grand chose, car personne ne sait faire ça (même en théorie).

+2 -0

Je suis supposé obtenir un nombre entre 0 et 1. En le multipliant par 2 et en le castant un int ça doit me faire un entier parmi 0 et 1. J'ai du encore gourer quelque part… En fait je n'ai pas bien compris ce que renvoient les différentes étapes. Mais bon la distribution normale c'était clairement pas ça.

Si tu veux soit 0 soit 1 et rien d'autre, pourquoi tu ne prends pas une distribution uniforme discrète ? c'est beaucoup plus simple non ?

Sinon si tu tiens à tirer d'abord un réel entre 0 et 1, le plus simple pour le convertir en soit 0 soit 1, c'est de trancher sur <0.5.

+1 -0

Si tu veux soit 0 soit 1 et rien d'autre, pourquoi tu ne prends pas une distribution uniforme discrète ? c'est beaucoup plus simple non ?

Là tu fais engin()%2 ou, si tu veux te faire passer pour un hacker-de-la-mort-qui-tue-qui-domine-le-bitwise tu fais engin()&1 ce qui est rigoureusement identique (pour ton compilo) puisque ça test si le premier bit (le bit dit de parité) est ou non activé.

En fait je n'ai pas bien compris ce que renvoient les différentes étapes.

Allez c'est parti :

  • normal(engin) renvoie un nombre issu d'une distribution normale centrée réduite, càd que les nombres valent 0 en moyenne (réparti de façon symétrique autour du 0) et s'accumulent pour la plupart d'entre eux entre -1 et +1, mais il n'y a potentiellement aucune limite dans les deux directions (par exemple il peut retourner +5 et -5, ou +42 et -42) la probabilité est juste de plus en plus faible (elle décroît exponentiellement) au fur et à mesure qu'on s'éloigne de 0
  • modf(normal(engin), &useless) tu retournes la partie réelle du nombre précédent. C'est à dire un nombre entre -1 (exclu) et 1 (exclu). useless contient la partie entière. Notons que la distribution n'est toujours pas uniforme car dans la loi normale 0.1 est plus représenté que 0.9, que 1.1 est plus représenté que 1.9, que 2.1 est plus représenté que 2.9, etc… en conséquence modf retournera plus souvent 0.1 que 0.9
  • modf(normal(engin), &useless) * 2 tu multiplies par deux. Donc tes nombres sont entre -2 (exclu) et +2 (exclu) et une fois encore les nombres proches de 0 sont sur-représentés.
  • static_cast<int>(modf(normal(engin), &useless) * 2) tu prends la partie entière. Les nombres entre -2 et -1 sont ramenés à -1, ceux entre -1 et +1 sont ramenés à 0, et ceux entre +1 et +2 sont ramenés à 1. Pour les mêmes raisons que précédemment, 0 est sur-présenté par rapport à +1 et -1. À présent grosse différence par rapport à précédemment : tu avais des intervalles du type ]-2;+2[ mais maintenant ce sont des ensembles discrets {-1, 0, 1}.
  • abs(static_cast<int>(modf(normal(engin), &useless) * 2)) enfin la valeur absolue. Ton ensemble est maintenant {0, 1} mais 0 reste sur-représenté.
+1 -0

En fait je n'ai pas bien compris ce que renvoient les différentes étapes.

Je parlais des étapes du tuto, vu que l'horrible ligne abs(static_cast<int>(modf(normal(engin), &useless) * 2)) c'est moi qui l'ai pondu. En tout cas merci à vous tous, c'est beaucoup plus clair maintenant.

J'ai voulu écrire une classe qui me produit des nombres aléatoires à la demande, malheureusement j'ai une erreur de compilation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#ifndef RANDOM_HPP
#define RANDOM_HPP 

#include <random>
#include <chrono>

class Random
{
    public:
        Random(std::time_t seed);
        int cointoss();
        int randomInt(int limit);

    private:
        std::default_random_engine gen;
        std::uniform_real_distribution<double> distribution{0.0, 1.0};
        // pourquoi pas des parenthèses ?
};

#endif /* RANDOM_HPP */
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "Random.hpp"

Random::Random(std::time_t seed)
    : gen(seed)
{}

int Random::cointoss()
{
    return (distribution(gen) < 0.5 ? 0 : 1);
}

int Random::randomInt(int limit)
{
    return static_cast<int>(distribution(gen) * limit);
}
1
2
3
sources/Random.cpp:3:18: error: expected ‘)’ before ‘seed’
 Random(std::time seed)
                  ^
+0 -0

En fait je n'ai pas bien compris ce que renvoient les différentes étapes.

Je parlais des étapes du tuto

matthieuj

Pourtant, chaque étape est expliquée dans le tuto (generation de la graine, generateur déterministe, distribution statistique).

Peux tu expliquer ce que tu n'as pas compris ?

+0 -0

Je crois que le problème vient du fait que j'ai lu le tuto une première fois en entier, et quand j'ai voulu l'utiliser plus tard je ne l'ai pas relu. En particulier je ne savais pas ce que renvoyais std::normal_distribution<float> normal(0, 1); Et je ne me suis pas plongé dans la documentation, mea culpa.

Edit: pour le problème de la classe que j'ai créé, j'ai finalement réussi à compiler, mais je ne comprends toujours pas les subtilités du pourquoi je ne peux pas utiliser de parenthèses pour initialiser distribution.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte