Generación aleatoria de números flotantes


271

¿Cómo genero flotantes aleatorios en C ++?

Pensé que podría tomar el rand entero y dividirlo por algo, ¿sería suficiente?


2
Depende más bien para qué desea el número y qué tan aleatorio. típicamente rand () dará 15 bits de aleatoriedad, pero los flotantes tienen una precisión de 23 bits, por lo que perderá algunos valores.
Pete Kirkham el

1
He actualizado mi respuesta para incluir todas las opciones principales disponibles y mi elección de centrarme en el randomencabezado agregado en C ++ 11 se ve reforzada por el documento estándar N3924: Desalentador rand () en C ++ 14 . Incluyo rand()en mi respuesta por consideraciones principalmente históricas, pero también me doy cuenta de que existen aplicaciones heredadas.
Shafik Yaghmour

Mi respuesta incluye cómo evitar obtener los mismos números cada vez con el <random>encabezado
Andreas DM

Respuestas:


381

rand()se puede usar para generar números pseudoaleatorios en C ++. En combinación con RAND_MAXun poco de matemática, puede generar números aleatorios en cualquier intervalo arbitrario que elija. Esto es suficiente para fines de aprendizaje y programas de juguetes. Si necesita números verdaderamente aleatorios con distribución normal, deberá emplear un método más avanzado.


Esto generará un número de 0.0 a 1.0, inclusive.

float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);

Esto generará un número desde 0,0 hasta cierto arbitraria float, X:

float r2 = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX/X));

Esto generará un número de algún arbitrario LOa otro arbitrario HI:

float r3 = LO + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(HI-LO)));

Tenga en cuenta que la rand()función a menudo no será suficiente si necesita números verdaderamente aleatorios.


Antes de llamar rand(), primero debe "inicializar" el generador de números aleatorios llamando srand(). Esto debe hacerse una vez durante la ejecución de su programa, no una vez cada vez que llame rand(). Esto a menudo se hace así:

srand (static_cast <unsigned> (time(0)));

Para llamar rando sranddebes hacerlo #include <cstdlib>.

Para llamar time, debes hacerlo #include <ctime>.


22
¡No olvides sembrar primero!
Klaim

14
Es mejor tener en cuenta que ambos límites son inclusivos.
dmckee --- gatito ex moderador

14
Esta respuesta es engañosa. Fue cubierto en Going Native 2013 la semana pasada; rand () Considerado dañino, channel9.msdn.com/Events/GoingNative/2013/… para una explicación muy detallada.
Ade Miller

14
No entiendo por qué tanta gente votó por esta respuesta. Es matemáticamente incorrecto. RAND_MAX es un número muy pequeño (normalmente 2 ^ 16). Eso significa que a partir de 23 bits del punto flotante, solo se hacen 15 al azar. Los otros serán probablemente cero. De hecho, obtendrá números aleatorios en distribución uniforme pero de baja precisión. Por ejemplo, su generador aleatorio puede generar 0.00001 y 0.00002 pero no puede generar 0.000017. Entonces tiene una distribución uniforme pero de baja precisión (256 veces menos precisión que el punto flotante real).
DanielHsH

10
@DanielHsH: El OP preguntó específicamente con qué mecánica se podría utilizar para generar flotadores aleatorios rand(). Esta pregunta, y mi respuesta, se centró específicamente en aprender lo básico y no le preocupaban los altos grados de precisión. Tienes que aprender a caminar antes de poder aprender a correr.
John Dibling

137

C ++ 11 le ofrece muchas opciones nuevas con random. El artículo canónico sobre este tema sería N3551, Generación de números aleatorios en C ++ 11

Para ver por qué usar rand() puede ser problemático, consulte el material de presentación rand () considerado perjudicial por Stephan T. Lavavej dado durante el evento GoingNative 2013 . Las diapositivas están en los comentarios, pero aquí hay un enlace directo .

