O código a seguir tem como objetivo gerar uma lista de cinco números pseudo-aleatórios no intervalo [1.100]. Eu semeio o default_random_engine
com time(0)
, que retorna a hora do sistema em tempo unix . Quando eu compilo e executo este programa no Windows 7 usando o Microsoft Visual Studio 2013, ele funciona como esperado (veja abaixo). Quando faço isso no Arch Linux com o compilador g ++, no entanto, ele se comporta de maneira estranha.
No Linux, 5 números serão gerados a cada vez. Os últimos 4 números serão diferentes em cada execução (como geralmente será o caso), mas o primeiro número permanecerá o mesmo.
Exemplo de resultado de 5 execuções no Windows e Linux:
| Windows: | Linux:
---------------------------------------
Run 1 | 54,01,91,73,68 | 25,38,40,42,21
Run 2 | 46,24,16,93,82 | 25,78,66,80,81
Run 3 | 86,36,33,63,05 | 25,17,93,17,40
Run 4 | 75,79,66,23,84 | 25,70,95,01,54
Run 5 | 64,36,32,44,85 | 25,09,22,38,13
Para aumentar o mistério, esse primeiro número incrementa periodicamente em um no Linux. Depois de obter os resultados acima, esperei cerca de 30 minutos e tentei novamente para descobrir que o primeiro número havia mudado e agora estava sempre sendo gerado como 26. Ele continuou a aumentar em 1 periodicamente e agora está em 32. Parece corresponder com a alteração do valor de time(0)
.
Por que o primeiro número raramente muda entre as execuções e, quando muda, aumenta em 1?
O código. Ele imprime perfeitamente os 5 números e a hora do sistema:
#include <iostream>
#include <random>
#include <time.h>
using namespace std;
int main()
{
const int upper_bound = 100;
const int lower_bound = 1;
time_t system_time = time(0);
default_random_engine e(system_time);
uniform_int_distribution<int> u(lower_bound, upper_bound);
cout << '#' << '\t' << "system time" << endl
<< "-------------------" << endl;
for (int counter = 1; counter <= 5; counter++)
{
int secret = u(e);
cout << secret << '\t' << system_time << endl;
}
system("pause");
return 0;
}
sizeof(time_t)
vs.sizeof(default_random_engine::result_type)
?default_random_engine
é completamente diferente nessas duas plataformas.Respostas:
Aqui está o que está acontecendo:
default_random_engine
em libstdc ++ (biblioteca padrão do GCC) éminstd_rand0
, que é um mecanismo congruencial linear simples:A forma como este mecanismo gera números aleatórios é x i + 1 = (16807x i + 0) mod 2147483647.
Portanto, se as sementes forem diferentes em 1, na maioria das vezes o primeiro número gerado será diferente em 16807.
O intervalo deste gerador é [1, 2147483646]. A forma como o libstdc ++
uniform_int_distribution
mapeia para um inteiro no intervalo [1, 100] é essencialmente esta: gere um númeron
. Se o número não for maior que 2147483600, retorne(n - 1) / 21474836 + 1
; caso contrário, tente novamente com um novo número.Deve ser fácil ver que na grande maioria dos casos, dois
n
s que diferem por apenas 16807 produzirão o mesmo número em [1,100] sob este procedimento. Na verdade, seria de se esperar que o número gerado aumentasse em um a cada 21474836/16807 = 1278 segundos ou 21,3 minutos, o que concorda muito bem com suas observações.O MSVC
default_random_engine
émt19937
, que não tem esse problema.fonte
rand()
% 7 sempre retornar 0rand()
é algo compreensível (é uma porcaria de legado sem esperança). Usar um PRNG de nível de merda para algo novo é imperdoável. Eu até consideraria isso uma violação do padrão, já que o padrão exige "fornecer pelo menos um comportamento de motor aceitável para uso relativamente casual, inexperiente e / ou leve". que esta implementação não fornece, uma vez que falha catastroficamente, mesmo para casos de uso triviais como o seurand % 7
exemplo.rand()
compreensível exatamente? É apenas porque ninguém poderia ter pensado em fazer isso?srand
é muito pequena para gerar facilmente sementes únicas. 3) Ele retorna um número inteiro com um limite superior definido pela implementação que o chamador tem de reduzir de alguma forma para um número na faixa desejada, o que quando feito corretamente é mais trabalhoso do que escrever uma substituição com uma API sã pararand()
4) Ele usa o estado mutável globalA
std::default_random_engine
implementação é definida. Usestd::mt19937
ou em seustd::mt19937_64
lugar.Além disso,
std::time
e asctime
funções não são muito precisas, use os tipos definidos no<chrono>
cabeçalho:fonte
std::random_device
vez de current_time para semear seu gerador aleatório. Por favor, verifique qualquer exemplo de cppreference sobre Random.ctime
é 1 segundo. A granularidade dasstd::chrono
implementações é definida pelo usuário, padronizando para, parastd::high_resolution_clock
(no Visual Studio é um typedef parastd::steady_clock
) nanossegundos, mas pode escolher uma medida muito menor, portanto, muito mais precisa.No Linux, a função aleatória não é uma função aleatória no sentido probabilístico da maneira, mas um gerador de números pseudo-aleatórios. É salgado com uma semente e, com base nessa semente, os números produzidos são pseudo-aleatórios e uniformemente distribuídos. A maneira Linux tem a vantagem de que, no projeto de certos experimentos usando informações de populações, a repetição do experimento com ajustes conhecidos de informações de entrada pode ser medida. Quando o programa final está pronto para o teste da vida real, o sal (semente) pode ser criado pedindo ao usuário para mover o mouse, misturar o movimento do mouse com algumas teclas e adicionar um traço de contagem de microssegundos desde o início de a última energia ligada.
A semente de números aleatórios do Windows é obtida a partir da coleção de números de mouse, teclado, rede e hora do dia. Não é repetível. Mas esse valor de sal pode ser redefinido para uma semente conhecida, se, como mencionado acima, alguém estiver envolvido no planejamento de um experimento.
Sim, o Linux tem dois geradores de números aleatórios. Um, o padrão é o módulo 32bits e o outro é o módulo 64bits. Sua escolha depende das necessidades de precisão e da quantidade de tempo de computação que você deseja consumir para o seu teste ou uso real.
fonte
collection of mouse, keyboard, network and time of day numbers