web-dev-qa-db-fra.com

Comment générer un double aléatoire réparti uniformément entre 0 et 1 à partir de C ++?

Comment générer un double aléatoire réparti uniformément entre 0 et 1 à partir de C++?

Bien sûr, je peux penser à quelques réponses, mais j'aimerais savoir quelle est la pratique standard, pour avoir:

  • Bonne conformité aux normes
  • Bon caractère aléatoire
  • Bonne vitesse

(la vitesse est plus importante que le hasard pour mon application).

Merci beaucoup!

PS: Au cas où cela compte, mes plateformes cibles sont Linux et Windows.

58
static_rtti

En C++ 11 et C++ 14, nous avons de bien meilleures options avec le en-tête aléatoire . La présentation Rand () considérée comme nuisible par Stephan T. Lavavej explique pourquoi nous devrions éviter l'utilisation de Rand() en C++ en faveur de random header et N3924: Discouraging Rand () en C++ 14 renforce encore ce point.

L'exemple ci-dessous est une version modifiée de l'exemple de code sur le site cppreference et utilise le moteur std :: mersenne_twister_engine et le moteur std :: uniform_real_distribution qui génère des nombres dans le [0,1) Plage (voir en direct):

#include <iostream>
#include <iomanip>
#include <map>
#include <random>

int main()
{
    std::random_device rd;


    std::mt19937 e2(rd());

    std::uniform_real_distribution<> dist(0, 1);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::round(dist(e2))];
    }

    for (auto p : hist) {
        std::cout << std::fixed << std::setprecision(1) << std::setw(2)
                  << p.first << ' ' << std::string(p.second/200, '*') << '\n';
    }
}

la sortie sera similaire à la suivante:

0 ************************
1 *************************

Étant donné que le message mentionnait que la vitesse était importante, nous devrions considérer la section cppreference qui décrit les différents moteurs de nombres aléatoires ( c'est moi qui souligne):

Le choix du moteur à utiliser implique un certain nombre de compromis *: le ** moteur congruentiel linéaire est modérément rapide et a une très petite exigence de stockage pour l'état . Les générateurs Fibonacci retardés sont très rapides même sur des processeurs sans ensembles d'instructions arithmétiques avancées , au détriment d'un stockage d'état plus important et de caractéristiques spectrales parfois moins souhaitables. Le twister de Mersenne est plus lent et a des exigences de stockage d'état plus importantes mais avec les bons paramètres a la séquence non répétitive la plus longue avec les caractéristiques spectrales les plus souhaitables (pour un définition donnée de souhaitable).

Donc, s'il y a un désir pour un générateur plus rapide peut-être ranlux24_base ou ranlux48_base sont de meilleurs choix plutôt que mt19937 .

Rand()

Si vous avez forcé à utiliser Rand() alors la FAQ C pour un guide sur Comment puis-je générer des nombres aléatoires à virgule flottante? , nous donne un exemple similaire à celui-ci pour générer un sur l'intervalle [0,1):

#include <stdlib.h>

double randZeroToOne()
{
    return Rand() / (Rand_MAX + 1.);
}

et pour générer un nombre aléatoire dans la plage de [M,N):

double randMToN(double M, double N)
{
    return M + (Rand() / ( Rand_MAX / (N-M) ) ) ;  
}
31
Shafik Yaghmour

Une solution old school comme:

double X=((double)Rand()/(double)Rand_MAX);

Devrait répondre à tous vos critères (portable, standard et rapide). évidemment, le nombre aléatoire généré doit être défini, la procédure standard est quelque chose comme:

srand((unsigned)time(NULL));
55
Elemental

La random_real class de la Boost random library est ce dont vous avez besoin.

6
Vijay Mathew

Voici comment vous le feriez si vous utilisiez C++ TR1 .

5
John D. Cook

Si la vitesse est votre principale préoccupation, je choisirais simplement

double r = (double)Rand() / (double)Rand_MAX;
3
suszterpatt

La bibliothèque standard C++ 11 contient un cadre décent et un couple de générateurs réparables, ce qui est parfaitement suffisant pour les devoirs et l'utilisation au pied levé.

Cependant, pour le code de production, vous devez savoir exactement quelles sont les propriétés spécifiques des différents générateurs avant de les utiliser, car tous ont leurs mises en garde. De plus, aucun d'entre eux ne réussit les tests standard pour les PRNG comme TestU01, à l'exception des générateurs ranlux s'ils sont utilisés avec un facteur de luxe généreux.

Si vous voulez des résultats solides et reproductibles, vous devez apporter votre propre générateur.

Si vous voulez la portabilité, vous devez apporter votre propre générateur.

Si vous pouvez vivre avec une portabilité restreinte, vous pouvez utiliser boost ou le framework C++ 11 en conjonction avec vos propres générateurs.

Plus de détails - y compris le code pour un générateur simple mais rapide d'excellente qualité et des liens copieux - peuvent être trouvés dans mes réponses à des sujets similaires:

Pour les écarts de virgule flottante uniformes professionnels, il y a deux autres questions à considérer:

  • ouvert contre semi-ouvert contre fermé, c'est-à-dire (0,1), [0, 1) ou [0,1]
  • méthode de conversion de l'intégrale en virgule flottante (précision, vitesse)

Les deux sont en fait les deux faces d'une même pièce, car la méthode de conversion prend en charge l'inclusion/exclusion de 0 et 1. Voici trois méthodes différentes pour l'intervalle semi-ouvert:

// exact values computed with bc

#define POW2_M32   2.3283064365386962890625e-010
#define POW2_M64   5.421010862427522170037264004349e-020

double random_double_a ()
{
   double lo = random_uint32() * POW2_M64;
   return lo + random_uint32() * POW2_M32;
}

double random_double_b ()
{
   return random_uint64() * POW2_M64;
}

double random_double_c ()
{
   return int64_t(random_uint64()) * POW2_M64 + 0.5;
}

(random_uint32() et random_uint64() sont des espaces réservés pour vos fonctions réelles et devraient normalement être passés en tant que paramètres de modèle)

La méthode a montre comment créer un écart uniforme qui n'est pas biaisé par une précision excessive pour les valeurs inférieures; le code pour 64 bits n'est pas affiché car il est plus simple et implique simplement de masquer 11 bits. La distribution est uniforme pour toutes les fonctions, mais sans cette astuce, il y aurait plus de valeurs différentes dans la zone plus proche de 0 qu'ailleurs (espacement de grille plus fin en raison de la variation ulp).

La méthode c montre comment obtenir un écart uniforme plus rapidement sur certaines plates-formes populaires où la FPU ne connaît qu'un type intégral 64 bits signé. Ce que vous voyez le plus souvent, c'est la méthode b mais là, le compilateur doit générer beaucoup de code supplémentaire sous le capot pour préserver la sémantique non signée.

Mélangez et associez ces principes pour créer votre propre solution sur mesure.

Tout cela est expliqué dans l'excellent article de Jürgen Doornik Conversion of High-Period Random Numbers to Floating Point .

3
DarthGizka

Incluez d'abord stdlib.h

#include<stdlib.h>

Ensuite, la fonction suivante peut être une fonction pour générer un double nombre aléatoire entre une plage en langage de programmation C.

double randomDouble() {
    double lowerRange = 1.0;
    double upperRange = 10.0;
    return ((double)Rand() * (upperRange - lowerRange)) / (double)Rand_MAX + lowerRange;
}

Ici Rand_MAX est défini dans stdlib.h

2
Amarjit Datta

À mon avis, il y a trois façons de procéder,

1) La manière la plus simple.

