2009-08-27 28 views
53

Come si genera un doppio casuale distribuito uniformemente tra 0 e 1 da C++?Come si genera un doppio casuale distribuito uniformemente tra 0 e 1 da C++?

Naturalmente mi viene in mente alcune risposte, ma mi piacerebbe sapere qual è la prassi standard è, di avere:

  • Buoni standard di conformità
  • buona casualità
  • Buona velocità

(la velocità è più importante della casualità per la mia applicazione).

Grazie mille!

PS: In questo caso, le mie piattaforme di destinazione sono Linux e Windows.

+1

nota, come la mia risposta indica la moderna C++ modo per farlo è usando l'intestazione 'random'. –

risposta

26

In C++ 11 e C++ 14 abbiamo opzioni molto migliori con lo random header. La presentazione rand() Considered Harmful da Stephan T. Lavavej spiega perché dovremmo evitare l'uso di rand() in C++ a favore della random intestazione e N3924: Discouraging rand() in C++14 rafforza ulteriormente questo punto.

L'esempio che segue è una versione modificata del codice di esempio sul sito cppreference e utilizza il motore std::mersenne_twister_engine e la std::uniform_real_distribution che genera numeri nella [0,1) gamma (see it live):

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

uscita sarà simile al seguente:

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

Poiché il posto ha detto che la velocità era importante allora dovremmo considerare la sezione cppreference che descrive i diversi motori di numeri casuali (sottolineatura mia):

La scelta di quale motore usare comporta una serie di compromessi *: il ** motore lineare congruenziale è moderatamente veloce e ha un requisito di memoria molto piccolo per lo stato. I generatori di Fibonacci ritardati sono molto veloci anche su processori senza istruzioni aritmetiche avanzate set, a spese di una maggiore memoria di stato e talvolta meno caratteristiche spettrali desiderabili. Il twister Mersenne è più lento e ha maggiori requisiti di memoria di stato ma con i parametri corretti ha la sequenza non ripetuta più lunga con le caratteristiche spettrali più desiderate (per una definizione data di desiderabile).

Quindi, se v'è il desiderio di un generatore più veloce forse ranlux24_base o ranlux48_base sono scelte migliori oltre mt19937.

rand()

Se costretti ad usare rand() poi il C FAQ per una guida su How can I generate floating-point random numbers?, ci dà un esempio simile a questo per generare un sull'intervallo [0,1):

#include <stdlib.h> 

double randZeroToOne() 
{ 
    return rand()/(RAND_MAX + 1.); 
} 

e per generare un numero casuale nell'intervallo da [M,N):

double randMToN(double M, double N) 
{ 
    return M + (rand()/(RAND_MAX/(N-M))) ; 
} 
55

Una soluzione vecchia scuola come:

double X=((double)rand()/(double)RAND_MAX); 

dovrebbero soddisfare tutti i tuoi criteri (portatili, standard e veloce). ovviamente il numero casuale generato deve essere seminato la procedura standard è qualcosa di simile:

srand((unsigned)time(NULL)); 
+6

Diffidare di RAND_MAX anche se si sta cercando un intervallo e un comportamento identici su sistemi diversi. Alcuni hanno un RAND_MAX di 32k, altri 2048k. Sempre meglio controllare – Dave

+7

@Dave: solo per essere nitpicky: in questo caso, l'output sarà sempre un doppio nell'intervallo [0,1], quindi l'unica cosa che il valore di RAND_MAX può cambiare è la quantità di valori distinti che l'espressione può ritorno. – suszterpatt

+0

Grazie, questo è quello che stavo cercando. –

6

Il random_real class dal Boost random library è quello che ti serve.

+3

La documentazione per quella libreria è piuttosto scarsa. Hai un campione di codice? – Bill

+2

Si prega di dare un'occhiata a questo campione: http://www.boost.org/doc/libs/1_39_0/libs/random/random_demo.cpp –

4

Se la velocità è la preoccupazione principale, allora mi piacerebbe semplicemente andare con