También cubro boosty utilizo, randya que el código heredado aún puede requerir su soporte.

El siguiente ejemplo se extrae del sitio cppreference y utiliza el motor std :: mersenne_twister_engine y el std :: uniform_real_distribution que genera números en el [0,10)intervalo, con otros motores y distribuciones comentadas a cabo ( ver en directo ):

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

int main()
{
    std::random_device rd;

    //
    // Engines 
    //
    std::mt19937 e2(rd());
    //std::knuth_b e2(rd());
    //std::default_random_engine e2(rd()) ;

    //
    // Distribtuions
    //
    std::uniform_real_distribution<> dist(0, 10);
    //std::normal_distribution<> dist(2, 2);
    //std::student_t_distribution<> dist(5);
    //std::poisson_distribution<> dist(2);
    //std::extreme_value_distribution<> dist(0,2);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::floor(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 salida será similar a la siguiente:

0 ****
1 ****
2 ****
3 ****
4 *****
5 ****
6 *****
7 ****
8 *****
9 ****

La salida variará dependiendo de la distribución que elija, por lo que si decidimos ir con std :: normal_distribution con un valor de 2tanto para media y STDDEV por ejemplo, dist(2, 2)en lugar de la salida sería similar a esta ( ver en directo ):

-6 
-5 
-4 
-3 
-2 **
-1 ****
 0 *******
 1 *********
 2 *********
 3 *******
 4 ****
 5 **
 6 
 7 
 8 
 9 

La siguiente es una versión modificada de algunos de los códigos presentados en N3551 ( ver en vivo ):

#include <algorithm>
#include <array>
#include <iostream>
#include <random>

std::default_random_engine & global_urng( )
{
    static std::default_random_engine u{};
    return u ;
}

void randomize( )
{
    static std::random_device rd{};
    global_urng().seed( rd() );
}

int main( )
{
  // Manufacture a deck of cards:
  using card = int;
  std::array<card,52> deck{};
  std::iota(deck.begin(), deck.end(), 0);

  randomize( ) ;  

  std::shuffle(deck.begin(), deck.end(), global_urng());
  // Display each card in the shuffled deck:
  auto suit = []( card c ) { return "SHDC"[c / 13]; };
  auto rank = []( card c ) { return "AKQJT98765432"[c % 13]; };

  for( card c : deck )
      std::cout << ' ' << rank(c) << suit(c);

   std::cout << std::endl;
}

Los resultados serán similares a:

5H 5S AS 9S 4D 6H TH 6D KH 2S QS 9H 8H 3D KC TD 7H 2D KS 3C TC 7D 4C QH QC QD JD AH JC AC KD 9D 5C 2H 4H 9C 8C JH 5D 4S 7C AD 3S 8S TS 2C 8D 3H 6C JS 7S 6S

Aumentar

Por supuesto, Boost.Random también es siempre una opción, aquí estoy usando boost :: random :: uniform_real_distribution :

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

int main()
{
    boost::random::mt19937 gen;
    boost::random::uniform_real_distribution<> dist(0, 10);

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

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

rand ()

Si debe usarlo rand(), podemos ir a las Preguntas frecuentes de C para obtener una guía sobre ¿Cómo puedo generar números aleatorios de punto flotante? , que básicamente da un ejemplo similar a este para generar un intervalo[0,1) :

#include <stdlib.h>

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 / (N-M) ) ) ;  
}

1
¿Puedes arreglar tus randMToNpls? tenga en cuenta que es [M,N]o agregue de nuevo el + 1.de lo anterior randZeroToOne. -> piensa en llamarlo así:randMToN(0.0, 1.0);
BeyelerStudios

1
También tenga cuidado con la división por cero en (N-M). Aquí se encuentra una buena manera de lidiar con este error: stackoverflow.com/questions/33058848/…
Dr Beco

61

Echa un vistazo a Boost.Random . Podrías hacer algo como esto:

