¿Cuál es la mejor manera de generar bools aleatorios?


Necesito generar valores booleanos aleatorios en una ruta crítica de rendimiento.

El código que escribí para esto es

std::random_device   rd;
std::uniform_int_distribution<> randomizer(0, 1);
const int val randomizer(std::mt19937(rd()));
const bool isDirectionChanged = static_cast<bool>(val);

Pero no creo que esta sea la mejor manera de hacer esto, ya que no me gusta hacerlo static_cast<bool>.

En la web he encontrado algunas soluciones más

1. std::bernoulli_distribution

2. bool randbool = rand() & 1; Recuerde llamar srand() al principio.

Author: Community, 2016-02-12

9 answers

Para fines de rendimiento, a un precio de menos "aleatoriedad" que, por ejemplo, std::mt19937_64, puede usar Xorshift+ para generar números de 64 bits y luego usar los bits de esos números como booleanos pseudo-aleatorios.

Citando la Wikipedia:

Este generador es uno de los generadores más rápidos que pasa BigCrush

Detalles: http://xorshift.di.unimi.it / . Hay una tabla de comparación en el centro de la página, que muestra que mt19937_64 es 2 veces más lento y es sistemática.

A continuación se muestra el código de ejemplo (el código real debe envolverlo en una clase):

#include <cstdint>
#include <random>
using namespace std;

random_device rd;
/* The state must be seeded so that it is not everywhere zero. */
uint64_t s[2] = { (uint64_t(rd()) << 32) ^ (rd()),
    (uint64_t(rd()) << 32) ^ (rd()) };
uint64_t curRand;
uint8_t bit = 63;

uint64_t xorshift128plus(void) {
    uint64_t x = s[0];
    uint64_t const y = s[1];
    s[0] = y;
    x ^= x << 23; // a
    s[1] = x ^ y ^ (x >> 17) ^ (y >> 26); // b, c
    return s[1] + y;
}

bool randBool()
{
    if(bit >= 63)
    {
        curRand = xorshift128plus();
        bit = 0;
        return curRand & 1;
    }
    else
    {
        bit++;
        return curRand & (1<<bit);
    }
}
 39
Author: Serge Rogatch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-12 12:53:01

Algunos puntos de referencia rápidos (código):

   647921509 RandomizerXorshiftPlus
   821202158 BoolGenerator2 (reusing the same buffer)
  1065582517 modified Randomizer
  1130958451 BoolGenerator2 (creating a new buffer as needed)
  1140139042 xorshift128plus
  2738780431 xorshift1024star
  4629217068 std::mt19937
  6613608092 rand()
  8606805191 std::bernoulli_distribution
 11454538279 BoolGenerator
 19288820587 std::uniform_int_distribution

Para aquellos que quieren código listo para usar, les presento XorShift128PlusBitShifterPseudoRandomBooleanGenerator, una versión modificada de RandomizerXorshiftPlus del enlace anterior. En mi máquina, es casi tan rápido como la solución de @SergeRogatch, pero consistentemente alrededor de 10-20% más rápido cuando el conteo de bucles es alto (≳100,000), y hasta ~30% más lento con recuentos de bucles más pequeños.

class XorShift128PlusBitShifterPseudoRandomBooleanGenerator {
public:
  bool randBool() {
    if (counter == 0) {
      counter = sizeof(GeneratorType::result_type) * CHAR_BIT;
      random_integer = generator();
    }
    return (random_integer >> --counter) & 1;
  }

private:
  class XorShift128Plus {
  public:
    using result_type = uint64_t;

    XorShift128Plus() {
      std::random_device rd;
      state[0] = rd();
      state[1] = rd();
    }

    result_type operator()() {
      auto x = state[0];
      auto y = state[1];
      state[0] = y;
      x ^= x << 23;
      state[1] = x ^ y ^ (x >> 17) ^ (y >> 26);
      return state[1] + y;
    }

  private:
    result_type state[2];
  };

  using GeneratorType = XorShift128Plus;

  GeneratorType generator;
  GeneratorType::result_type random_integer;
  int counter = 0;
};
 18
Author: emlai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-13 17:13:43

