Generando entero aleatorio a partir de un rango

158

Necesito una función que genere un número entero aleatorio en un rango determinado (incluidos los valores de borde). No tengo requisitos de calidad / aleatoriedad irracionales, tengo cuatro requisitos:

  • Necesito que sea rápido. Mi proyecto necesita generar millones (o incluso decenas de millones) de números aleatorios y mi función actual de generador ha demostrado ser un cuello de botella.
  • Necesito que sea razonablemente uniforme (el uso de rand () está perfectamente bien).
  • los rangos min-max pueden ser desde <0, 1> hasta <-32727, 32727>.
  • Tiene que ser visible.

Actualmente tengo el siguiente código C ++:

output = min + (rand() * (int)(max - min) / RAND_MAX)

El problema es que no es realmente uniforme: max solo se devuelve cuando rand () = RAND_MAX (para Visual C ++ es 1/32727). Este es un problema importante para rangos pequeños como <-1, 1>, donde el último valor casi nunca se devuelve.

Así que tomé lápiz y papel y se me ocurrió la siguiente fórmula (que se basa en el truco de redondeo de enteros (int) (n + 0.5)):

ingrese la descripción de la imagen aquí

Pero todavía no me da una distribución uniforme. Las ejecuciones repetidas con 10000 muestras me dan una relación de 37:50:13 para valores valores -1, 0. 1.

¿Podría por favor sugerir una mejor fórmula? (o incluso la función de generador de números pseudoaleatorios completos)

Matěj Zábský
fuente
3
@Bill MaGriff: sí. Tiene el mismo problema Una versión simplificada es: ¿cómo puede dividir 10 piezas de dulces entre 3 niños de manera uniforme (sin romper ninguno de los dulces)? La respuesta es que no puedes, tienes que dar tres a cada niño, y simplemente no dar el décimo a nadie.
Jerry Coffin
55
¿Has mirado en Boost.Random ?
Fred Nurk
3
Consulte el artículo de Andrew Koenig "Un problema simple que casi nunca se resuelve correctamente": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev
1
@Gene Bushuyev: Andrew y yo hemos estado insistiendo en este tema durante bastante tiempo. Ver: groups.google.com/group/comp.lang.c++/browse_frm/thread/… , y: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

Respuestas:

105

Una solución distribuida rápida, algo mejor que la suya, pero aún no uniformemente distribuida es

output = min + (rand() % static_cast<int>(max - min + 1))

Excepto cuando el tamaño del rango es una potencia de 2, este método produce números distribuidos no uniformes sesgados independientemente de la calidad de rand(). Para una prueba exhaustiva de la calidad de este método, lea esto .

Mark B
fuente
2
Gracias, esto parece ser lo suficientemente bueno para mí de las pruebas rápidas: su distribución para -1, 0, 1 es casi 33:33:33.
Matěj Zábský
3
Devuelve el valor máximo siempre. ¿Me estoy perdiendo algo aquí? : |
rohan-patel
15
rand()debería considerarse dañino en C ++, hay formas mucho mejores de obtener algo que esté distribuido de manera uniforme y realmente aleatorio.
Mgetz
1
¿Realmente devuelve un número correcto dentro del rango el 100% del tiempo? He encontrado alguna otra respuesta de stackoverflow aquí que usa la recursividad para hacerlo "de la manera correcta": stackoverflow.com/a/6852396/623622
Czarek Tomczak
2
Dado que es una respuesta muy votada (que es deseable), que parece una fuente confiable de información para muchos lectores nuevos, creo que es muy importante mencionar la calidad y los peligros potenciales de esta solución, así que hice una edición.
plasmacel
297

La respuesta más simple (y por lo tanto mejor) de C ++ (usando el estándar 2011) es

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

No hay necesidad de reinventar la rueda. No hay que preocuparse por el sesgo. No hay que preocuparse por usar el tiempo como semilla aleatoria.

Walter
fuente
1
Hoy en día esta debería ser la respuesta . Referencia de generación de números pseudoaleatorios para más funciones.
alextoind
8
Estoy de acuerdo con lo "más simple" (y lo más idiomático), no con lo "mejor". Lamentablemente, el Estándar no ofrece garantías random_device, lo que podría romperse por completo en algunos casos . Además, mt19937aunque es una muy buena opción de uso general, no es el más rápido de los generadores de buena calidad (vea esta comparación ) y, por lo tanto, podría no ser el candidato ideal para el OP.
Alberto M
1
@AlbertoM Desafortunadamente, la comparación a la que se refiere no proporciona suficientes detalles y no es reproducible, lo que la hace dudosa (además, es de 2015, mientras que mi respuesta se remonta a 2013). Bien puede ser cierto que existen mejores métodos (y con suerte en el futuro, minstdserá un método así), pero eso es un progreso. En cuanto a la implementación deficiente random_device, eso es horrible y debería considerarse un error (posiblemente también del estándar C ++, si lo permite).
Walter
1
Estoy totalmente de acuerdo contigo; En realidad, no quería criticar su solución per se , solo quería advertir al lector casual que la respuesta definitiva sobre el asunto, a pesar de las promesas de C ++ 11, aún no se ha escrito. Voy a publicar una descripción general del tema a partir de 2015 como respuesta a una pregunta relacionada .
Alberto M
1
Eso es "más simple"? ¿Podría explicar por qué lo mucho más simple rand()no es una opción, y es importante para un uso no crítico, como generar un índice pivote aleatorio? Además, ¿tengo que preocuparme por construir random_device/ mt19937/ uniform_int_distributionen un bucle cerrado / función en línea? ¿Debería preferir pasarlos?
bluenote10
60