double Rand_easy(void)
{       return (double) Rand() / (Rand_MAX + 1.0);
}

2) La manière sûre (conforme à la norme).

double Rand_safe(void)
{
        double limit = pow(2.0, DBL_MANT_Dig);
        double denom = Rand_MAX + 1.0;
        double denom_to_k = 1.0;
        double numer = 0.0;

        for ( ; denom_to_k < limit; denom_to_k *= denom )
           numer += Rand() * denom_to_k;

        double result = numer / denom_to_k;
        if (result == 1.0)
           result -= DBL_EPSILON/2;
        assert(result != 1.0);
        return result;
}

3) La manière personnalisée.

En éliminant Rand() nous n'avons plus à nous soucier des particularités d'une version particulière, ce qui nous donne plus de latitude dans notre propre implémentation.

Note: La période du générateur utilisé ici est ≅ 1.8e + 19.

#define RANDMAX (-1ULL)
uint64_t custom_lcg(uint_fast64_t* next)
{       return *next = *next * 2862933555777941757ULL + 3037000493ULL;
}

uint_fast64_t internal_next;
void seed_fast(uint64_t seed)
{       internal_next = seed;
}

double Rand_fast(void)
{
#define SHR_BIT (64 - (DBL_MANT_Dig-1))
        union {
            double f; uint64_t i;
        } u;
        u.f = 1.0;
        u.i = u.i | (custom_lcg(&internal_next) >> SHR_BIT);
        return u.f - 1.0;
}

Quel que soit le choix, les fonctionnalités peuvent être étendues comme suit,

double Rand_dist(double min, double max)
{       return Rand_fast() * (max - min) + min;
}

double Rand_open(void)
{       return Rand_dist(DBL_EPSILON, 1.0);
}

double Rand_closed(void)
{       return Rand_dist(0.0, 1.0 + DBL_EPSILON);
}

Notes finales: La version rapide - bien qu'écrite en C - peut être adaptée pour être utilisée en C++ pour être utilisée en remplacement de std::generate_canonical, Et fonctionnera pour tout générateur émettant des valeurs avec suffisamment bits significatifs.

La plupart des générateurs 64 bits profitent de leur pleine largeur, ce qui peut probablement être utilisé sans modification (ajustement de décalage). par exemple. cela fonctionne tel quel avec le moteur std::mt19937_64.

1
Johnny Cage

En considérant la simplicité et la vitesse comme vos critères principaux, vous pouvez ajouter un petit assistant générique comme celui-ci: -

  // C++ Rand generates random numbers between 0 and Rand_MAX. This is quite a big range
  // Normally one would want the generated random number within a range to be really
  // useful. So the arguments have default values which can be overridden by the caller
  int nextRandomNum(int low = 0, int high = 100) const {
    int range = (high - low) + 1;
    // this modulo operation does not generate a truly uniformly distributed random number
    // in the span (since in most cases lower numbers are slightly more likely), 
    // but it is generally a good approximation for short spans. Use it if essential
    //int res = ( std::Rand() % high + low );
    int res = low + static_cast<int>( ( range * std::Rand() / ( Rand_MAX + 1.0) ) );
    return res;
  }

La génération de nombres aléatoires est un sujet bien étudié, complexe et avancé. Vous pouvez trouver des algorithmes simples mais utiles ici en dehors de ceux mentionnés dans d'autres réponses: -

Confondu éternellement

0
Abhay