Una forma sería simplemente generar un unsigned long long por cada 64 llamadas aleatorias como se indica en los comentarios. Un ejemplo:

#include <random>
class Randomizer
{
public:
    Randomizer() : m_rand(0), counter(0), randomizer(0, std::numeric_limits<unsigned long long>::max()) {}

    bool RandomBool()
    {
        if (!counter)
        {
            m_rand = randomizer(std::mt19937(rd()));
            counter = sizeof(unsigned long long) * 8;

        }
        return (m_rand >> --counter) & 1;
    }
private:
    std::random_device  rd;
    std::uniform_int_distribution<unsigned long long> randomizer;
    unsigned long long m_rand;
    int counter;
};
 10
Author: Sombrero Chicken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-12 09:32:23

Rellenaría previamente un búfer (lo suficientemente largo) (circular) de valores aleatorios de 64 bits, y luego tomaría muy rápidamente un bit a la vez cuando necesite un valor aleatorio booleano

#include <stdint.h>

class BoolGenerator {
  private:
  const int BUFFER_SIZE = 65536;
  uint64_t randomBuffer[BUFFER_SIZE];
  uint64_t mask;
  int counter;

  void advanceCounter {
    counter++;
    if (counter == BUFFER_SIZE) {
        counter = 0;
    }
  }

  public:
  BoolGenerator() {
    //HERE FILL YOUR BUFFER WITH A RANDOM GENERATOR
    mask = 1;
    counter = 0;
  }

  bool generate() {
    mask <<= 1;
    if (!mask) { //After 64 shifts the mask becomes zero
        mask = 1;//reset mask
        advanceCounter();//get the next value in the buffer
    }
    return randomBuffer[counter] & mask;
  }
}

Por supuesto, la clase se puede hacer general para el tamaño del búfer, el generador aleatorio, el tipo base (no necesariamente tiene que ser uint64_t), etc.


Accediendo al buffer solo una vez cada 64 llamadas:

#include <stdint.h> //...and much more

class BoolGenerator {
  private:
  static const int BUFFER_SIZE = 65536;
  uint64_t randomBuffer[BUFFER_SIZE];
  uint64_t currValue;
  int bufferCounter;
  int bitCounter;

  void advanceBufferCounter() {
    bufferCounter++;
    if (bufferCounter == BUFFER_SIZE) {
        bufferCounter = 0;
    }
  }

  void getNextValue() {
      currValue = randomBuffer[bufferCounter];
      bitCounter = sizeof(uint64_t) * 8;
      advanceBufferCounter();
  }

  //HERE FILL YOUR BUFFER WITH A RANDOM GENERATOR
  void initializeBuffer() {
  //Anything will do, taken from here: http://stackoverflow.com/a/19728404/2436175
      std::random_device rd;
      std::mt19937 rng(rd());
      std::uniform_int_distribution<uint64_t> uni(0,std::numeric_limits<uint64_t>::max());
      for (int i = 0; i < BUFFER_SIZE; i++ ) {
          randomBuffer[i] = uni(rng);
      }
  }

  public:
  BoolGenerator() {
      initializeBuffer();
      bufferCounter = 0;
      getNextValue();
  }

  bool generate() {
      if (!bitCounter) {
           getNextValue();
      }
      //A variation of other methods seen around
      bitCounter--;
      bool retVal = currValue & 0x01;
      currValue >>= 1;
      return retVal;
  }
};
 4
Author: Antonio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-13 16:08:54

A menos que tenga más restricciones sobre la aleatoriedad que necesita, la forma más rápida de generar un bool aleatorio es:

bool RandomBool() { return false; }

Para ser más específico, hay miles de formas de generar números booleanos aleatorios, todos satisfaciendo diferentes restricciones, y muchos de ellos no entregan números aleatorios "verdaderamente" (eso incluye todas las otras respuestas hasta ahora). La palabra "aleatorio" por sí sola no le dice a nadie qué propiedades realmente necesita.

 3
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-12 18:23:55

Si el rendimiento es su único criterio, entonces la respuesta es:

