Wdrażałem hashap w C jako część projektu, nad którym pracuję i używam losowych wstawek do testowania go, gdy zauważyłem, że rand()
w Linuksie wydaje się powtarzać liczby znacznie częściej niż na Macu. RAND_MAX
to 2147483647 / 0x7FFFFFFF na obu platformach. Sprowadziłem go do tego programu testowego, który tworzy tablicę bajtów RAND_MAX+1
-długą, generuje RAND_MAX
liczby losowe, zauważa, czy każdy z nich jest duplikatem, i sprawdza go z listy, jak widać.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main() {
size_t size = ((size_t)RAND_MAX) + 1;
char *randoms = calloc(size, sizeof(char));
int dups = 0;
srand(time(0));
for (int i = 0; i < RAND_MAX; i++) {
int r = rand();
if (randoms[r]) {
// printf("duplicate at %d\n", r);
dups++;
}
randoms[r] = 1;
}
printf("duplicates: %d\n", dups);
}
Linux konsekwentnie generuje około 790 milionów duplikatów. Mac konsekwentnie generuje tylko jeden, więc zapętla każdą losową liczbę, którą może wygenerować prawie bez powtarzania. Czy ktoś może mi wyjaśnić, jak to działa? Nie mogę powiedzieć nic innego niż strony podręcznika, nie mogę powiedzieć, którego RNG używa i nie mogę znaleźć niczego online. Dzięki!
Odpowiedzi:
Choć na początku może się wydawać, że macOS
rand()
jest w jakiś sposób lepszy, jeśli nie powtarza żadnych liczb, należy zauważyć, że przy takiej liczbie wygenerowanych liczb oczekuje się dużej liczby duplikatów (w rzeczywistości około 790 milionów lub (2 31 -1) ) / e ). Podobnie powtarzanie liczb w sekwencji również nie spowodowałoby duplikatów, ale nie byłoby uważane za bardzo przypadkowe. Tak więcrand()
implementacja Linuksa jest w tym teście nie do odróżnienia od prawdziwego losowego źródła, podczas gdy macOSrand()
nie.Inną rzeczą, która wydaje się zaskakująca na pierwszy rzut oka, jest sposób, w jaki macOS
rand()
potrafi tak dobrze unikać duplikatów. Patrząc na jego kod źródłowy , stwierdzamy, że implementacja wygląda następująco:To rzeczywiście powoduje, że wszystkie liczby od 1 do
RAND_MAX
włącznie, dokładnie jeden raz, przed powtórzeniem sekwencji. Ponieważ następny stan opiera się na pomnożeniu, stan nigdy nie może wynosić zero (lub wszystkie przyszłe stany również będą zerowe). Zatem powtarzana liczba, którą widzisz, jest pierwsza, a zero to ta, która nigdy nie jest zwracana.Apple promuje stosowanie lepszych generatorów liczb losowych w ich dokumentacji i przykładach przez co najmniej tak długo, jak istnieje macOS (lub OS X), więc jakość
rand()
prawdopodobnie nie jest uważana za ważną i po prostu utknęli w jednym z najprostsze dostępne generatory pseudolosowe. (Jak już zauważyłeś, ichrand()
komentarz został nawet opatrzony zaleceniem użyciaarc4random()
).W pokrewnej uwadze, najprostszym generatorem liczb pseudolosowych, jaki udało mi się znaleźć, który daje przyzwoite wyniki w tym (i wielu innych) testach losowości, jest xorshift * :
Ta implementacja daje prawie dokładnie 790 milionów duplikatów w teście.
źródło
arc4random()
podobny kodrand()
i uzyskać dobryrand()
wynik. Zamiast próbować sterować programistami inaczej, po prostu twórz lepsze funkcje biblioteczne. „właśnie utknęli” to ich wybór.rand()
powoduje , że jest tak zły, że nie jest użyteczny w praktycznym użyciu: Dlaczego rand ()% 7 zawsze zwraca 0? , Rand ()% 14 generuje tylko wartości 6 lub 13rand
, aby ponowne uruchomienie go z tym samym materiałem siewnym wytworzyło tę samą sekwencję. OpenBSDrand
jest zepsuty i nie przestrzega tej umowy.rand()
przy tym samym nasieniu produkować tę samą sekwencję między różnymi wersjami biblioteki? Taka gwarancja może być przydatna do testowania regresji między wersjami bibliotek, ale nie znajduję w niej wymagań C.MacOS zapewnia nieudokumentowaną funkcję rand () w stdlib. Jeśli pozostawisz to bez nasion, pierwsze wartości, które wyśle to 16807, 282475249, 1622650073, 984943658 i 1144108930. Szybkie wyszukiwanie pokaże, że ta sekwencja odpowiada bardzo prostemu generatorowi liczb losowych LCG, który iteruje następującą formułę:
Ponieważ stan tego RNG jest opisany w całości przez wartość pojedynczej 32-bitowej liczby całkowitej, jego okres nie jest bardzo długi. Mówiąc ściślej, powtarza się co 2 31 - 2 iteracji, generując każdą wartość od 1 do 2 31 - 2.
Nie sądzę, aby istniała standardowa implementacja rand () dla wszystkich wersji Linuksa, ale często używana jest funkcja rand () glibc . Zamiast pojedynczej 32-bitowej zmiennej stanu wykorzystuje to pulę ponad 1000 bitów, która zgodnie ze wszystkimi celami i celami nigdy nie wytworzy w pełni powtarzalnej sekwencji. Ponownie, prawdopodobnie możesz dowiedzieć się, jaką wersję posiadasz, drukując kilka pierwszych wyników z tego RNG bez wcześniejszego uruchamiania. (Funkcja rand () glibc generuje liczby 1804289383, 846930886, 1681692777, 1714636915 i 1957747793.)
Powodem, dla którego masz więcej kolizji w Linuksie (a prawie wcale w MacOS) jest to, że wersja rand () Linuksa jest w zasadzie bardziej losowa.
źródło
rand()
muszą zachowywać się jak jeden zsrand(1);
rand()
macOS jest dostępny: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/... FWIW, uruchomiłem ten sam test w stosunku do tego skompilowanego ze źródła i rzeczywiście powoduje to tylko jeden duplikat. Apple promuje użycie innych generatorów liczb losowych (takich jakarc4random()
przed przejęciem Swift) w ich przykładach i dokumentacji, więc użycie ichrand()
prawdopodobnie nie jest bardzo powszechne w natywnych aplikacjach na ich platformach, co może wyjaśniać, dlaczego nie jest lepsze.rand()
był nieudokumentowany, ale @Arkku podał link do widocznego źródła. Czy któryś z was wie, dlaczego nie mogę znaleźć tego pliku w moim systemie i dlaczego widzę tylkoint rand(void) __swift_unavailable("Use arc4random instead.");
na komputerze Macstdlib.h
? Przypuszczam, że kod @Arkku, z którym jest połączony, jest po prostu wkompilowany w ... jaką bibliotekę?/usr/lib/libc.dylib
. =)rand()
danego zastosowania program C nie jest określana przez „kompilator” lub „system operacyjny”, ale raczej wdrożenie standardowej biblioteki C (na przykładglibc
,libc.dylib
,msvcrt*.dll
).rand()
jest zdefiniowany przez standard C, a standard C nie określa, którego algorytmu użyć. Oczywiście Apple używa gorszego algorytmu do implementacji GNU / Linux: Linuksa nie można odróżnić od prawdziwego losowego źródła w teście, podczas gdy implementacja Apple po prostu przetasowuje liczby.Jeśli chcesz losowych liczb dowolnej jakości, albo użyj lepszego PRNG, który daje przynajmniej pewne gwarancje jakości liczb, które zwraca, lub po prostu odczytaj z
/dev/urandom
lub podobny. Później daje kryptograficzne wartości jakości, ale jest powolny. Nawet jeśli sam jest zbyt wolny,/dev/urandom
może zapewnić doskonałe nasiona innym, szybszym PRNG.źródło
Ogólnie rzecz biorąc, para rand / srand była uważana za przestarzałą przez długi czas z powodu bitów niskiego rzędu, które wykazują mniej losowości niż bity wysokiego rzędu w wynikach. To może, ale nie musi mieć nic wspólnego z twoimi wynikami, ale myślę, że nadal jest to dobra okazja, aby pamiętać, że mimo iż niektóre implementacje rand / srand są teraz bardziej aktualne, starsze implementacje są nadal dostępne i lepiej używać losowych (3). ). W moim Arch Linuxie następująca uwaga jest wciąż na stronie podręcznika dla rand (3):
Tuż poniżej tego strona podręcznika podaje bardzo krótkie, bardzo proste przykładowe implementacje rand i srand, które dotyczą najprostszych LC RNG, jakie kiedykolwiek widziałeś i mają małą RAND_MAX. Nie sądzę, żeby pasowały do tego, co jest w standardowej bibliotece C, jeśli kiedykolwiek tak było. A przynajmniej mam nadzieję, że nie.
Zasadniczo, jeśli zamierzasz użyć czegoś ze standardowej biblioteki, użyj losowo, jeśli możesz (strona podręcznika wymienia to jako standard POSIX z powrotem do POSIX.1-2001, ale rand jest standardem znacznie wcześniej niż C został znormalizowany) . Albo jeszcze lepiej: otwórz przepisy numeryczne (lub poszukaj ich online) lub Knuth i zaimplementuj je. Są naprawdę łatwe i naprawdę musisz to zrobić tylko raz, aby mieć RNG ogólnego przeznaczenia z atrybutami, których najczęściej potrzebujesz i który ma znaną jakość.
źródło
rand()
ulepszenie oznaczałoby spowolnienie (co zapewne zrobiłoby - zabezpieczone kryptograficznie liczby losowe wymagają dużo wysiłku), prawdopodobnie lepiej jest zachować je szybko, nawet jeśli są nieco bardziej przewidywalne. Przykład: mieliśmy aplikację produkcyjną, której uruchomienie trwało wieki, którą prześledziliśmy do RNG, którego inicjalizacja musiała czekać na wygenerowanie wystarczającej entropii… Okazało się, że nie musiała być tak bezpieczna, więc zastąpiliśmy ją „gorszy” RNG był dużą poprawą.