Istnieją dwa oczywiste sposoby generowania losowej cyfry od 0 do 9 w Pythonie. Można wygenerować losową liczbę zmiennoprzecinkową między 0 a 1, pomnożyć przez 10 i zaokrąglić w dół. Alternatywnie można użyć tej random.randint
metody.
import random
def random_digit_1():
return int(10 * random.random())
def random_digit_2():
return random.randint(0, 9)
Byłem ciekawy, co by się stało, gdyby ktoś wygenerował losową liczbę od 0 do 1 i zachował ostatnią cyfrę. Niekoniecznie spodziewałem się, że rozkład będzie jednolity, ale wynik był dość zaskakujący.
from random import random, seed
from collections import Counter
seed(0)
counts = Counter(int(str(random())[-1]) for _ in range(1_000_000))
print(counts)
Wynik:
Counter({1: 84206,
5: 130245,
3: 119433,
6: 129835,
8: 101488,
2: 100861,
9: 84796,
4: 129088,
7: 120048})
Histogram pokazano poniżej. Zauważ, że 0 nie pojawia się, ponieważ końcowe zera są obcinane. Ale czy ktoś może wyjaśnić, dlaczego cyfry 4, 5 i 6 są bardziej powszechne niż reszta? Użyłem Python 3.6.10, ale wyniki były podobne w Python 3.8.0a4.
str
konwertuje go do base-10, co z pewnością spowoduje problemy. np. 1-bitowa mantysa pływakab0 -> 1.0
ib1 -> 1.5
. „Ostatnią cyfrą” będzie zawsze0
lub5
.random.randrange(10)
jest jeszcze bardziej oczywiste, IMHO.random.randint
(który wywołujerandom.randrange
pod maską) był późniejszym dodatkiem dorandom
modułu dla osób, które nie rozumieją, jak działają zakresy w Pythonie. ;)randrange
właściwie zajął drugie miejsce po tym, jak zdecydowali, żerandint
interfejs jest błędem.Odpowiedzi:
To nie jest „ostatnia cyfra” numeru. To ostatnia cyfra ciągu
str
podana po przekazaniu numeru.Kiedy wywołujesz
str
liczbę zmiennoprzecinkową, Python podaje wystarczająco dużo cyfr, aby wywołaniefloat
ciągu dało ci oryginalną wartość zmiennoprzecinkową. W tym celu końcowe 1 lub 9 jest mniej prawdopodobne niż inne cyfry, ponieważ końcowe 1 lub 9 oznacza, że liczba jest bardzo zbliżona do wartości, którą uzyskasz zaokrąglając tę cyfrę. Istnieje duża szansa, że żadne inne zmiennoprzecinkowe nie są bliżej, a jeśli tak, to cyfrę można odrzucić bez poświęcaniafloat(str(original_float))
zachowania.Jeśli
str
dostarczyłbyś wystarczającą liczbę cyfr do dokładnego przedstawienia argumentu, ostatnia cyfra prawie zawsze wynosiłaby 5, z wyjątkiem gdyrandom.random()
zwraca 0,0, w którym to przypadku ostatnia cyfra wynosiłaby 0. (Liczba zmiennoprzecinkowa może reprezentować tylko racjonalne różnice , a ostatnia niezerowa cyfra dziesiętna niecałkowite uzasadnienie dyadyczne to zawsze 5.) Wyjścia również byłyby bardzo długie, wyglądającktóry jest jednym z powodów, dla których
str
tego nie robi.Jeśli
str
podałeś dokładnie 17 cyfr znaczących (wystarczających do odróżnienia wszystkich wartości zmiennoprzecinkowych, ale czasami więcej cyfr niż to konieczne), wówczas efekt, który widzisz, zniknąłby. Byłby prawie równomierny rozkład cyfr końcowych (w tym 0).(Poza tym zapomniałeś, że
str
czasami zwraca ciąg znaków w notacji naukowej, ale to niewielki efekt, ponieważ istnieje małe prawdopodobieństwo uzyskania liczby zmiennoprzecinkowej tam, gdzie to by się wydarzyłorandom.random()
.)źródło
TL; DR Twój przykład tak naprawdę nie patrzy na ostatnią cyfrę. Ostatnia cyfra skończonej reprezentowanej binarnie mantysy przekonwertowanej na base-10 powinna zawsze być
0
lub5
.Spójrz na
cpython/floatobject.c
:A teraz o
cpython/pystrtod.c
:Wikipedia potwierdza to:
Tak więc, kiedy używamy
str
(lubrepr
), reprezentujemy tylko 17 cyfr znaczących w bazie-10. Oznacza to, że część liczb zmiennoprzecinkowych zostanie obcięta. W rzeczywistości, aby uzyskać dokładną reprezentację, potrzebujesz dokładności 53 cyfr znaczących! Możesz to sprawdzić w następujący sposób:Teraz, używając maksymalnej precyzji, oto właściwy sposób na znalezienie „ostatniej cyfry”:
UWAGA: Jak wskazał użytkownik2357112, poprawnymi implementacjami do obejrzenia są
PyOS_double_to_string
iformat_float_short
, ale zostawię obecne, ponieważ są one bardziej interesujące pedagogicznie.źródło
str(some_float)
zastosowania zaokrąglania za pomocą wystarczającej liczby cyfr do zaokrąglenia w obie strony .PyOS_double_to_string
. Ta implementacja jest wstępnie przetwarzana na korzyść tejfloat(str(x)) == x
. Przeważnie ta odpowiedź miała na celu jedynie wykazanie, że założenie („ostatnia cyfra dokładnej reprezentacji”) postawione w pytaniu było błędne, ponieważ poprawny wynik to po prostu5
s (i mało prawdopodobne0
).