float gen_random_float(float min, float max)
{
    boost::mt19937 rng;
    boost::uniform_real<float> u(min, max);
    boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > gen(rng, u);
    return gen();
}

Juega un poco, es mejor que pases el mismo objeto mt19937 en lugar de construir uno nuevo cada vez, pero espero que entiendas la idea.


1
uniform_real usa un intervalo medio abierto [min, max), lo que significa que obtendrá su valor mínimo pero nunca alcanzará el valor máximo. Es algo a considerar, aunque si redondeas de alguna manera, puedes superar este problema.
TehWan

20
Esto ahora es parte de C ++ 11.
Tomás Andrle

@Wolf en aplicaciones prácticas, las probabilidades de alcanzar cualquier valor específico de coma flotante son tan bajas que no importa si el punto final está incluido o excluido. Si necesita max, pero puede utilizar una participación abierta min, puede invertir el intervalo fácilmente: return min + max - gen();.
Mark Ransom

26

En moderno c++, puede usar el <random>encabezado que viene con c++11.
Para obtener aleatorios float, puedes usarstd::uniform_real_distribution<> .

Puede usar una función para generar los números y, si no desea que los números sean iguales todo el tiempo, configure el motor y la distribución static.
Ejemplo:

float get_random()
{
    static std::default_random_engine e;
    static std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1
    return dis(e);
}

Es ideal para colocar el float's en un contenedor como std::vector:

int main()
{
    std::vector<float> nums;
    for (int i{}; i != 5; ++i) // Generate 5 random floats
        nums.emplace_back(get_random());

    for (const auto& i : nums) std::cout << i << " ";
}

Salida de ejemplo:

0.0518757 0.969106 0.0985112 0.0895674 0.895542

std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1es técnicamente incorrecto, nunca se generará 1.0, consulte en.cppreference.com/w/cpp/numeric/random/… To create a distribution over the closed interval [a,b], std::nextafter(b, std::numeric_limits<RealType>::max()) may be used as the second parameter.
Troyseph

1
Esta debería ser la respuesta aceptada, está volviendo loco 2020.
Alex

25

Llame al código con dos floatvalores, el código funciona en cualquier rango.

float rand_FloatRange(float a, float b)
{
    return ((b - a) * ((float)rand() / RAND_MAX)) + a;
}

Vale la pena mencionar que este es un caso de uso potencial para fmaf()(o la fma()sobrecarga flotante en C ++) en C99 o C ++ 11, lo que podría preservar más precisión. Como en fmaf((float)rand() / RAND_MAX, b - a, a).
Tim Čas

22

Si está utilizando C ++ y no C, recuerde que en el informe técnico 1 (TR1) y en el borrador de C ++ 0x han agregado funciones para un generador de números aleatorios en el archivo de encabezado, creo que es idéntico al Boost. Biblioteca aleatoria y definitivamente más flexible y "moderna" que la función de biblioteca C, rand.

Esta sintaxis ofrece la posibilidad de elegir un generador (como el mersenne twister mersenne mt19937) y luego elegir una distribución (normal, bernoulli, binomial, etc.).

La sintaxis es la siguiente (descaradamente prestado de este sitio ):

  #include <iostream>
  #include <random>

  ...

  std::tr1::mt19937 eng;  // a core engine class 
  std::tr1::normal_distribution<float> dist;     

  for (int i = 0; i < 10; ++i)        
      std::cout << dist(eng) << std::endl;

2
Esto ahora está en C ++ 11, también dist se puede inicializar con valores mínimos y máximos.
Étienne

Me parece extraño poner el mínimo y el máximo en el inicializador y proporcionar el generador al obtener un valor; preferiría que fuera al revés, bueno.
yoyo

6

En algunos sistemas (Windows con VC me RAND_MAXviene a la mente, actualmente), es ridículamente pequeño, i. mi. solo 15 bit. Al dividir porRAND_MAX solo está generando una mantisa de 15 bits en lugar de los 23 bits posibles. Esto puede o no ser un problema para usted, pero está perdiendo algunos valores en ese caso.

