Il existe deux façons évidentes de générer un chiffre aléatoire de 0 à 9 en Python. On pourrait générer un nombre à virgule flottante aléatoire entre 0 et 1, multiplier par 10 et arrondir vers le bas. Alternativement, on pourrait utiliser la random.randint
méthode.
import random
def random_digit_1():
return int(10 * random.random())
def random_digit_2():
return random.randint(0, 9)
J'étais curieux de savoir ce qui se passerait si l'on générait un nombre aléatoire entre 0 et 1 et conservait le dernier chiffre. Je ne m'attendais pas nécessairement à une distribution uniforme, mais j'ai trouvé le résultat assez surprenant.
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)
Production:
Counter({1: 84206,
5: 130245,
3: 119433,
6: 129835,
8: 101488,
2: 100861,
9: 84796,
4: 129088,
7: 120048})
Un histogramme est illustré ci-dessous. Notez que 0 n'apparaît pas, car les zéros de fin sont tronqués. Mais quelqu'un peut-il expliquer pourquoi les chiffres 4, 5 et 6 sont plus courants que les autres? J'ai utilisé Python 3.6.10, mais les résultats étaient similaires dans Python 3.8.0a4.
str
le convertit en base-10 qui est susceptible de causer des problèmes. par exemple une mantisse flottante à 1 bitb0 -> 1.0
etb1 -> 1.5
. Le "dernier chiffre" sera toujours0
ou5
.random.randrange(10)
est encore plus évident, à mon humble avis.random.randint
(qui appellerandom.randrange
sous le capot) a été ajouté ultérieurement aurandom
module pour les personnes qui ne comprennent pas comment les plages fonctionnent en Python. ;)randrange
est arrivé en deuxième position, après avoir décidé que l'randint
interface était une erreur.Réponses:
Ce n'est pas "le dernier chiffre" du nombre. C'est le dernier chiffre de la chaîne que
str
vous avez donné lorsque vous avez passé le numéro.Lorsque vous appelez
str
un flottant, Python vous donne suffisamment de chiffres pour que l'appelfloat
sur la chaîne vous donne le flottant d'origine. À cette fin, un 1 ou 9 de fin est moins susceptible d'être nécessaire que d'autres chiffres, car un 1 ou 9 de fin signifie que le nombre est très proche de la valeur que vous obtiendriez en arrondissant ce chiffre. Il y a de fortes chances qu'aucun autre flotteur ne soit plus proche, et si c'est le cas, ce chiffre peut être jeté sans sacrifier lefloat(str(original_float))
comportement.S'il
str
vous a donné suffisamment de chiffres pour représenter exactement l'argument, le dernier chiffre serait presque toujours 5, sauf lorsquerandom.random()
renvoie 0,0, auquel cas le dernier chiffre serait 0. (Les flottants ne peuvent représenter que des logiques dyadiques et le dernier chiffre décimal non nul de un rationnel dyadique non entier est toujours 5.) Les sorties seraient également extrêmement longues, ressemblant àce qui est l'une des raisons pour lesquelles
str
cela ne se produit pas.Si
str
vous vous donnait exactement 17 chiffres significatifs (assez pour distinguer toutes les valeurs flottantes les unes des autres, mais parfois plus de chiffres que nécessaire), alors l'effet que vous voyez disparaîtrait. Il y aurait une distribution presque uniforme des chiffres de fin (y compris 0).(De plus, vous avez oublié que
str
parfois renvoie une chaîne en notation scientifique, mais c'est un effet mineur, car il y a une faible probabilité d'obtenir un flotteur d'où cela se produiraitrandom.random()
.)la source
TL; DR Votre exemple ne regarde pas réellement le dernier chiffre. Le dernier chiffre d'une mantisse finie représentée binaire convertie en base 10 doit toujours être
0
ou5
.Jetez un œil à
cpython/floatobject.c
:Et maintenant à
cpython/pystrtod.c
:Wikipédia le confirme:
Ainsi, lorsque nous utilisons
str
(ourepr
), nous ne représentons que 17 chiffres significatifs en base-10. Cela signifie qu'une partie du nombre à virgule flottante sera tronquée. En fait, pour obtenir la représentation exacte, vous avez besoin d'une précision de 53 chiffres significatifs! Vous pouvez le vérifier comme suit:Maintenant, en utilisant la précision maximale, voici la bonne façon de trouver le "dernier chiffre":
REMARQUE: Comme indiqué par user2357112, les implémentations correctes à examiner sont
PyOS_double_to_string
etformat_float_short
, mais je laisserai les actuelles car elles sont plus intéressantes sur le plan pédagogique.la source
str(some_float)
utilisations de l' arrondi juste assez de chiffres pour l'aller-retour .PyOS_double_to_string
. Cette implémentation est prétraitée en faveur de celle-cifloat(str(x)) == x
. La plupart du temps, cette réponse était juste pour montrer que l'hypothèse ("dernier chiffre de la représentation exacte") faite dans la question était fausse, car le résultat correct est juste5
s (et peu probable0
).