¿Cómo se genera un doble aleatorio distribuido uniformemente entre 0 y 1 desde C ++?

¿Cómo se genera un doble aleatorio distribuido uniformemente entre 0 y 1 desde C ++?

Por supuesto que puedo pensar en algunas respuestas, pero me gustaría saber cuál es la práctica estándar: tener:

  • Buen cumplimiento de las normas
  • Buena aleatoriedad
  • Buena velocidad

(la velocidad es más importante que la aleatoriedad para mi aplicación).

¡Muchas gracias!

PD: en caso de que eso importe, mis plataformas de destino son Linux y Windows.

En C ++ 11 y C ++ 14 tenemos opciones mucho mejores con el encabezado aleatorio . La presentación rand () Considerado dañino por Stephan T. Lavavej explica por qué deberíamos evitar el uso de rand() en C ++ a favor del encabezado random y N3924: Desalendiendo rand () en C ++ 14 refuerza aún más este punto.

El siguiente ejemplo es una versión modificada del código de ejemplo en el sitio cppreference y utiliza el motor std :: mersenne_twister_engine y el std :: uniform_real_distribution que genera números en el rango [0,1) ( ver en vivo ):

 #include  #include  #include  #include  int main() { std::random_device rd; std::mt19937 e2(rd()); std::uniform_real_distribution<> dist(0, 1); std::map 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'; } } 

el resultado será similar al siguiente:

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

Como la publicación mencionó que la velocidad era importante, deberíamos considerar la sección cppreference que describe los diferentes motores de números aleatorios ( énfasis mío ):

La elección de qué motor utilizar implica una serie de concesiones *: el ** motor congruente lineal es moderadamente rápido y tiene un requisito de almacenamiento muy pequeño para el estado. Los generadores de Fibonacci rezagados son muy rápidos incluso en procesadores sin conjuntos de instrucciones aritméticas avanzadas , a expensas de un mayor almacenamiento de estado y, a veces, características espectrales menos deseables. El tornado Mersenne es más lento y tiene mayores requisitos de almacenamiento de estado, pero con los parámetros correctos tiene la secuencia no repetitiva más larga con las características espectrales más deseables (para una definición dada de deseable).

Entonces, si hay un deseo de un generador más rápido, tal vez ranlux24_base o ranlux48_base sean mejores opciones que mt19937 .

rand ()

Si forzó el uso de rand() entonces las preguntas frecuentes C para una guía sobre ¿Cómo puedo generar números aleatorios de coma flotante? , nos da un ejemplo similar a esto para generar un en el intervalo [0,1) :

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

y para generar un número aleatorio en el rango de [M,N) :

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

Una solución de la vieja escuela como:

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

Debe cumplir con todos sus criterios (portátil, estándar y rápido). obviamente, el número aleatorio generado debe ser sembrado, el procedimiento estándar es algo así como:

 srand((unsigned)time(NULL)); 

La clase random_real de la biblioteca aleatoria Boost es lo que necesita.

Así es como lo harías si estuvieras usando C ++ TR1 .

Si la velocidad es su principal preocupación, entonces simplemente iría con

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

La biblioteca estándar de C ++ 11 contiene un marco decente y un par de generadores útiles, lo cual es perfectamente suficiente para las asignaciones de tareas y el uso fuera del manguito.

Sin embargo, para el código de grado de producción, debe saber exactamente cuáles son las propiedades específicas de los diversos generadores antes de usarlos, ya que todos tienen sus advertencias. Además, ninguno de ellos pasa las pruebas estándar para PRNG como TestU01, a excepción de los generadores de ranglio si se usan con un generoso factor de lujo.

Si desea resultados sólidos y repetibles, debe traer su propio generador.

Si desea portabilidad, debe traer su propio generador.

Si puede vivir con portabilidad restringida, puede usar boost o el marco de C ++ 11 junto con su propio generador (es).

Más detalles, incluido el código para un generador simple pero rápido de excelente calidad y copiosos enlaces, se pueden encontrar en mis respuestas a temas similares:

  • Generación de números aleatorios de propósito general
  • Generador de números aleatorios de distribución uniforme muy rápido

