Najbardziej podstawowym problemem twojej aplikacji testowej jest to, że dzwonisz srand
raz, a potem dzwonisz rand
jeden raz i kończysz.
Głównym celem srand
funkcji jest zainicjowanie sekwencji liczb pseudolosowych pseudolosowych losowym ziarnem.
Oznacza to, że jeśli przejdą taką samą wartość , aby srand
w dwóch różnych aplikacji (z tym samym srand
/ rand
realizacji) wtedy dostaniesz dokładnie tę samą sekwencję zrand()
wartości odczytu po czym w obu aplikacjach.
Jednak w Twojej przykładowej aplikacji sekwencja pseudolosowa składa się tylko z jednego elementu - pierwszego elementu pseudolosowej sekwencji wygenerowanej z ziarna równego aktualnemu czasowi second
precyzji. Czego spodziewasz się wtedy zobaczyć na wyjściu?
Oczywiście, gdy zdarzy ci się uruchomić aplikację w tej samej sekundzie - używasz tej samej wartości początkowej - więc twój wynik jest oczywiście taki sam (o czym Martin York wspomniał już w komentarzu do pytania).
Właściwie powinieneś zadzwonić srand(seed)
jeden raz, a potem zadzwonić rand()
wiele razy i przeanalizować tę sekwencję - powinna wyglądać losowo.
EDYTOWAĆ:
Oh, rozumiem. Z pozoru opis słowny to za mało (może bariera językowa czy coś… :)).
DOBRZE. Przykład staromodnego kodu C oparty na tych samych srand()/rand()/time()
funkcjach, które zostały użyte w pytaniu:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main(void)
{
unsigned long j;
srand( (unsigned)time(NULL) );
for( j = 0; j < 100500; ++j )
{
int n;
/* skip rand() readings that would make n%6 non-uniformly distributed
(assuming rand() itself is uniformly distributed from 0 to RAND_MAX) */
while( ( n = rand() ) > RAND_MAX - (RAND_MAX-5)%6 )
{ /* bad value retrieved so get next one */ }
printf( "%d,\t%d\n", n, n % 6 + 1 );
}
return 0;
}
^^^ TO sekwencja z jednego przebiegu programu ma wyglądać przypadkowe.
EDYCJA2:
Korzystając z biblioteki standardowej C lub C ++, ważne jest, aby zrozumieć, że w chwili obecnej nie ma ani jednej standardowej funkcji ani klasy, która ostatecznie tworzy faktycznie losowe dane (gwarantowane przez standard). Jedynym standardowym narzędziem, które rozwiązuje ten problem, jest std :: random_device, które niestety nadal nie daje gwarancji faktycznej losowości.
W zależności od charakteru aplikacji należy najpierw zdecydować, czy naprawdę potrzebujesz naprawdę losowych (nieprzewidywalnych) danych. Godny uwagi przypadek, w którym z całą pewnością potrzebujesz prawdziwej przypadkowości jest bezpieczeństwo informacji - np. Generowanie kluczy symetrycznych, asymetrycznych kluczy prywatnych, wartości soli, tokenów bezpieczeństwa itp.
Jednak liczby losowe o stopniu bezpieczeństwa to osobna branża warta osobnego artykułu.
W większości przypadków generator liczb pseudolosowych jest wystarczający - np. Do symulacji naukowych lub gier. W niektórych przypadkach konsekwentnie zdefiniowana sekwencja pseudolosowa jest nawet wymagana - np. W grach możesz zdecydować się na generowanie dokładnie takich samych map w czasie wykonywania, aby uniknąć przechowywania dużej ilości danych.
Oryginalne pytanie i powtarzająca się mnogość identycznych / podobnych pytań (a nawet wiele błędnych „odpowiedzi” na nie) wskazują, że przede wszystkim ważne jest, aby odróżnić liczby losowe od liczb pseudolosowych ORAZ zrozumieć, czym jest sekwencja liczb pseudolosowych w Po pierwsze ORAZ zdać sobie sprawę, że generatory liczb pseudolosowych NIE są używane w taki sam sposób, jak można używać prawdziwych generatorów liczb losowych.
Intuicyjnie, gdy zażądasz liczby losowej - zwracany wynik nie powinien zależeć od wcześniej zwróconych wartości i nie powinien zależeć od tego, czy ktoś wcześniej o coś poprosił i nie powinien zależeć w jakim momencie i przez jaki proces, na jakim komputerze i z jakiego generatora o jaką galaktykę został poproszony. To w końcu oznacza słowo „losowy” - bycie nieprzewidywalnym i niezależnym od wszystkiego - w przeciwnym razie nie jest już przypadkowe, prawda? Mając taką intuicję, naturalne jest przeszukiwanie sieci w poszukiwaniu jakichś magicznych zaklęć, które można rzucić, aby uzyskać taką losową liczbę w dowolnym możliwym kontekście.
^^^ TEN rodzaj intuicyjnych oczekiwań jest BARDZO NIEPRAWIDŁOWY i szkodliwy we wszystkich przypadkach związanych z generatorami liczb pseudolosowych liczb pseudolosowych - mimo że są rozsądne dla prawdziwych liczb losowych.
Chociaż istnieje sensowne pojęcie „liczby losowej” - nie ma czegoś takiego jak „liczba pseudolosowa”. Pseudo-Random Number Generator faktycznie produkuje liczb pseudolosowych sekwencji .
Kiedy eksperci mówią o jakości PRNG, w rzeczywistości mówią o statystycznych właściwościach wygenerowanej sekwencji (i jej godnych uwagi podsekwencji). Na przykład, jeśli połączysz dwa wysokiej jakości PRNG, używając ich obu na zmianę - możesz wytworzyć złą wynikową sekwencję - mimo że generują one dobre sekwencje każda z osobna (te dwie dobre sekwencje mogą po prostu korelować ze sobą, a zatem źle się łączyć).
Sekwencja pseudolosowa jest w rzeczywistości zawsze deterministyczna (z góry określona przez jej algorytm i parametry początkowe), tj. Właściwie nie ma w niej nic losowego.
W szczególności rand()
/ srand(s)
para funkcji zapewnia pojedynczą sekwencję liczb pseudolosowych na proces, która nie jest bezpieczna dla wątków (!), Generowaną za pomocą algorytmu zdefiniowanego w ramach implementacji. Funkcja rand()
generuje wartości w zakresie [0, RAND_MAX]
.
Cytat ze standardu C11:
srand
Funkcja używa argumentu jako nasiona do nowej sekwencji liczb pseudo-losowych być zwrócony przez kolejnych wywołań rand
. Jeśli
srand
zostanie wywołany z tą samą wartością początkową, sekwencja liczb pseudolosowych zostanie powtórzona. Jeśli rand
zostanie wywołana przed jakimikolwiek wywołaniami, które srand
miały zostać wykonane, zostanie wygenerowana ta sama sekwencja, jak przy srand
pierwszym wywołaniu z wartością początkową 1.
Wiele osób słusznie spodziewa się, że rand()
dałoby to sekwencję półniezależnych, równomiernie rozłożonych liczb w zakresie 0
do RAND_MAX
. Cóż, zdecydowanie powinno (w przeciwnym razie jest bezużyteczne), ale niestety nie tylko standard tego nie wymaga - istnieje nawet wyraźne zastrzeżenie, które stwierdza, że „nie ma gwarancji co do jakości wyprodukowanej sekwencji losowej” . W niektórych przypadkach historycznych rand
/srand
wdrożenie było rzeczywiście bardzo złej jakości. Chociaż w nowoczesnych wdrożeniach jest najprawdopodobniej wystarczająco dobry - ale zaufanie jest zepsute i niełatwe do odzyskania. Poza tym, że nie jest bezpieczny dla wątków, jego bezpieczne użycie w aplikacjach wielowątkowych jest trudne i ograniczone (nadal jest możliwe - możesz po prostu używać ich z jednego dedykowanego wątku).
Nowy szablon klasy std :: mersenne_twister_engine <> (i jego wygodne typy czcionek - std::mt19937
/ std::mt19937_64
z dobrą kombinacją parametrów szablonu) zapewnia per-obiekt pseudolosowych generator liczb zdefiniowane w standardzie C ++ 11. Z tymi samymi parametrami szablonu i tymi samymi parametrami inicjalizacji różne obiekty będą generować dokładnie tę samą sekwencję wyjściową dla każdego obiektu na dowolnym komputerze w dowolnej aplikacji zbudowanej przy użyciu biblioteki standardowej zgodnej z C ++ 11. Zaletą tej klasy jest przewidywalna sekwencja wyjściowa o wysokiej jakości i pełna spójność między implementacjami.
Istnieje również więcej silników PRNG zdefiniowanych w standardzie C ++ 11 - std :: linear_congruential_engine <> (historycznie używany jako srand/rand
algorytm uczciwej jakości w niektórych implementacjach bibliotek standardowych C) i std :: subtract_with_carry_engine <> . Generują również w pełni zdefiniowane zależne od parametrów sekwencje wyjściowe na obiekt.
Współczesny przykład C ++ 11 zastępujący przestarzały kod C powyżej:
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
// seed value is designed specifically to make initialization
// parameters of std::mt19937 (instance of std::mersenne_twister_engine<>)
// different across executions of application
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
for( unsigned long j = 0; j < 100500; ++j )
/* ^^^Yes. Generating single pseudo-random number makes no sense
even if you use std::mersenne_twister_engine instead of rand()
and even when your seed quality is much better than time(NULL) */
{
std::mt19937::result_type n;
// reject readings that would make n%6 non-uniformly distributed
while( ( n = gen() ) > std::mt19937::max() -
( std::mt19937::max() - 5 )%6 )
{ /* bad value retrieved so get next one */ }
std::cout << n << '\t' << n % 6 + 1 << '\n';
}
return 0;
}
Wersja poprzedniego kodu, która używa std :: uniform_int_distribution <>
#include <iostream>
#include <chrono>
#include <random>
int main()
{
std::random_device rd;
std::mt19937::result_type seed = rd() ^ (
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()
).count() +
(std::mt19937::result_type)
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
).count() );
std::mt19937 gen(seed);
std::uniform_int_distribution<unsigned> distrib(1, 6);
for( unsigned long j = 0; j < 100500; ++j )
{
std::cout << distrib(gen) << ' ';
}
std::cout << '\n';
return 0;
}