Oh, acabo de notar que ya había un comentario para ese problema. De todos modos, aquí hay un código que podría resolver esto por usted:

float r = (float)((rand() << 15 + rand()) & ((1 << 24) - 1)) / (1 << 24);

No probado, pero podría funcionar :-)


¿Qué pasa con float r = (float) ((rand () << 9) | rand ()) / RAND_MAX? (también no probado)
Trampa

Argh, lo siento, dividir por RAND_MAX no te llevará a ninguna parte ... el objetivo de este truco era tener algo más grande que RAND_MAX ... también lo arregló para mí.
Joey

2
Tenga cuidado al componer números aleatorios sin teoría ... las llamadas consecutivas a rand () podrían no ser completamente independientes. Sugerencia: si es un generador congruencial lineal, observe el bit bajo en llamadas consecutivas: alterna entre 0 y 1.
RBerteig el

Lo sé. Sin embargo, para algunas aplicaciones esto podría ser suficiente. Pero sí, probablemente debería usar más de dos llamadas en este caso. No hay una bala de plata en este caso, ni siquiera puede confiar en que sea un LCG. Otros PRNG tienen bits altos débiles. La solución Boost debería ser la mejor aquí.
Joey

(nb: el bit bajo devuelto por rand en MSVC no es el bit más bajo del estado RNG. Para las llamadas de 100 rand () obtengo lo siguiente: 1100100000111111101010010010011010101110110110111010011111100100000000010100011011000000100101100011. Java usa un LCG de 48 bits, y solo usa 32 bits de LCG, solo parece usar 32 bits V LCC y solo usa 32 bits de LCG. hacerlo de manera similar)
Joey


2

No estaba satisfecho con ninguna de las respuestas hasta ahora, así que escribí una nueva función flotante aleatoria. Hace suposiciones bit a bit sobre el tipo de datos flotante. Todavía necesita una función rand () con al menos 15 bits aleatorios.

//Returns a random number in the range [0.0f, 1.0f).  Every
//bit of the mantissa is randomized.
float rnd(void){
  //Generate a random number in the range [0.5f, 1.0f).
  unsigned int ret = 0x3F000000 | (0x7FFFFF & ((rand() << 8) ^ rand()));
  unsigned short coinFlips;

  //If the coin is tails, return the number, otherwise
  //divide the random number by two by decrementing the
  //exponent and keep going. The exponent starts at 63.
  //Each loop represents 15 random bits, a.k.a. 'coin flips'.
  #define RND_INNER_LOOP() \
    if( coinFlips & 1 ) break; \
    coinFlips >>= 1; \
    ret -= 0x800000
  for(;;){
    coinFlips = rand();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    //At this point, the exponent is 60, 45, 30, 15, or 0.
    //If the exponent is 0, then the number equals 0.0f.
    if( ! (ret & 0x3F800000) ) return 0.0f;
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
    RND_INNER_LOOP(); RND_INNER_LOOP(); RND_INNER_LOOP();
  }
  return *((float *)(&ret));
}

77
enfoque interesante, me gustaría upvote, pero, realmente no entiendo lo que está pasando
Hasen

2

En mi opinión, la respuesta anterior da un flotador 'aleatorio', pero ninguno de ellos es realmente un flotador aleatorio (es decir, pierden una parte de la representación del flotador). Antes de apresurarme en mi implementación, primero echemos un vistazo al formato estándar ANSI / IEEE para flotadores:

| signo (1 bit) | e (8 bits) | f (23 bits) |

el número representado por esta palabra es (-1 * signo) * 2 ^ e * 1.f

tenga en cuenta que el número 'e' es un número sesgado (con un sesgo de 127) que oscila entre -127 y 126. La función más simple (y en realidad más aleatoria) es simplemente escribir los datos de un int aleatorio en un flotante, así