Si su compilador admite C ++ 0x y usarlo es una opción para usted, <random>es probable que el nuevo encabezado estándar satisfaga sus necesidades. Tiene una alta calidad uniform_int_distributionque aceptará límites mínimos y máximos (inclusive según lo necesite), y puede elegir entre varios generadores de números aleatorios para conectarse a esa distribución.

Aquí hay un código que genera un millón de ints aleatorios distribuidos uniformemente en [-57, 365]. He utilizado las nuevas <chrono>instalaciones estándar para cronometrarlo , ya que mencionó que el rendimiento es una preocupación importante para usted.

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

Para mí (2.8 GHz Intel Core i5) esto imprime:

2.10268e + 07 números aleatorios por segundo.

Puede sembrar el generador pasando un int a su constructor:

    G g(seed);

Si más tarde descubre que intno cubre el rango que necesita para su distribución, esto puede remediarse cambiando uniform_int_distributionlo siguiente (por ejemplo, a long long):

    typedef std::uniform_int_distribution<long long> D;

Si más tarde descubre que minstd_randno es un generador de calidad suficientemente alta, eso también puede cambiarse fácilmente. P.ej:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

Tener un control separado sobre el generador de números aleatorios y la distribución aleatoria puede ser bastante liberador.

También calculé (no se muestran) los primeros 4 "momentos" de esta distribución (usando minstd_rand) y los comparé con los valores teóricos en un intento de cuantificar la calidad de la distribución:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

(El x_prefijo se refiere a "esperado")

Howard Hinnant
fuente
3
Esta respuesta podría usar un breve fragmento de código de resumen que muestra solo el código que realmente se necesita para generar un entero aleatorio a partir de un rango.
arekolek
El problema se facilita por el hecho de que min y max de la distribución nunca cambian. ¿Qué pasaría si tuviera que crear den cada iteración con límites diferentes? ¿Cuánto ralentizaría el ciclo?
quant_dev
16

Dividamos el problema en dos partes:

  • Genere un número aleatorio nen el rango de 0 a (max-min).
  • Agregue min a ese número

La primera parte es obviamente la más difícil. Supongamos que el valor de retorno de rand () es perfectamente uniforme. El uso de módulo agregará sesgo a los primeros (RAND_MAX + 1) % (max-min+1)números. Entonces, si pudiéramos cambiar mágicamente RAND_MAXa RAND_MAX - (RAND_MAX + 1) % (max-min+1), ya no habría ningún sesgo.

Resulta que podemos usar esta intuición si estamos dispuestos a permitir el pseudo-no determinismo en el tiempo de ejecución de nuestro algoritmo. Cada vez que rand () devuelve un número que es demasiado grande, simplemente pedimos otro número aleatorio hasta obtener uno que sea lo suficientemente pequeño.

El tiempo de ejecución ahora se distribuye geométricamente , con el valor esperado 1/pdonde pestá la probabilidad de obtener un número lo suficientemente pequeño en el primer intento. Como RAND_MAX - (RAND_MAX + 1) % (max-min+1)siempre es menor que (RAND_MAX + 1) / 2, lo sabemos p > 1/2, por lo que el número esperado de iteraciones siempre será menor que dos para cualquier rango. Debería ser posible generar decenas de millones de números aleatorios en menos de un segundo en una CPU estándar con esta técnica.

EDITAR:

Aunque lo anterior es técnicamente correcto, la respuesta de DSimon es probablemente más útil en la práctica. No deberías implementar estas cosas tú mismo. He visto muchas implementaciones de muestreo de rechazo y, a menudo, es muy difícil ver si es correcto o no.

Jørgen Fogh
fuente
Para completar: esta es la Muestra de rechazo .
etarion
3
Dato curioso: Joel Spolsky mencionó una vez una versión de esta pregunta como un ejemplo de lo que StackOverflow fue bueno para responder. Miré a través de las respuestas en el sitio de muestreo que implica el rechazo en ese momento y cada solo uno era incorrecta.
Jørgen Fogh
13

