Genere números aleatorios de manera uniforme en todo un rango


93

Necesito generar números aleatorios dentro de un intervalo específico, [max; min].

Además, los números aleatorios deben distribuirse uniformemente a lo largo del intervalo, no ubicarse en un punto en particular.

Actualmente estoy generando como:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

A partir de mis pruebas, los números aleatorios se generan alrededor de un solo punto.

Example
min = 3604607;
max = 7654607;

Números aleatorios generados:

3631594
3609293
3630000
3628441
3636376
3621404

De las respuestas a continuación: OK, RAND_MAX es 32767. Estoy en la plataforma C ++ Windows. ¿Existe algún otro método para generar números aleatorios con una distribución uniforme?


2
Construye un Dice-O-Matic: gamesbyemail.com/News/DiceOMatic
Jarett Millard

1
No tenía idea de que C ++ rand()era uniforme. ¿Qué biblioteca estás usando? cstdlib.h's rand()no es uniforme: cplusplus.com/reference/cstdlib/rand
Mike Warren

3
No, rand () es uniforme (excepto en algunas implementaciones tempranas con errores). lo que no es uniforme es usar el operador de módulo '%' para restringir el rango. Consulte stackoverflow.com/questions/2999075/… para obtener una solución adecuada, o si tiene 'arc4random_uniform' disponible, también puede usarlo directamente.
John Meacham

@ Alien01: ¿Consideraría cambiar la respuesta aceptada a la de "Zapato" ("Por qué rand es una mala idea", etc.)? Mi respuesta está realmente desactualizada y cada vez que recibo un voto positivo siento que alguien está corriendo por el pasillo equivocado.
Peterchen

Buen libro blanco sobre el azar en c ++ 11.
Pupsik

Respuestas:


153

Por que randes una mala idea

La mayoría de las respuestas que obtuvo aquí utilizan la randfunción y el operador de módulo. Es posible que ese método no genere números de manera uniforme (depende del rango y el valor de RAND_MAX) y, por lo tanto, se desaconseja.

C ++ 11 y generación en un rango

Con C ++ 11 han surgido muchas otras opciones. Uno de los cuales se adapte a sus necesidades, para generar un número aleatorio en un rango, bastante bien: std::uniform_int_distribution. He aquí un ejemplo:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

Y aquí está el ejemplo de ejecución.

Otros generadores aleatorios

El <random>encabezado ofrece innumerables otros generadores de números aleatorios con diferentes tipos de distribuciones, incluidos Bernoulli, Poisson y normal.

¿Cómo puedo barajar un contenedor?

El estándar proporciona std::shuffle, que se puede utilizar de la siguiente manera:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

El algoritmo reordenará los elementos de forma aleatoria, con una complejidad lineal.

Boost.Random

Otra alternativa, en caso de que no tenga acceso a un compilador de C ++ 11 +, es usar Boost.Random . Su interfaz es muy similar a la de C ++ 11.


22
ATENCIÓN a esta respuesta, ya que es mucho más moderna.
gsamaras

Ésta es la respuesta correcta. ¡Gracias! Aún así, me gustaría ver una descripción más detallada de cada paso de ese código. Por ejemplo, ¿qué es un mt19937tipo?
Apollo

@Apollo La documentación dice "Mersenne Twister de 32 bits de Matsumoto y Nishimura, 1998". Supongo que es un algoritmo para generar números pseudoaleatorios.
Zapato

@Shoe, para un intervalo dado, se genera números en mismo orden, 1 9 6 2 8 7 1 4 7 7. ¿Cómo aleatorizar esto cada vez que ejecutamos el programa?

1
@Richard ¿Cuál es la alternativa?
Zapato

59

[editar] Advertencia: No lo use rand()para estadísticas, simulación, criptografía ni nada serio.

Es lo suficientemente bueno para hacer que los números parezcan aleatorios para un humano típico con prisa, nada más.

Vea la respuesta de @ Jefffrey para obtener mejores opciones, o esta respuesta para números aleatorios seguros con cifrado.


