Já falei sobre o LCG (Linear Congruential Generator) e outros algoritmos como o Mersene Twster e o XorShift128+. Todos eles são “pseudo” geradores de valores aleatórios, já que não é possível gerar valores aleatórios com métodos determinísticos. Aliás, gerar valores aleatórios é um troço bem difícil porque a maioria dos fenômenos físicos tendem a oferecer um padrão, dada uma quantidade razoável de amostras. Por exemplo, se você jogar a mesma moeda, num jogo de “cara ou coroa”, digamos, 1 milhão de vezes, observará que a quantidade de “caras” ou “coroas” é maior que a outra. Ou seja, você pode prever, com maior segurança (embora não exatamente) qual será o resultado da próxima jogada…
De fato, até onde sei, os únicos fenômenos aleatórios são ao nível quântico (variação de fuga de carga num semnicondutor aquecido, por exemplo… Ou o decaimento radioativo num intervalo de tempo pequeno o suficiente)… De fato, também já falei por aqui, um desses fenômenos é usado, hoje em dia, para disponibilizar um DTRNG (Digital True Random Number Generator), presente dentro de todos os processadores da família Intel (pelo menos nos Ivy Bridge em diante) e acessíveis através da instrução RDRAND.
Mas, note: Mesmo que o LCG e outros métodos não criem valores verdadeiramente aleatórios, num escopo restrito e onde não há preocupação com a “aleatoriedade absoluta”, eles podem ser encacrados como geradores aleatórios reais. Abaixo, mostro um programinha que cria uma imagem de 1024×1024 pixels (RGB) com base na função rand() da biblioteca de C:
#include <stdio.h> #include <stdlib.h> #include <time.h> // Scale 'n' to 24 bits (0xBBGGRR). #define rand2rgb(n) \ ( ( int ) ( ( (n) * 16777215.0 ) / RAND_MAX ) ) int main ( void ) { int x, y, r; srand( time(NULL) ); fputs( "P6\n1024 1024\n255\n", stdout ); for ( y = 0; y < 1024; y++ ) for ( x = 0; x < 1024; x++ ) { r = rand2rgb(rand()); fwrite( &r, 3, 1, stdout ); // assume little endian. } }
Compilando e executando:
$ cc -O2 -o random random.c $ ./random > rand1.ppm
Obtemos um arquivo rand1.ppm assim:
Recentemente vi alguém, num forum, tentando implementar seu próprio PRNG na rotina mcc_rnd(), abaixo:
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <limits.h> // Scale n to 24 bit value (0xBBGGRR). // OBS: Uses long double because 'long' is 64 bits long in x86-64 systems. #define rand2rgb(n) \ ( ( int ) ( ( (n) * 16777215.0L ) / LONG_MAX ) ) // using size_t instead of ptrdiff_t (same thing) typedef size_t mcc_rnd_t; static long mcc_rnd ( mcc_rnd_t *seed, long min, long max ); int main ( void ) { int x, y; int r; long seed = 1; fputs ( "P6\n1024 1024\n255\n", stdout ); for ( y = 0; y < 1024; y++ ) for ( x = 0; x < 1024; x++ ) { r = rand2rgb ( mcc_rnd ( &seed, 0, LONG_MAX ) ); fwrite ( &r, 3, 1, stdout ); } } long mcc_rnd ( mcc_rnd_t *seed, long min, long max ) { // Most of the time tmp will point to the SAME memory block // for every process. Notice that, probably, tmp is aligned, // meaning the lower bits aren't 'random' at all... // and the upper bits isn't random as well... void *tmp = malloc ( 1 ); long val = time ( NULL ) + ( size_t ) tmp; free ( tmp ); // This isn't 'random' seed. Taking the argument address means // it will be copied to the stack and the stack pointer isn't // that 'random'... The value on stack isn't that unpredictable // as you think! if ( ! seed ) seed = ( mcc_rnd_t * ) ( &seed ); // Well... this isn't random, again, isn't it? if ( ! *seed ) *seed = 1; val %= *seed; val *= clock(); // The seed, again, isn't random! *seed <<= 1; // Saturation get even less random values! return ( val > max ) ? max : ( val < min ? min : val ); }
Os comentários na função mcc_rnd() são meus. A rotina tem várias falhas:
- malloc() retorna um endereço no virtual address space, que tende a ser o mesmo para todos os processos;
- Dependendo da velocidade com que a rotina é chamada, time() e clock() retornam o mesmo valor;
- clock() não mede a quantidade de ciclos de clock do processador, mas tem uma granulidade muito maior. Mesmo que medisse, teríamos valores praticamente sequenciais;
- A semente (*seed), usada como divisor, é tudo menos aleatória;
- A “saturação”, no caso de valores “fora da faixa” torna a rotina não aleatória!
De fato, compilando e executando, obtemos uma imagem assim:
Além da maioria dos valores serem zero, os pixels coloridos seguem padrões específicos. Notem os muitos pixels vermelhos e os gradientes do verde para o azul nos “blocos” acima…
Bem… minha dica: Não tente inventar um PRNG na base da tentativa e erro ou assumindo que certos “fenômenos” são aleatórios…
Você precisa fazer login para comentar.