Есть два очевидных способа генерирования случайной цифры от 0 до 9 в Python. Можно сгенерировать случайное число с плавающей запятой между 0 и 1, умножить на 10 и округлить в меньшую сторону. В качестве альтернативы можно использовать random.randint
метод.
import random
def random_digit_1():
return int(10 * random.random())
def random_digit_2():
return random.randint(0, 9)
Мне было любопытно, что произойдет, если кто-то сгенерирует случайное число от 0 до 1 и сохранит последнюю цифру. Я не обязательно ожидал, что распределение будет равномерным, но я нашел результат довольно удивительным.
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)
Вывод:
Counter({1: 84206,
5: 130245,
3: 119433,
6: 129835,
8: 101488,
2: 100861,
9: 84796,
4: 129088,
7: 120048})
Гистограмма показана ниже. Обратите внимание, что 0 не появляется, так как конечные нули усекаются. Но кто-нибудь может объяснить, почему цифры 4, 5 и 6 встречаются чаще, чем остальные? Я использовал Python 3.6.10, но результаты были похожи в Python 3.8.0a4.
str
преобразует его в базу-10, которая обязательно вызовет проблемы. например, 1-битная плавающая мантиссаb0 -> 1.0
иb1 -> 1.5
. «Последняя цифра» всегда будет0
или5
.random.randrange(10)
Еще более очевидно, ИМХО.random.randint
(который вызываетсяrandom.randrange
изнутри) был более поздним дополнением кrandom
модулю для людей, которые не понимают, как диапазоны работают в Python. ;)randrange
самом деле пришел вторым после того, как они решили, чтоrandint
интерфейс был ошибкой.Ответы:
Это не «последняя цифра» числа. Это последняя цифра строки, которую
str
вы получили, когда передали число.Когда вы вызываете число
str
с плавающей точкой, Python дает вам достаточно цифр, чтобы при вызовеfloat
строки получал исходное число с плавающей точкой. Для этой цели, трейлинг 1 или 9 менее вероятно потребуется, чем другие цифры, потому что трейлинг 1 или 9 означает, что число очень близко к значению, которое вы получите, округлив эту цифру. Есть хороший шанс, что другие поплавки не будут ближе, и если это так, то эта цифра может быть отброшена без ущерба дляfloat(str(original_float))
поведения.Если
str
дать вам достаточно цифр для точного представления аргумента, последняя цифра почти всегда будет 5, за исключением случаев, когдаrandom.random()
возвращается 0,0, и в этом случае последняя цифра будет 0. (Плавающие могут представлять только двоичные числа , а последняя ненулевая десятичная цифра нецелочисленное двоичное рациональное всегда равно 5.) Выходные данные также будут очень длинными, выглядящими какчто является одной из причин, по которой
str
этого не происходит.Если
str
вам дано ровно 17 значащих цифр (достаточно, чтобы отличить все значения с плавающей точкой друг от друга, но иногда больше цифр, чем необходимо), то эффект, который вы видите, исчезнет. Было бы почти равномерное распределение конечных цифр (включая 0).(Кроме того, вы забыли, что
str
иногда возвращает строку в научной нотации, но это незначительный эффект, потому что существует небольшая вероятность получить значение с плавающей точкой, где это могло бы произойтиrandom.random()
.)источник
TL; DR Ваш пример на самом деле не смотрит на последнюю цифру. Последняя цифра конечного двоичного представления мантиссы, преобразованного в основание-10, всегда должна быть
0
или5
.Посмотрите на
cpython/floatobject.c
:А теперь по адресу
cpython/pystrtod.c
:Википедия подтверждает это:
Таким образом, когда мы используем
str
(илиrepr
), мы представляем только 17 значащих цифр в base-10. Это означает, что некоторые числа с плавающей запятой будут усечены. Фактически, чтобы получить точное представление, вам нужна точность 53 значащих цифр! Вы можете проверить это следующим образом:Теперь, используя максимальную точность, вот правильный способ найти «последнюю цифру»:
ПРИМЕЧАНИЕ. Как указывает user2357112, правильные реализации, на которые нужно смотреть, - это
PyOS_double_to_string
иformat_float_short
, но я оставлю текущие реализации, потому что они более интересны с педагогической точки зрения.источник
str(some_float)
использования округления «достаточно много цифр к круговому обходу» .PyOS_double_to_string
. Эта реализация предварительно обработана в пользу этойfloat(str(x)) == x
. В основном, этот ответ был просто для того, чтобы показать допущение («последняя цифра точного представления»), сделанное в вопросе, было неверным, поскольку правильный результат - просто5
s (и маловероятно0
).