bool get_random()
{
    return true; // chosen by fair coin flip.
                 // guaranteed to be random.
}

Desafortunadamente, la entropía de este número aleatorio es cero, pero el rendimiento es bastante rápido.

Dado que sospecho que este generador de números aleatorios no es muy útil para usted, necesitará cuantificar qué tan aleatorio desea que sean sus booleanos. ¿Qué tal un ciclo de 2048? Un millón? 2^19937-1? Hasta el fin del universo?

Sospecho que, ya que explícitamente declaró que el rendimiento es su máxima preocupación, entonces un buen generador congruente lineal a la antigua podría ser "lo suficientemente bueno". Basado en este artículo , supongo que el período de este generador está alrededor 32*((2^31)-5), o alrededor de 68 billones de iteraciones. Si eso no es" lo suficientemente bueno", puede agregar cualquier generador compatible con C++11 que desee en lugar de minstd_rand.

Para obtener crédito adicional, y un pequeño éxito de rendimiento, modifique el siguiente código para usar el algoritmo de moneda sesgada para eliminar el sesgo en el generador.

#include <iostream>
#include <random>

bool get_random()
{
    typedef std::minstd_rand generator_type;
    typedef generator_type::result_type result_type;

    static generator_type generator;
    static unsigned int bits_remaining = 0;
    static result_type random_bits;

    if ( bits_remaining == 0 )
    {
        random_bits = generator();
        bits_remaining = sizeof( result_type ) * CHAR_BIT - 1;
    }

    return ( ( random_bits & ( 1 << bits_remaining-- ) ) != 0 );
}

int main()
{
    for ( unsigned int i = 0; i < 1000; i++ )
    {
        std::cout << " Choice " << i << ": ";
        if ( get_random() )
            std::cout << "true";
        else
            std::cout << "false";

        std::cout << std::endl;
    }
}
 1
Author: johnwbyrd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-17 02:41:11

Si el rendimiento es importante, tal vez sea una buena idea generar un número aleatorio de 32 bits y usar cada bit separado de él, algo como esto:

bool getRandBool() {
    static uint32_t randomnumber;
    static int i=0;
    if (i==0) {
        randomnumber = <whatever your favorite randonnumbergenerator is>;
        i=32;
    }
    return (randomnumber & 1<<--i); 
 }

De esta manera la generación solo impacta cada llamada 32

 0
Author: Tommylee2k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-12 09:23:18

Creo que la mejor manera es un uso de matriz aleatoria precalculada:

uint8_t g_rand[UINT16_MAX];
bool InitRand()
{
    for (size_t i = 0, n = UINT16_MAX; i < n; ++i)
        g_rand[i] = ::rand() & 1;
    return true;
}
bool g_inited = InitRand();
inline const uint8_t * Rand()
{
    return g_rand + (::rand()&INT16_MAX);
}

Se usa para llenar algún array dst [size]:

const size_t size = 10000;
bool dst[size];
for (size_t i = 0; i < size; i += INT16_MAX)
     memcpy(dst + i, Rand(), std::min<size_t>(INT16_MAX, size - col));

Por supuesto, puede inicializar una matriz pre-calculada con el uso de otra función aleatoria.

 0
Author: ErmIg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-12 09:29:48

Aparentemente tengo que agregar otra respuesta. Acabo de descubrir que a partir de Ivy Bridge arquitectura Intel agregó RdRand la instrucción de CPU y AMD la agregó más tarde en junio de 2015. Entonces, si está apuntando a un procesador que es lo suficientemente nuevo y no le importa usar ensamblado (en línea), la forma más rápida de generar bools aleatorios debería ser llamando a RdRand Instrucciones de CPU para obtener un número aleatorio de 64 bits como se describe aquí (desplácese aproximadamente a la mitad de la página para ver el código ejemplos) (en ese enlace también hay un ejemplo de código para comprobar la compatibilidad de la CPU actual con la instrucción RdRand, y ver también la Wikipedia para una explicación de cómo hacer esto con la instrucción CPUID), y luego usar los bits de ese número para booleanos como se describe en mi Respuesta basada en Xorshit+ .

 0
Author: Serge Rogatch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:33:23