double r = (double)rand()/(double)RAND_MAX; 
0

considerando Bene semplicità e la velocità come criterio principale, è possibile aggiungere un piccolo aiuto generico come questo: -

// 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 generazione di numeri casuali è un argomento ben studiato, complesso e avanzato. Potete trovare alcuni algoritmi semplici ma utili qui oltre a quelli menzionati in altre risposte: -

Eternally Confuzzled

5

Ecco come si farebbe se si stesse utilizzando C++ TR1.

+3

Questa mi sembra l'unica implementazione che in realtà pretende di darti una distribuzione reale UNIFORME - la mia soluzione sopra dà MAX_RAND valori possibili distribuiti uniformemente nell'intervallo 0 a 1 che non è proprio la stessa cosa. – Elemental

+2

meno uno, le risposte al solo collegamento sono corrugate su – ValarDohaeris

+2

per migliorare questa risposta, spiegare la risposta concisamente qui e collegarsi al blog per ulteriori dettagli, se applicabile – dinosaur

-2
double randDouble() 
{ 
    double out; 
    out = (double)rand()/(RAND_MAX + 1); //each iteration produces a number in [0, 1) 
    out = (rand() + out)/RAND_MAX; 
    out = (rand() + out)/RAND_MAX; 
    out = (rand() + out)/RAND_MAX; 
    out = (rand() + out)/RAND_MAX; 
    out = (rand() + out)/RAND_MAX; 

    return out; 
} 

Non abbastanza veloce come double X=((double)rand()/(double)RAND_MAX);, ma con una migliore distribuzione. Questo algoritmo fornisce solo RAND_MAX una scelta uniforme dei valori di ritorno; questo dà RANDMAX^6, quindi la sua distribuzione è limitata solo dalla precisione del doppio.

Se si desidera un doppio lungo, aggiungere solo alcune iterazioni. Se si desidera un numero in [0, 1] anziché [0, 1), basta fare in modo che la riga 4 legga out = (double)rand()/(RAND_MAX);.

+1

Correggetemi se ho torto, ma questo metodo ha un valore non nullo probabilità di dare un numero strettamente superiore a 1, giusto? – Julio

+1

@Julio: Non solo, non produce nemmeno una distribuzione uniforme anche se i numeri sono nell'intervallo 0-1. In realtà la distribuzione dovrebbe essere tutt'altro che uniforme. Questo è il motivo per cui NON dovresti pensare a questi algoritmi tu stesso ... – thesaint

+2

-1 questo sembra davvero indebolire invece di rafforzare la casualità – Elemental

-2
//Returns a random number in the range (0.0f, 1.0f). 
// 0111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 
// seee eeee eeee vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv 
// sign  = 's' 
// exponent = 'e' 
// value = 'v' 
double DoubleRand() { 
    typedef unsigned long long uint64; 
    uint64 ret = 0; 
    for (int i = 0; i < 13; i++) { 
    ret |= ((uint64) (rand() % 16) << i * 4); 
    } 
    if (ret == 0) { 
    return rand() % 2 ? 1.0f : 0.0f; 
    } 
    uint64 retb = ret; 
    unsigned int exp = 0x3ff; 
    retb = ret | ((uint64) exp << 52); 
    double *tmp = (double*) &retb; 
    double retval = *tmp; 
    while (retval > 1.0f || retval < 0.0f) { 
    retval = *(tmp = (double*) &(retb = ret | ((uint64) (exp--) << 52))); 
    } 
    if (rand() % 2) { 
    retval -= 0.5f; 
    } 
    return retval; 
} 

Questo dovrebbe fare il trucco, ho usato this articolo di Wikipedia per contribuire a creare questo. Credo che sia buono come drand48();

+1

Wow, questo è davvero, davvero terribile. – kay

3

La libreria standard C++ 11 contiene una struttura decente e un paio di generatori utilizzabili, che sono perfettamente sufficienti per l'assegnazione dei compiti a casa e l'uso fuori dagli schemi.