¿Qué tal el Mersenne Twister ? La implementación de impulso es bastante fácil de usar y está bien probada en muchas aplicaciones del mundo real. Lo he usado yo mismo en varios proyectos académicos, como inteligencia artificial y algoritmos evolutivos.

Aquí está su ejemplo donde hacen una función simple para lanzar un dado de seis lados:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

Ah, y aquí hay un poco más de proxenetismo de este generador en caso de que no esté convencido de que debería usarlo sobre el muy inferior rand():

El Mersenne Twister es un generador de "números aleatorios" inventado por Makoto Matsumoto y Takuji Nishimura; su sitio web incluye numerosas implementaciones del algoritmo.

Esencialmente, el Mersenne Twister es un registro de desplazamiento de retroalimentación lineal muy grande. El algoritmo funciona en una semilla de 19.937 bits, almacenada en una matriz de 624 elementos de enteros sin signo de 32 bits. El valor 2 ^ 19937-1 es un primo de Mersenne; La técnica para manipular la semilla se basa en un antiguo algoritmo de "torsión", de ahí el nombre de "Mersenne Twister".

Un aspecto atractivo del Mersenne Twister es su uso de operaciones binarias, en lugar de la multiplicación que consume mucho tiempo, para generar números. El algoritmo también tiene un período muy largo y buena granularidad. Es rápido y efectivo para aplicaciones no criptográficas.

Aphex
fuente
1
El tornado de Mersenne es un buen generador, pero el problema con el que está lidiando permanece, independientemente del generador subyacente.
Jerry Coffin
No quiero usar Boost solo para el generador aleatorio, porque (dado que mi proyecto es una biblioteca) significa introducir otra dependencia al proyecto. Probablemente me veré obligado a usarlo de todos modos en el futuro, para poder cambiar a este generador.
Matěj Zábský
1
@Jerry Coffin ¿Qué problema? Lo ofrecí porque cumplía con todos sus requisitos: es rápido, es uniforme (usando la boost::uniform_intdistribución), puedes transformar los rangos min max en lo que quieras, y es visible.
Aphex
@mzabsky Probablemente no dejaría que eso me detuviera, cuando tuve que enviar mis proyectos a mis profesores para su presentación, simplemente incluí los archivos de encabezado de impulso relevantes que estaba usando; No debería tener que empaquetar toda la biblioteca de impulso de 40 MB con su código. Por supuesto, en su caso, esto podría no ser factible por otros motivos, como los derechos de autor ...
Aphex
@Aphex Mi proyecto no es realmente un simulador científico o algo que necesita una distribución realmente uniforme. Utilicé el generador antiguo durante 1.5 años sin ningún problema, solo noté la distribución sesgada cuando la necesité por primera vez para generar números desde un rango muy pequeño (3 en este caso). Sin embargo, la velocidad sigue siendo un argumento para considerar la solución de impulso. Revisaré su licencia para ver si puedo agregar los pocos archivos necesarios a mi proyecto. Me gusta "Checkout -> F5 -> listo para usar" como está ahora.
Matěj Zábský
11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Esta es una asignación de 32768 enteros a enteros (nMax-nMin + 1). La asignación será bastante buena si (nMax-nMin + 1) es pequeño (como en su requisito). Sin embargo, tenga en cuenta que si (nMax-nMin + 1) es grande, la asignación no funcionará (por ejemplo, no puede asignar valores de 32768 a valores de 30000 con la misma probabilidad). Si se necesitan tales rangos, debe usar una fuente aleatoria de 32 o 64 bits, en lugar de los 15 bits rand (), o ignorar los resultados de rand () que están fuera de rango.

Lior Kogan
fuente
A pesar de su impopularidad, esto también es lo que uso para mis proyectos no científicos. Fácil de entender (no necesita un título en matemáticas) y se desempeña adecuadamente (nunca tuvo que perfilar ningún código que lo use). :) En el caso de rangos grandes, supongo que podríamos unir dos valores rand () y obtener un valor de 30 bits para trabajar (suponiendo RAND_MAX = 0x7fff, es decir, 15 bits aleatorios)
efotinis
cambie RAND_MAXa (double) RAND_MAXpara evitar la advertencia de desbordamiento de enteros.
alex
4