int tmp = rand();
float f = (float)*((float*)&tmp);

tenga en cuenta que si lo hace float f = (float)rand();, convertirá el entero en un flotante (por lo tanto, 10 se convertirá en 10.0).

Entonces, si desea limitar el valor máximo, puede hacer algo como (no estoy seguro si esto funciona)

int tmp = rand();
float f = *((float*)&tmp);
tmp = (unsigned int)f       // note float to int conversion!
tmp %= max_number;
f -= tmp;

pero si observa la estructura del flotador, puede ver que el valor máximo de un flotador es (aprox.) 2 ^ 127, que es mucho mayor que el valor máximo de un int (2 ^ 32), lo que descarta una parte significativa de Los números que pueden ser representados por un flotador. Esta es mi implementación final:

/**
 * Function generates a random float using the upper_bound float to determine 
 * the upper bound for the exponent and for the fractional part.
 * @param min_exp sets the minimum number (closest to 0) to 1 * e^min_exp (min -127)
 * @param max_exp sets the maximum number to 2 * e^max_exp (max 126)
 * @param sign_flag if sign_flag = 0 the random number is always positive, if 
 *              sign_flag = 1 then the sign bit is random as well
 * @return a random float
 */
float randf(int min_exp, int max_exp, char sign_flag) {
    assert(min_exp <= max_exp);

    int min_exp_mod = min_exp + 126;

    int sign_mod = sign_flag + 1;
    int frac_mod = (1 << 23);

    int s = rand() % sign_mod;  // note x % 1 = 0
    int e = (rand() % max_exp) + min_exp_mod;
    int f = rand() % frac_mod;

    int tmp = (s << 31) | (e << 23) | f;

    float r = (float)*((float*)(&tmp));

    /** uncomment if you want to see the structure of the float. */
//    printf("%x, %x, %x, %x, %f\n", (s << 31), (e << 23), f, tmp, r);

    return r;
}

El uso de esta función randf(0, 8, 0)devolverá un número aleatorio entre 0.0 y 255.0


1
tienes un error rand ()% frac_mod no funcionará ya que MAX_RAND suele ser inferior a (1 << 23).
DanielHsH

Tengo que admitir que no sé el tamaño exacto de MAX_RAND. Sin embargo, seguirá funcionando, quizás sea una afirmación inútil, pero seguirá funcionando. 8% 10 = 8, así que está bien, pero si MAX_RAND siempre es más pequeño, entonces (1 << 23) puede eliminarlo.
user2546926

2
No, estás un poco equivocado. RandMax es típicamente ~ 65,000. Eso significa que a partir de 23 bits solo se obtienen 15 al azar. Los otros serán probablemente cero. De hecho, obtendrá números aleatorios pero de baja precisión. Por ejemplo, su generador aleatorio puede generar 0.001 y 0.002 pero no puede generar 0.0017. Entonces tiene una distribución uniforme pero de baja precisión (256 veces menos precisión que el flotador).
DanielHsH

Hay dos errores en esta respuesta. Arreglando el rango de exponente: int e = (rand() % (max_exp - min_exp)) + min_exp_mod;y la mantisa: int f = (int)(frac_mod * (float)rand() / RAND_MAX);reemplazando sus respectivas líneas arriba. Tenga en cuenta que el error de mantisa es importante: ¡para los RAND_MAXmás pequeños 1 << 23solo aleatorizaría los bits significativos más bajos y obtendría ceros para los bits más significativos todo el tiempo!
BeyelerStudios

2

Si sabe que su formato de coma flotante es IEEE 754 (casi todas las CPU modernas, incluidas Intel y ARM), puede construir un número aleatorio de coma flotante a partir de un entero aleatorio utilizando métodos basados ​​en bits. Esto solo debe considerarse si no tiene acceso a C ++ 11 randomo Boost.Randomambos son mucho mejores.