Generalmente, los bits altos muestran una mejor distribución que los bits bajos, por lo que la forma recomendada de generar números aleatorios de un rango para propósitos simples es:

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Nota : ¡asegúrese de que RAND_MAX + 1 no se desborde (gracias Demi)!

La división genera un número aleatorio en el intervalo [0, 1); "estire" esto al rango requerido. Solo cuando max-min + 1 se acerca a RAND_MAX, necesita una función "BigRand ()" como la publicada por Mark Ransom.

Esto también evita algunos problemas de división debido al módulo, lo que puede empeorar aún más sus números.


No se garantiza que el generador de números aleatorios incorporado tenga la calidad requerida para las simulaciones estadísticas. Está bien que los números "parezcan aleatorios" para un humano, pero para una aplicación seria, debe tomar algo mejor, o al menos verificar sus propiedades (la distribución uniforme suele ser buena, pero los valores tienden a correlacionarse y la secuencia es determinista ). Knuth tiene un tratado excelente (aunque difícil de leer) sobre generadores de números aleatorios, y recientemente descubrí que LFSR es excelente y muy simple de implementar, dado que sus propiedades están bien para usted.


4
BigRand puede dar mejores resultados incluso cuando el rango deseado no excede RAND_MAX. Considere cuándo RAND_MAX es 32767 y desea 32767 valores posibles: dos de esos 32768 números aleatorios (incluido el cero) se asignarán a la misma salida y será el doble de probable que ocurra que los demás. ¡Difícilmente una propiedad aleatoria ideal!
Mark Ransom

7
(RAND_MAX + 1) es una mala idea. Esto puede acumularse y darle un valor negativo. Es mejor hacer algo como: ((doble) RAND_MAX) + 1.0
Demi