Aquí hay una versión imparcial que genera números en [low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

Si su rango es razonablemente pequeño, no hay razón para almacenar en caché el lado derecho de la comparación en el dobucle.

Jeremiah Willcock
fuente
En mi opinión, ninguna de las soluciones presentadas allí es realmente una gran mejora. Su solución basada en bucles funciona, pero es probable que sea bastante ineficiente, especialmente para un rango pequeño como el OP analiza. Su solución de desviación uniforme no produce desviaciones uniformes en absoluto. A lo sumo, de alguna manera camufla la falta de uniformidad.
Jerry Coffin
@Jerry: Por favor verifique la nueva versión.
Jeremiah Willcock
No estoy seguro de que funcione correctamente. Podría, pero la corrección no parece obvia, al menos para mí.
Jerry Coffin
@Jerry: Aquí está mi razonamiento: suponga que el rango es [0, h)por simplicidad. Llamar rand()tiene RAND_MAX + 1posibles valores de retorno; tomando rand() % hcolapsos (RAND_MAX + 1) / hde ellos a cada uno de los hvalores de salida, excepto que (RAND_MAX + 1) / h + 1estos se asignan a los valores que son menores que (RAND_MAX + 1) % h(debido al último ciclo parcial a través de las hsalidas). Por lo tanto, eliminamos (RAND_MAX + 1) % hposibles salidas para obtener una distribución imparcial.
Jeremiah Willcock
3

Recomiendo la biblioteca Boost.Random , es súper detallada y bien documentada, le permite especificar explícitamente qué distribución desea, y en escenarios no criptográficos puede realmente superar la implementación típica de una biblioteca C rand.

DSimon
fuente
1

suponga que min y max son valores int, [y] significa incluir este valor, (y) significa no incluir este valor, utilizando lo anterior para obtener el valor correcto utilizando c ++ rand ()

referencia: para () [] definir, visite:

https://en.wikipedia.org/wiki/Interval_(mathematics)

para la función rand y srand o RAND_MAX define, visite:

http://en.cppreference.com/w/cpp/numeric/random/rand

[mínimo máximo]

int randNum = rand() % (max - min + 1) + min

(mínimo máximo]

int randNum = rand() % (max - min) + min + 1

[mínimo máximo)

int randNum = rand() % (max - min) + min

(mínimo máximo)

int randNum = rand() % (max - min - 1) + min + 1
Huang Kun
fuente
0

En este hilo, el muestreo de rechazo ya se discutió, pero quería sugerir una optimización basada en el hecho de que rand() % 2^somethingno introduce ningún sesgo como ya se mencionó anteriormente.

El algoritmo es realmente simple:

  • calcular la potencia más pequeña de 2 mayor que la longitud del intervalo
  • aleatorizar un número en ese intervalo "nuevo"
  • devolver ese número si es menor que la longitud del intervalo original
    • rechazar de otra manera

Aquí está mi código de muestra:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

Esto funciona bien especialmente para intervalos pequeños, porque la potencia de 2 estará "más cerca" de la longitud real del intervalo, por lo que el número de fallos será menor.

PD:
Obviamente, evitar la recursión sería más eficiente (no es necesario calcular una y otra vez el límite máximo de registro ...) pero pensé que era más legible para este ejemplo.

Pado
fuente
0

Tenga en cuenta que, en la mayoría de las sugerencias, el valor aleatorio inicial que obtiene de la función rand (), que normalmente es de 0 a RAND_MAX, simplemente se desperdicia. Está creando solo un número aleatorio, mientras que hay un procedimiento de sonido que puede brindarle más.

Suponga que desea la región [min, max] de números aleatorios enteros. Comenzamos desde [0, max-min]

Tome la base b = max-min + 1

Comience por representar un número que obtuvo de rand () en la base b.

De esa manera, tiene piso (log (b, RAND_MAX)) porque cada dígito en la base b, excepto posiblemente el último, representa un número aleatorio en el rango [0, max-min].

Por supuesto, el cambio final a [min, max] es simple para cada número aleatorio r + min.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

Si NUM_DIGIT es el número de dígitos en la base b que puede extraer y eso es

NUM_DIGIT = floor(log(b,RAND_MAX))

entonces lo anterior es como una implementación simple de extraer NUM_DIGIT números aleatorios de 0 a b-1 de un número aleatorio RAND_MAX que proporciona b <RAND_MAX.

alex.peter
fuente
-1

La fórmula para esto es muy simple, así que prueba esta expresión,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
Sohail xIN3N
fuente
2
Todo el problema fue usar el rand de C / C ++ que devuelve un entero en un rango especificado por el tiempo de ejecución. Como se demostró en este hilo, mapear enteros aleatorios de [0, RAND_MAX] a [MIN, MAX] no es del todo sencillo, si desea evitar destruir sus propiedades estadísticas o rendimiento. Si tiene dobles en el rango [0, 1], la asignación es fácil.
Matěj Zábský
2
Su respuesta es incorrecta, debería usar el módulo en su lugar:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes
-2

La siguiente expresión debe ser imparcial si no me equivoco:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Asumo aquí que rand () le da un valor aleatorio en el rango entre 0.0 y 1.0 SIN incluir 1.0 y que max y min son enteros con la condición de que min <max.

Moritz
fuente
std::floordevuelve double, y necesitamos un valor entero aquí. Me gustaría echar en intlugar de usar std::floor.
musiphil