Hay dos formas obvias de generar un dígito aleatorio de 0 a 9 en Python. Se podría generar un número aleatorio de coma flotante entre 0 y 1, multiplicar por 10 y redondear hacia abajo. Alternativamente, uno podría usar el random.randint
método.
import random
def random_digit_1():
return int(10 * random.random())
def random_digit_2():
return random.randint(0, 9)
Tenía curiosidad sobre lo que sucedería si uno generara un número aleatorio entre 0 y 1, y mantuviera el último dígito. No esperaba necesariamente que la distribución fuera uniforme, pero el resultado me pareció bastante sorprendente.
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)
Salida:
Counter({1: 84206,
5: 130245,
3: 119433,
6: 129835,
8: 101488,
2: 100861,
9: 84796,
4: 129088,
7: 120048})
A continuación se muestra un histograma. Tenga en cuenta que 0 no aparece, ya que los ceros finales se truncan. Pero, ¿alguien puede explicar por qué los dígitos 4, 5 y 6 son más comunes que el resto? Usé Python 3.6.10, pero los resultados fueron similares en Python 3.8.0a4.
str
lo convierte a base-10, lo que seguramente causará problemas. por ejemplo, una mantisa flotante de 1 bitb0 -> 1.0
yb1 -> 1.5
. El "último dígito" siempre será0
o5
.random.randrange(10)
es aún más obvio, en mi humilde opinión.random.randint
(que llamarandom.randrange
bajo el capó) fue una adición posterior alrandom
módulo para personas que no entienden cómo funcionan los rangos en Python. ;)randrange
realidad llegó en segundo lugar, después de que decidieron que larandint
interfaz era un error.Respuestas:
Ese no es "el último dígito" del número. Ese es el último dígito de la cadena que
str
le dio cuando pasó el número.Cuando llama
str
a un flotante, Python le da suficientes dígitos que al llamarfloat
a la cadena le dará el flotante original. Para este propósito, es menos probable que sea necesario un 1 o 9 al final que otros dígitos, porque un 1 o 9 al final significa que el número está muy cerca del valor que obtendría al redondear ese dígito. Hay una buena posibilidad de que no haya otros flotadores más cerca, y si es así, ese dígito puede descartarse sin sacrificar elfloat(str(original_float))
comportamiento.Si
str
le da suficientes dígitos para representar exactamente el argumento, el último dígito casi siempre será 5, excepto cuandorandom.random()
devuelve 0.0, en cuyo caso el último dígito sería 0. (Los flotantes solo pueden representar racionales diádicos y el último dígito decimal distinto de cero de un racional diádico no entero es siempre 5.) Las salidas también serían extremadamente largas, comocuál es una de las razones por las
str
que no hace eso.Si
str
le dio exactamente 17 dígitos significativos (suficiente para distinguir todos los valores flotantes entre sí, pero a veces más dígitos de los necesarios), entonces el efecto que está viendo desaparecería. Habría una distribución casi uniforme de los dígitos finales (incluido 0).(Además, olvidó que a
str
veces devuelve una cadena en notación científica, pero ese es un efecto menor, porque hay una baja probabilidad de obtener un flotador de donde eso sucederíarandom.random()
).fuente
TL; DR Su ejemplo no está realmente mirando el último dígito. El último dígito de una mantisa finita binaria representada convertida a base-10 siempre debe ser
0
o5
.Echa un vistazo a
cpython/floatobject.c
:Y ahora en
cpython/pystrtod.c
:Wikipedia confirma esto:
Por lo tanto, cuando usamos
str
(orepr
), solo representamos 17 dígitos significativos en base-10. Esto significa que parte del número de coma flotante se truncará. De hecho, para obtener la representación exacta, ¡necesita una precisión de 53 dígitos significativos! Puede verificar esto de la siguiente manera:Ahora, usando la máxima precisión, esta es la forma correcta de encontrar el "último dígito":
NOTA: Como lo señaló user2357112, las implementaciones correctas a considerar son
PyOS_double_to_string
yformat_float_short
, pero dejaré las actuales porque son más pedagógicamente interesantes.fuente
str(some_float)
usos de redondeo de dígitos suficientes para el viaje de ida y vuelta .PyOS_double_to_string
. Esa implementación está preprocesada a favor de estafloat(str(x)) == x
. Principalmente, esta respuesta fue solo para mostrar que la suposición ("último dígito de representación exacta") hecha en la pregunta era incorrecta, ya que el resultado correcto es solo5
s (y poco probable0
).