Tuttavia, per il codice di livello di produzione è necessario conoscere esattamente quali sono le proprietà specifiche dei vari generatori prima di utilizzarli, poiché tutti hanno i loro avvertimenti. Inoltre, nessuno di questi supera i test standard per PRNG come TestU01, ad eccezione dei generatori di ranlux se usati con un generoso fattore di lusso.

Se si desidera ottenere risultati solidi e ripetibili, è necessario portare il proprio generatore.

Se si desidera la portabilità, è necessario portare il proprio generatore.

Se è possibile vivere con portabilità limitata, è possibile utilizzare boost o il framework C++ 11 in combinazione con i propri generatori.

Maggiori dettagli - compreso il codice per un generatore semplice ma veloce di ottima qualità e collegamenti copiose - può essere trovato nelle mie risposte ad argomenti simili:

Per deviazioni in virgola mobile uniforme professionale ci sono due ulteriori problemi da considerare:

  • aperto vs. semiaperta vs intervallo chiuso, cioè (0,1), [0, 1) o [0,1]
  • metodo di conversione da integrale a virgola mobile (precisione, velocità)

Entrambi sono in realtà due facce della stessa medaglia, come il metodo di conversione si occupa della inclusione/esclusione di 0 e 1. Qui sono tre metodi diversi per l'intervallo semiaperto:

// 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() e random_uint64() sono segnaposto per le funzioni effettive e normalmente essere passati come parametri di modello)

Metodo un dimostra come creare un deviare uniforme che non viene sollecitato da eccesso di precisione per valori più bassi; il codice per 64-bit non viene mostrato perché è più semplice e richiede solo il mascheramento di 11 bit. La distribuzione è uniforme per tutte le funzioni, ma senza questo trucco ci sarebbero più valori diversi nell'area più vicina a 0 che altrove (una spaziatura più fine della griglia a causa dell'ulp variabile).

Metodo c mostra come ottenere una deviazione uniforme più veloce su alcune piattaforme popolari in cui la FPU conosce solo un tipo integrale a 64 bit con segno. Quello che vedi più spesso è il metodo b ma il compilatore deve generare molto codice extra sotto il cofano per preservare la semantica non firmata.

Combina e abbina questi principi per creare la tua soluzione personalizzata.

Tutto questo è spiegato nell'eccellente carta di Jürgen Doornik Conversion of High-Period Random Numbers to Floating Point.

1

come la vedo io, ci sono tre modi per andare con questo,

1) Il modo più semplice.

double rand_easy(void) 
{  return (double) rand()/(RAND_MAX + 1.0); 
} 

2) Il modo sicuro (conforme standard).

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) Il modo personalizzato.

Eliminando rand() non dobbiamo più preoccuparci delle idiosincrasie di una particolare versione, che ci dà più margine di manovra nella nostra implementazione.

Nota: Il periodo di utilizzo del generatore è ≅ 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; 
} 

Qualunque sia la scelta, la funzionalità può essere estesa come segue,

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

Note finali: la versione rapida - mentre scritto in C - possono essere adattati per l'uso in C++ per essere utilizzato come una sostituzione per std::generate_canonical e funzionerà per qualsiasi generatore che emetta valori con bit significativi sufficienti.

La maggior parte dei generatori a 64 bit sfrutta tutta la loro larghezza, pertanto è possibile utilizzarla senza modifiche (regolazione dello spostamento). per esempio. questo funziona così com'è con il motore std::mt19937_64.

2

First includono stdlib.h

#include<stdlib.h> 

Dai seguenti possono essere una funzione per generare il numero casuale doppio tra un intervallo nel linguaggio di programmazione C.

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

Qui RAND_MAX è definito in stdlib.h

-1

Questo è quello che ho finito per usare per le mie esigenze:

int range_upper_bound = 12345; 
int random_number =((double)rand()/(double)range_upper_bound); 
Problemi correlati