3
@peterchen: Creo que entendiste mal lo que decía Demi. Quería decir esto: ( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min simplemente mueva la conversión al doble y evite el problema.
Mooing Duck

3
Además, esto simplemente cambia la distribución desde los valores inferiores 32767 en el rango a valores 32767 distribuidos uniformemente en el rango, y los valores restantes 4017233 nunca serán seleccionados por este algoritmo.
Mooing Duck

1
La respuesta dada está desviada en 1. La ecuación correcta es: ((doble) rand () / (RAND_MAX + 1.0)) * (max-min) + min El "max-min + 1" se usa cuando se usa% not * . Verá por qué cuando hace min = 0, max = 1. ¿Podría peterchen o @ peter-mortensen enmendarlo?
davepc

17

Me gustaría complementar las excelentes respuestas de Angry Shoe y peterchen con una breve descripción del estado del arte en 2015:

Algunas buenas elecciones

randutils

La randutilsbiblioteca (presentación) es una novedad interesante, que ofrece una interfaz simple y capacidades aleatorias robustas (declaradas). Tiene las desventajas de que agrega dependencia a su proyecto y, al ser nuevo, no se ha probado exhaustivamente. De todos modos, al ser gratis (licencia MIT) y solo de encabezado, creo que vale la pena intentarlo.

Muestra mínima: una tirada de dado

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

Incluso si uno no está interesado en la biblioteca, el sitio web ( http://www.pcg-random.org/ ) proporciona muchos artículos interesantes sobre el tema de la generación de números aleatorios en general y la biblioteca C ++ en particular.

Boost.Random

Boost.Random (documentación) es la biblioteca que inspiró C++11's <random>, con el que comparte gran parte de la interfaz. Si bien teóricamente también es una dependencia externa, Boostahora tiene el estado de biblioteca "cuasi estándar", y su Randommódulo podría considerarse como la opción clásica para la generación de números aleatorios de buena calidad. Presenta dos ventajas con respecto a la C++11solución:

  • es más portátil, solo necesita compatibilidad con el compilador para C ++ 03
  • sus random_devicemétodos de usos específicos del sistema a la oferta de siembra de buena calidad

El único pequeño defecto es que la oferta del módulo random_deviceno es solo de encabezado, hay que compilar y vincular boost_random.

Muestra mínima: una tirada de dado

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Si bien la muestra mínima funciona bien, los programas reales deberían usar un par de mejoras:

  • hacer mt19937a thread_local: el generador es bastante voluminoso (> 2 KB) y es mejor que no se asigne en la pila
  • semilla mt19937con más de un entero: el Mersenne Twister tiene un estado grande y puede beneficiarse de más entropía durante la inicialización

Algunas opciones no tan buenas

La biblioteca C ++ 11

Si bien es la solución más idiomática, la <random>biblioteca no ofrece mucho a cambio de la complejidad de su interfaz incluso para las necesidades básicas. La falla está en std::random_device: el Estándar no exige una calidad mínima para su salida (siempre que entropy()devuelva 0) y, a partir de 2015, MinGW (no es el compilador más utilizado, pero no es una opción esotérica) siempre imprimirá 4en la muestra mínima.

Muestra mínima: una tirada de dado

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Si la implementación no está podrida, esta solución debería ser equivalente a la de Boost, y se aplican las mismas sugerencias.

La solución de Godot

Muestra mínima: una tirada de dado

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

Esta es una solución simple, efectiva y ordenada. Solo es un defecto, llevará un tiempo compilarlo, aproximadamente dos años, siempre que C ++ 17 se publique a tiempo y la randintfunción experimental esté aprobada en el nuevo estándar. Quizás en ese momento también mejoren las garantías sobre la calidad de la siembra.

La solución peor es mejor

Muestra mínima: una tirada de dado

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

La antigua solución C se considera dañina y por buenas razones (consulte las otras respuestas aquí o este análisis detallado ). Aún así, tiene sus ventajas: es simple, portátil, rápido y honesto, en el sentido de que se sabe que los números aleatorios que se obtienen son poco decentes y, por lo tanto, no se tiene la tentación de usarlos para propósitos serios.

La solución de trol de contabilidad

Muestra mínima: una tirada de dado

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

Si bien 9 es un resultado algo inusual para una tirada de dado normal, uno tiene que admirar la excelente combinación de buenas cualidades en esta solución, que logra ser la más rápida, simple, más fácil de usar y más portátil. Sustituyendo 9 con 4, se obtiene un generador perfecto para cualquier tipo de Dungeons and Dragons dado, mientras se evitan los valores cargados de símbolos 1, 2 y 3. El único pequeño defecto es que, debido al mal genio de los trolls contables de Dilbert, este programa en realidad genera un comportamiento indefinido.


La randutilsbiblioteca se llama ahora PCG.
tay10r

11

Si RAND_MAXes 32767, puede duplicar el número de bits fácilmente.

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

No creo que esto funcione. Los generadores de números pseudoaleatorios son típicamente deterministas. Por ejemplo, si la primera randllamada regresa 0x1234y la segunda 0x5678, obtienes 0x12345678. Ese es el único número que puede obtener que comienza con 0x1234, porque el próximo número siempre será 0x5678. Obtiene resultados de 32 bits, pero solo tiene 32768 números posibles.
user694733

@ user694733 un buen generador de números aleatorios tiene un período que es mayor que el número de salidas que puede generar, por lo que 0x1234 no siempre será seguido por 0x5678.
Mark Ransom

9

Si puede, use Boost . He tenido buena suerte con su biblioteca aleatoria .

uniform_int debe hacer lo que quiera.


He trabajado un poco en uniform_int con un tornado merseinne y, desafortunadamente, para ciertos rangos, los valores devueltos por uniform_int no son tan uniformes como esperaba. Por ejemplo, uniform_int <> (0, 3) tiende a producir más 0 que 1 o 2
ScaryAardvark

@ScaryAardvark eso suena como una mala implementación de uniform_intentonces. Es bastante fácil generar una salida imparcial, ha habido varias preguntas aquí que demuestran el método.
Mark Ransom

@Mark Ransom. Sí, estoy completamente de acuerdo.
ScaryAardvark

8

Si le preocupa la aleatoriedad y no la velocidad, debe utilizar un método seguro de generación de números aleatorios. Hay varias formas de hacer esto ... La más fácil es usar el generador de números aleatorios de OpenSSL .

También puede escribir el suyo propio utilizando un algoritmo de cifrado (como AES ). Al elegir una semilla y un IV y luego volver a cifrar continuamente la salida de la función de cifrado. Usar OpenSSL es más fácil, pero menos varonil.


¿No puedo usar ninguna biblioteca de terceros? Estoy restringido solo a C ++.
Anand

Luego siga la ruta masculina, implemente AES o algún otro algoritmo de cifrado.
SoapBox

2
RC4 es trivial de codificar y lo suficientemente aleatorio para todos los propósitos prácticos (excepto WEP, pero eso no es del todo culpa de RC4). Lo digo en serio, es un código increíblemente trivial. Como, 20 líneas más o menos. La entrada de Wikipedia tiene pseudocódigo.
Steve Jessop

4
¿Por qué no puede utilizar código de terceros? Si esta es una pregunta de tarea, debe decirlo, porque muchas personas prefieren dar consejos útiles en lugar de proporcionar soluciones completas en este caso. Si no es una tarea, patea al tipo que dice "sin código de terceros", porque es un idiota.
DevSolar

Enlace más directo a los documentos de la función OpenSSL rand (): openssl.org/docs/crypto/rand.html#
DevSolar

5

Debería buscar RAND_MAXsu compilador / entorno particular. Creo que vería estos resultados si rand()produce un número aleatorio de 16 bits. (parece que está asumiendo que será un número de 32 bits).

No puedo prometer que esta sea la respuesta, pero por favor publique su valor RAND_MAXy un poco más de detalles sobre su entorno.



2

Este no es el código, pero esta lógica puede ayudarte.

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

Si desea que los números se distribuyan uniformemente en el rango, debe dividir su rango en varias secciones iguales que representen la cantidad de puntos que necesita. Luego, obtenga un número aleatorio con un mínimo / máximo para cada sección.

Como otra nota, probablemente no debería usarlo, rand()ya que no es muy bueno para generar números aleatorios. No sé en qué plataforma está ejecutando, pero probablemente haya una función mejor a la que pueda llamar como random().


1

Esto debería proporcionar una distribución uniforme en el rango [low, high)sin usar flotadores, siempre que el rango general sea menor que RAND_MAX.

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

y para valores superiores a RAND_MAX, desea algo como

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

Así es aproximadamente como std :: uniform_int_distribution hace las cosas.


0

Por su naturaleza, una pequeña muestra de números aleatorios no tiene que distribuirse uniformemente. Son aleatorios, después de todo. Estoy de acuerdo en que si un generador de números aleatorios está generando números que parecen estar agrupados de manera consistente, entonces probablemente haya algo mal en él.

Pero tenga en cuenta que la aleatoriedad no es necesariamente uniforme.

Editar: agregué "pequeña muestra" para aclarar.


"distribuido uniformemente" tiene un significado bien definido, y los generadores aleatorios estándar generalmente se acercan.
peterchen

Sí, tiene razón, los generadores de números aleatorios deberían producir una salida que a lo largo del tiempo sea ​​generalmente uniforme en su distribución. Supongo que mi punto es que en una pequeña cantidad de instancias (6 como se muestra en el ejemplo) la salida no siempre será uniforme.
Kluge

Kluge tiene razón. La distribución uniforme en una muestra pequeña indica que la muestra definitivamente no es aleatoria.
Bill the Lizard

1
Bill, no indica tal cosa. Las muestras pequeñas carecen en su mayoría de sentido, pero si se supone que el RNG es uniforme y la salida es uniforme, ¿por qué es peor que una muestra pequeña no uniforme?
Dan Dyer

2
Las distribuciones significativas de cualquier manera indican no aleatoriedad: creo que Bill solo quiere decir que 6 resultados igualmente espaciados también serían sospechosos. En el OP, 6 valores se encuentran en un rango de 32k / 4M, o <1% del rango deseado. La probabilidad de que esto sea un falso positivo es demasiado pequeña para discutir.
Steve Jessop

0

La solución dada por man 3 rand para un número entre 1 y 10 inclusive es:

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

En tu caso sería:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

Por supuesto, esto no es una perfecta aleatoriedad o uniformidad, como señalan otros mensajes, pero esto es suficiente para la mayoría de los casos.


1
Esto simplemente reorganiza la distribución para que parezca más uniforme, pero en realidad ya no es incluso para rangos grandes (como el caso del OP)
Mooing Duck

0

@Solución ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Advertencia : No olvide que debido al estiramiento y los posibles errores de precisión (incluso si RAND_MAX fuera lo suficientemente grande), solo podrá generar "contenedores" distribuidos uniformemente y no todos los números en [min, max].


@Solución: Bigrand

Advertencia : Tenga en cuenta que esto duplica los bits, pero aún así no podrá generar todos los números en su rango en general, es decir, no es necesariamente cierto que BigRand () generará todos los números en su rango.


Información : Su enfoque (módulo) es "correcto" siempre que el rango de rand () exceda su rango de intervalo y rand () sea "uniforme". El error para como máximo los primeros números máximo - mínimo es 1 / (RAND_MAX +1).

Además, sugiero cambiar también al nuevo paquete aleatorio e en C ++ 11, que ofrece mejores y más variedades de implementaciones que rand ().


0

Esta es la solución que se me ocurrió:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

Esta es una solución de cubo, conceptualmente similar a las soluciones que se utilizan rand() / RAND_MAXpara obtener un rango de punto flotante entre 0-1 y luego redondearlo en un cubo. Sin embargo, utiliza matemática puramente entera y aprovecha el piso de división de enteros para redondear el valor al cubo más cercano.

Hace algunas suposiciones. Primero, asume que RAND_MAX * (max - min + 1)siempre encajará dentro de un int32_t. Si RAND_MAXes 32767 y se utilizan cálculos de 32 bits int, el rango máximo que puede tener es 32767. Si su implementación tiene un RAND_MAX mucho más grande, puede superar esto usando un entero más grande (como int64_t) para el cálculo. En segundo lugar, si int64_tse usa pero RAND_MAXsigue siendo 32767, en rangos mayores que RAND_MAXusted comenzará a tener "huecos" en los números de salida posibles. Este es probablemente el mayor problema con cualquier solución derivada del escalado rand().

Sin embargo, las pruebas sobre una gran cantidad de iteraciones muestran que este método es muy uniforme para rangos pequeños. Sin embargo, es posible (y probable) que matemáticamente esto tenga un pequeño sesgo y posiblemente desarrolle problemas cuando se acerque el rango RAND_MAX. Pruébelo usted mismo y decida si satisface sus necesidades.


-1

Por supuesto, el siguiente código no le dará números aleatorios sino números pseudoaleatorios. Usa el siguiente código

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

Por ejemplo:

int myRand = QUICK_RAND(10, 20);

Debes llamar

srand(time(0));  // Initialize random number generator.

de lo contrario, los números no serán casi aleatorios.


1
La pregunta es pedir una distribución uniforme. Esta solución propuesta no producirá una distribución uniforme. La biblioteca estándar de C ++ tiene instalaciones para la generación de números pseudoaleatorios . Los que lo hacen proporcionar una distribución uniforme, si así lo solicita.
Inspectable

-3

Acabo de encontrar esto en Internet. Esto debería funcionar:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

Aclare para qué los necesita, hay toneladas de algoritmos para PRNG por ahí. Además, sería más fácil si edita su pregunta principal en lugar de publicar respuestas.
peterchen

Esto funciona mejor para mí ... Soy capaz de obtener números aleatorios distribuidos mejor con esta fórmula ..
Anand

4
Si su rango excede RAND_MAX, es posible que los resultados no sean uniformes. Es decir, hay valores en el rango que no se representarán sin importar cuántas veces se llame a su función.
dmckee --- gatito ex-moderador

4
Además, si max y min son int sin signo, min es 0 y max es MAX_UINT, entonces ((max) - (min) +1) será 0, y el resultado será 0 siempre. ¡Cuidado con el desbordamiento al hacer este tipo de matemáticas! Como señaló dmckee, esto extiende la distribución sobre el rango de destino, pero no garantiza más que valores únicos de RAND_MAX.
jesup
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.