Existem duas maneiras óbvias de gerar um dígito aleatório de 0 a 9 no Python. Pode-se gerar um número de ponto flutuante aleatório entre 0 e 1, multiplicar por 10 e arredondar para baixo. Alternativamente, pode-se usar o random.randint
método
import random
def random_digit_1():
return int(10 * random.random())
def random_digit_2():
return random.randint(0, 9)
Eu estava curioso sobre o que aconteceria se alguém gerasse um número aleatório entre 0 e 1 e mantivesse o último dígito. Eu não esperava necessariamente que a distribuição fosse uniforme, mas achei o resultado bastante surpreendente.
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)
Resultado:
Counter({1: 84206,
5: 130245,
3: 119433,
6: 129835,
8: 101488,
2: 100861,
9: 84796,
4: 129088,
7: 120048})
Um histograma é mostrado abaixo. Observe que 0 não aparece, pois os zeros à direita são truncados. Mas alguém pode explicar por que os dígitos 4, 5 e 6 são mais comuns que o resto? Eu usei o Python 3.6.10, mas os resultados foram semelhantes no Python 3.8.0a4.
str
converte-o para a base-10, que provavelmente causará problemas. por exemplo, uma mantissa flutuante de 1 bitb0 -> 1.0
eb1 -> 1.5
. O "último dígito" será sempre0
ou5
.random.randrange(10)
é ainda mais óbvio, IMHO.random.randint
(que chama porrandom.randrange
baixo do capô) foi uma adição posterior aorandom
módulo para pessoas que não entendem como os intervalos funcionam no Python. ;)randrange
na verdade, ficou em segundo lugar, depois que eles decidiram que arandint
interface era um erro.Respostas:
Esse não é "o último dígito" do número. Esse é o último dígito da string
str
quando você passa o número.Quando você chama
str
um float, o Python fornece dígitos suficientes para que o chamarfloat
na string forneça o float original. Para isso, é menos provável que um 1 ou 9 à direita seja necessário que outros dígitos, porque 1 ou 9 à direita significa que o número está muito próximo do valor que você obteria ao arredondar esse dígito. Há uma boa chance de nenhum outro carro alegórico estar mais próximo e, nesse caso, esse dígito pode ser descartado sem sacrificar ofloat(str(original_float))
comportamento.Se
str
você forneceu dígitos suficientes para representar exatamente o argumento, o último dígito quase sempre seria 5, exceto quandorandom.random()
retorna 0,0, nesse caso o último dígito seria 0. (Os flutuadores podem representar apenas racionais diádicos e o último dígito decimal diferente de zero de um racional diádico não inteiro é sempre 5.) As saídas também seriam extremamente longas, parecendoqual é uma das razões
str
não faz isso.Se
str
você fornecesse exatamente 17 dígitos significativos (o suficiente para distinguir todos os valores flutuantes um do outro, mas às vezes mais dígitos do que o necessário), o efeito que você está vendo desapareceria. Haveria uma distribuição quase uniforme dos dígitos à direita (incluindo 0).(Além disso, você esqueceu que
str
às vezes retorna uma seqüência de caracteres em notação científica, mas isso é um efeito menor, porque há uma baixa probabilidade de obter uma flutuação de onde isso aconteceriarandom.random()
.)fonte
TL; DR Seu exemplo não está realmente olhando para o último dígito. O último dígito de uma mantissa finita representada em binário convertida em base-10 deve sempre ser
0
ou5
.Dê uma olhada em
cpython/floatobject.c
:E agora em
cpython/pystrtod.c
:A Wikipedia confirma isso:
Assim, quando usamos
str
(ourepr
), estamos representando apenas 17 dígitos significativos na base 10. Isso significa que parte do número de ponto flutuante será truncado. De fato, para obter a representação exata, você precisa de uma precisão de 53 dígitos significativos! Você pode verificar isso da seguinte maneira:Agora, usando a precisão máxima, eis a maneira correta de encontrar o "último dígito":
NOTA: Conforme indicado por user2357112, as implementações corretas a serem observadas são
PyOS_double_to_string
eformat_float_short
, mas deixarei as atuais porque são mais interessantes em termos pedagógicos.fonte
str(some_float)
usos de arredondamento de dígitos suficientes para a viagem de ida e volta .PyOS_double_to_string
. Essa implementação é pré-processada em favor destafloat(str(x)) == x
. Principalmente, essa resposta foi apenas para mostrar que a suposição ("último dígito da representação exata") feita na pergunta estava errada, uma vez que o resultado correto é apenas5
s (e um improvável0
).