Para las desviaciones de puntos flotantes uniformes profesionales, hay dos cuestiones más a tener en cuenta:

  • rango abierto vs. medio abierto vs. cerrado, es decir (0,1), [0, 1) o [0,1]
  • método de conversión de integral a punto flotante (precisión, velocidad)

Ambos son en realidad dos caras de la misma moneda, ya que el método de conversión se ocupa de la inclusión / exclusión de 0 y 1. Aquí hay tres métodos diferentes para el intervalo medio abierto:

 // 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() y random_uint64() son marcadores de posición para sus funciones reales y normalmente pasarían como parámetros de plantilla)

El método a demuestra cómo crear un desvío uniforme que no esté sesgado por una precisión excesiva para valores más bajos; el código para 64 bits no se muestra porque es más simple y solo implica el enmascaramiento de 11 bits. La distribución es uniforme para todas las funciones, pero sin este truco habría más valores diferentes en el área más cercana a 0 que en cualquier otra parte (espaciado de cuadrícula más fino debido a la variación de ULP).

El método c muestra cómo obtener un desvío uniforme más rápido en ciertas plataformas populares donde la FPU solo conoce un tipo integral de 64 bits firmado. Lo que se ve más a menudo es el método b, pero el comstackdor tiene que generar muchos códigos adicionales para preservar la semántica sin firmar.

Combine y combine estos principios para crear su propia solución a medida.

Todo esto se explica en el excelente trabajo de Jürgen Doornik Conversión de números aleatorios de alto período a punto flotante .

Primero incluye stdlib.h

 #include 

Luego, puede ser una función para generar un número doble aleatorio entre un rango en el lenguaje de progtwigción C.

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

Aquí RAND_MAX se define en stdlib.h

Como lo veo, hay tres formas de ir con esto,

1) La manera fácil.

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

2) La manera segura (conformidad estándar).

 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 forma personalizada.

Al eliminar rand() ya no tenemos que preocuparnos por la idiosincrasia de ninguna versión en particular, lo que nos da más margen de maniobra en nuestra propia implementación.

Nota: El período del generador utilizado aquí es ≅ 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; uf = 1.0; ui = ui | (custom_lcg(&internal_next) >> SHR_BIT); return uf - 1.0; } 

Cualquiera que sea la elección, la funcionalidad se puede ampliar de la siguiente manera:

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

Notas finales: la versión rápida, aunque está escrita en C, puede adaptarse para su uso en C ++ para ser utilizada como reemplazo de std::generate_canonical , y funcionará para cualquier generador que emita valores con suficientes bits significativos.

La mayoría de los generadores de 64 bits aprovechan todo su ancho, por lo que es probable que se pueda usar sin modificaciones (ajuste de desplazamiento). por ejemplo, esto funciona como está con el motor std::mt19937_64 .

Considerando bien la simplicidad y la velocidad como criterio principal, puede agregar un pequeño ayudante genérico como este:

  // 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( ( range * std::rand() / ( RAND_MAX + 1.0) ) ); return res; } 

La generación aleatoria de números es un tema bien estudiado, complejo y avanzado. Aquí puede encontrar algunos algoritmos simples pero útiles aparte de los mencionados en otras respuestas:

Eternamente confuso

Podría probar el algoritmo de Mersenne Twister.

http://en.wikipedia.org/wiki/Mersenne_twister

Tiene una buena combinación de velocidad y aleatoriedad, y una implementación de GPL.

Esto es lo que terminé usando para mis necesidades:

 int range_upper_bound = 12345; int random_number =((double)rand()/(double)range_upper_bound); 
 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; } 

No tan rápido como el double X=((double)rand()/(double)RAND_MAX); , pero con una mejor distribución. Ese algoritmo ofrece solo RAND_MAX elección de los valores de retorno espaciados uniformemente; este da RANDMAX ^ 6, por lo que su distribución está limitada solo por la precisión del doble.

Si quieres un doble largo solo agrega algunas iteraciones. Si desea un número en [0, 1] en lugar de [0, 1) simplemente haga que la línea 4 lea out = (double)rand()/(RAND_MAX); .

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

Esto debería hacer el truco, utilicé este artículo de Wikipedia para ayudar a crear esto. Creo que es tan bueno como drand48();