Ten sposób sprawdzi się tylko w przypadku, gdy zakres jest potęgą dwójki.
To nie prawda, nadal ostatni zakres będzie rzadszy pod warunkiem typowego RAND_MAX czyli 32767.
Załóżmy, ze RAND_MAX=32767
Wymyślimy sobie zakres od 0 do 127, czyli 128 możliwych liczb. Liczba 128 jest potęgą dwójki. Sama funkcja rand() zwraca losową wartość z zakresu od 0 do 32767. Zakładamy, że implementacja samej funkcji rand() nie budzi zastrzeżeń, a więc generuje każda liczbę z takim samym prawdopodobieństwem.
Idziemy dalej. Liczba 32767 przeliczona na binarnie to 15 jedynek, a każda liczba od 1 do 32766 to ciąg składający się z zer i jedynek, jeżeli on jest krótszy niż 15 bitów, to dopisujemy zera z przodu. Okazuje się, że liczbę od 0 do 32767 można otrzymać poprzez 15 losowań binarnych (np. rzucanie monetą) i spisanie wyników tych losowań jako kolejnych bitów. Ponieważ jednej liczbie odpowiada jedna kombinacja bitów, to jej prawdopodobieństwo to 50% pomnożone 15 razy, czyli 0,5^15=1/32768
Chcemy liczby z zakresu od 0 do 127, czyli robimy N % 128
. W praktyce jest to to samo, co N & 127
, a więc w obu przypadkach z wylosowanej liczby bierzemy 7 ostatnich bitów, resztę odrzucamy. W tym przypadku jednej wylosowanej liczbie po odrzuceniu odpowiada dokładnie 256 liczb przed odrzuceniem, bo odrzucamy 8 bitów, których wartość nie jest ważna. Z tego wynika, że prawdopodobieństwo każdej liczby od 0 do 127 jest takie samo i wynosi 256/32768=1/128.
Równie dobrze można obliczać N >> 8
, bo odrzucamy 8 ostatnich bitów i zostaje 7 bitów, które były z przodu. Uogólniając, jeżeli dla każdego bitu wartość 1 uzyskuje się z prawdopodobieństwem 50%, to nie ma znaczenia, które bity będziemy odrzucać, a które zostawiać, ważne, żeby odrzucić 8 bitów i zostawić 7 bitów.
Czy moje rozumowanie jest poprawne?
Poza tym, ustalanie zakresu poprzez resztę z dzielenia zaburza równomierność losowanego ciągu (pomijam kwestię jakości samej funkcji rand, która jednak w 95% zastosowań jest wystarczająco dobra, ale w niektórych już nie bardzo). Ten sposób sprawdzi się tylko w przypadku, gdy zakres jest potęgą dwójki.
Lepiej coś takiego:
X = (((double)rand()/(double)(RAND_MAX)) * Range);
https://pl.wikibooks.org/wiki/C/srand
https://stackoverflow.com/questions/1202687/how-do-i-get-a-specific-range-of-numbers-from-rand
Czemu uważasz że przy losowaniu cyfr rzadsze trafienie cyfr 3, 6 jest lepszym rozwiązaniem niż rzadsze trafienie cyfr 7,8?
Ba czy wiesz o jaki procent tej różnicy się rozwodzisz?
W podanym przykładzie masz double
a wylosować trzeba cyfrę, z prawdopodobieństwem 99% nie dasz rady poprawnie zastosować to co proponujesz dla cyfr.
Niektóre samouczki podają wybór zakresu jako N % K
, a inne, takie lepsze, podają, ze to nie jest dobry sposób, bo zakłóca równomierność. Prosty przykład, niech zakres wynosi 0-9999, czyli będzie N % 10000
.
Ponieważ N może przyjąć zakres od 0 do 32767, wylosowaliśmy 2 liczby:
Liczba 2000, czyli N mógł mieć jedną z tych wartości {2000, 12000, 22000, 32000}
Liczba 3000, czyli N mógł mieć jedną z tych wartości {3000, 13000, 23000} - liczba 33000 już nie może być, bo przekracza zakres
Z powyższego wynika, że prawdopodobieństwo wylosowania 2000 wynosi 4/32768, a prawdopodobieństwo wylosowania 3000 wynosi 3/32768, czyli prawdopodobieństwa się różnią i już w tym momencie mamy potwierdzone, że N % 10000
zaburzyło równomierność i potrzebny jest inny sposób.
Nie napisałem, że uważam, że lepsze jest rzadsze lub częstsze trafienie konkretnych cyfr. Napisałem tylko, żeby odwieźć od wciąż podawanego w tutorialach N % K
jako sposób na zakres. A to, jakie znaczenie ma to zaburzenie równomierności to już inny temat. W projekcikach studenckich to może nie ma, ale w poważniejszych to już może mieć.
A z tym double
, to pierwszy lepszy przykład z Google, jak obsłużyć wskazany zakres losowania bez użycia modulo. Teraz jeżeli mamy liczbę ułamkową z zakresu od 0 do 32767/32768 z równomiernym prawdopodobieństwem, to prawdopodobieństwo nie zmieni się, jak pomnoży się to przez stałą liczbę ułamkową. Jedynym źródłem zaburzeń mogą być ograniczenia liczb zmiennoprzecinkowych i zaokrąglenia.
Czy dobrze rozumuję? A jeżeli uważasz, że zamiana na double
i mnożenie przez stałą double
również nie jest dobrym sposobem, to poproszę sposób, który jest dobry dla języka C, bo do C++ jest biblioteka <random>
, która zastępuje rand()
z C.