2010-06-14 8 views
10

Podczas badania dla this question i odczytywania kodu źródłowego w random.py, zacząłem się zastanawiać, czy randrange i randint naprawdę zachowują się jak "reklamowane". Jestem bardzo skłonny wierzyć tak, ale sposób w jaki ją przeczytać, randrange zasadniczo realizowane jakoCzy random.Randint (1,10) kiedykolwiek zwróci 11?

start + int(random.random()*(stop-start)) 

(przy założeniu wartości całkowitych dla start i stop), więc randrange(1, 10) powinna zwrócić liczbę losową z przedziału od 1 do 9.

randint(start, stop) dzwoni randrange(start, stop+1), tym samym zwracając liczbę od 1 do 10.

Moje pytanie brzmi teraz:

Jeśli random() kiedykolwiek zwrócą 1.0, to randint(1,10) zwróci 11, prawda?

+0

Nawiasem mówiąc, 'int (random.random() * n)' nadal nie jest idealnym sposobem na generowanie liczb całkowitych, które są równomiernie rozmieszczone w 'zakresie (n)'; istnieje odchylenie, które jest nieistotne dla małego 'n', ale staje się znaczące gdy' n' staje się duże. Otworzyłem błąd w języku Python na http://bugs.python.org/issue9025 –

+0

@ Mark Dickinson: Dzięki! To fascynujące. –

+0

@ Mark Dickinson: [Ten błąd został naprawiony od dzisiaj] (http://docs.python.org/dev/whatsnew/3.2.html#random). –

Odpowiedz

26

Od random.py i docs:

"""Get the next random number in the range [0.0, 1.0).""" 

) wskazuje, że przedział jest wyłącznym 1.0. Oznacza to, że nigdy nie zwróci 1.0.

Jest to ogólna konwencja w matematyce [ i ] jest włącznie, natomiast ( i ) jest wyłączna, a dwa rodzaje nawiasie można mieszać jak (a, b] lub [a, b). Zajrzyj do wikipedia: Interval (mathematics), aby uzyskać formalne wyjaśnienie.

+0

Nie złapałem tego ')' (a nawet gdybym miał, nie wiedziałbym, co to znaczy, dlatego bardzo dziękuję za tę wnikliwą odpowiedź). –

+1

@Tim: FYI, istnieje kilka różnych konwencji. Inną powszechnie stosowaną konwencją jest odwracanie klamr kwadratowych, aby "[a, b [" byłaby półotwartym przedziałem równoważnym "[a, b)". –

+5

To nie wystarczy, ponieważ nie jest oczywiste, że '0.0 <= x <1.0' implikuje, że' 0 <= x * n

3

z dokumentacji Pythonie

prawie wszystkie funkcje modułu podstawowej funkcji losowej(), która generuje zmiennoprzecinkowe w półotwarty zakresie [0,0; 1,0).

Jak prawie każdy PRNG liczb zmiennoprzecinkowych ..

12

Inne odpowiedzi wskazywali, że wynik jest zawsze random() ściśle mniej niż 1.0; to jednak tylko połowa historii.

Jeśli obliczeniowej randrange(n) jako int(random() * n), ty również muszą wiedzieć, że za każdym Python pływaka x spełniającej 0.0 <= x < 1.0 i każda dodatnia n, to prawda, że ​​0.0 <= x * n < n, tak że int(x * n) jest ściśle mniej niż n.

Są dwie rzeczy, które mogą pójść źle: po pierwsze, kiedy obliczyć x * n, n jest niejawnie konwertowane na float. Dla wystarczająco dużych n, ta konwersja może zmienić wartość.Ale jeśli spojrzysz na źródło Pythona, zobaczysz, że używa on tylko metody int(random() * n) dla mniejszej niż (tutaj i poniżej zakładam, że platforma używa IEEE 754), która jest zakresem, w którym konwersja n na pływak gwarantuje się, że nie straci informacji (ponieważ n może być reprezentowany dokładnie tak jak float).

Drugą rzeczą, która może się nie udać, jest to, że wynik mnożenia x * n (który obecnie jest wykonywany jako efekt pływaków, pamiętaj) prawdopodobnie nie będzie dokładnie reprezentowany, więc będzie zaistnieć pewne zaokrąglenie. Jeśli wartość parametru x jest wystarczająco zbliżona do wartości 1.0, możliwe jest, że zaokrąglenie obejmie wynik równy wartości n.

Aby zobaczyć, że tak się nie stanie, musimy wziąć pod uwagę tylko największą możliwą wartość dla x, która jest (na prawie wszystkich komputerach, na których działa Python) 1 - 2**-53. Musimy więc pokazać, że (1 - 2**-53) * n < n dla naszej pozytywnej liczby całkowitej n, ponieważ zawsze będzie prawdą, że random() * n <= (1 - 2**-53) * n.

Dowód (schemat) Niech k być unikalną k liczbą całkowitą tak, że 2**(k-1) < n <= 2**k. Następnie następna zmiana z n to n - 2**(k-53). Musimy pokazać, że n*(1-2**53) (tj. Rzeczywista, niezaokrąglona wartość produktu) jest bliższa n - 2**(k-53) niż n, więc zawsze będzie zaokrąglana w dół. Ale mała arytmetyka pokazuje, że odległość od n*(1-2**-53) do n wynosi 2**-53 * n, natomiast odległość od n*(1-2**-53) do n - 2**(k-53) to (2**k - n) * 2**-53. Ale 2**k - n < n (ponieważ wybraliśmy k więc tym 2**(k-1) < n), dzięki czemu produkt jest bliżej n - 2**(k-53), więc będzie się zaokrągla się w dół (przy założeniu, że to, że platforma robi jakąś formę okrągły do ​​najbliższego) .

Jesteśmy więc bezpieczni. Uff!


Dodatek (4.7.2015) Powyższy zakłada IEEE 754 binary64 arytmetycznych, z okrągłym-tie-do-parzystym trybie zaokrąglania. Na wielu maszynach to założenie jest dość bezpieczne. Jednak na maszynach x86, które używają FPU x87 dla zmiennoprzecinkowej (na przykład różne smaki 32-bitowego systemu Linux), istnieje możliwość double rounding w mnożeniu, co umożliwia random() * n okrążenie w górę na n w przypadku, gdy random() zwraca największą możliwą wartość. Najmniejszy taki n, dla którego może się to zdarzyć, to n = 2049. Zobacz dyskusję pod numerem http://bugs.python.org/issue24546, aby uzyskać więcej informacji.

Powiązane problemy