float rand_float()
{
    // returns a random value in the range [0.0-1.0)

    // start with a bit pattern equating to 1.0
    uint32_t pattern = 0x3f800000;

    // get 23 bits of random integer
    uint32_t random23 = 0x7fffff & (rand() << 8 ^ rand());

    // replace the mantissa, resulting in a number [1.0-2.0)
    pattern |= random23;

    // convert from int to float without undefined behavior
    assert(sizeof(float) == sizeof(uint32_t));
    char buffer[sizeof(float)];
    memcpy(buffer, &pattern, sizeof(float));
    float f;
    memcpy(&f, buffer, sizeof(float));

    return f - 1.0;
}

Esto dará una mejor distribución que una que usa división.


8
No estoy seguro de por qué estás diciendo que esto daría una "mejor distribución". De hecho, esto dará exactamente la misma distribución que solo return (float)random23 / (1 << 23). (Sí, acabo de probar esto , modificando su función para tomarla random32como parámetro y ejecutándola para todos los valores desde cero hasta (1 << 23)-1. Y sí, su método realmente da exactamente los mismos resultados que la división por 1 << 23.)
Ilmari Karonen

1

Para C ++, puede generar números flotantes reales dentro del rango especificado por la distvariable

#include <random>  //If it doesnt work then use   #include <tr1/random>
#include <iostream>

using namespace std;

typedef std::tr1::ranlux64_base_01 Myeng; 
typedef std::tr1::normal_distribution<double> Mydist;

int main() { 
       Myeng eng; 
       eng.seed((unsigned int) time(NULL)); //initializing generator to January 1, 1970);
       Mydist dist(1,10); 

       dist.reset(); // discard any cached values 
       for (int i = 0; i < 10; i++)
       {
           std::cout << "a random value == " << (int)dist(eng) << std::endl; 
       }

       return (0);
}

1
¿Acabas de copiar y pegar el código de esta respuesta? stackoverflow.com/a/1118739/1538531
Derek

En realidad No. ¡Estoy un poco sorprendido de ver cuánto se parecen! Pero inicialicé motor-generador el 1 de enero de 1970.
Marco167

Lo suficientemente justo. Me di cuenta de que inicializaste el generador a la época, ¡pero ese código es similar!
Derek

Me resulta un poco extraño dar un ejemplo TR1, ¿puede explicar en qué casos alguien tendría que usar TR1 en lugar de C ++ 11?
Shafik Yaghmour

0

rand () devuelve un int entre 0 y RAND_MAX. Para obtener un número aleatorio entre 0.0 y 1.0, primero arroje el retorno int por rand () a un flotante, luego divida por RAND_MAX.


0
#include <cstdint>
#include <cstdlib>
#include <ctime>

using namespace std;

/* single precision float offers 24bit worth of linear distance from 1.0f to 0.0f */
float getval() {
    /* rand() has min 16bit, but we need a 24bit random number. */
    uint_least32_t r = (rand() & 0xffff) + ((rand() & 0x00ff) << 16);
    /* 5.9604645E-8 is (1f - 0.99999994f), 0.99999994f is the first value less than 1f. */
    return (double)r * 5.9604645E-8;
}

int main()
{
    srand(time(NULL));
...

No pude publicar dos respuestas, así que aquí está la segunda solución. log2 números aleatorios, sesgo masivo hacia 0.0f pero es realmente un flotador aleatorio de 1.0f a 0.0f.

#include <cstdint>
#include <cstdlib>
#include <ctime>

using namespace std;

float getval () {
    union UNION {
        uint32_t i;
        float f;
    } r;
    /* 3 because it's 0011, the first bit is the float's sign.
     * Clearing the second bit eliminates values > 1.0f.
     */
    r.i = (rand () & 0xffff) + ((rand () & 0x3fff) << 16);
    return r.f;
}

int main ()
{
    srand (time (NULL));
...
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.