Je sais que la plupart des décimales n'ont pas de représentation en virgule flottante exacte (les mathématiques en virgule flottante sont-elles cassées? ).
Mais je ne vois pas pourquoi 4*0.1
est bien imprimé comme 0.4
, mais 3*0.1
ne l'est pas, alors que les deux valeurs ont en fait des représentations décimales laides:
>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
0.3000000000000000444089209850062616169452667236328125
fur0.30000000000000004
et à0.40000000000000002220446049250313080847263336181640625
mesure.4
qu'ils semblent avoir la même précision, et ne répond donc pas à la question.Réponses:
La réponse simple est que, en
3*0.1 != 0.3
raison d'une erreur de quantification (arrondi) (alors que4*0.1 == 0.4
que multiplier par une puissance de deux est généralement une opération "exacte").Vous pouvez utiliser la
.hex
méthode en Python pour afficher la représentation interne d'un nombre (en gros, la valeur exacte en virgule flottante binaire, plutôt que l'approximation en base 10). Cela peut aider à expliquer ce qui se passe sous le capot.0,1 est 0x1.999999999999a fois 2 ^ -4. Le "a" à la fin signifie le chiffre 10 - en d'autres termes, 0,1 en virgule flottante binaire est très légèrement plus grand que la valeur "exacte" de 0,1 (car le 0x0.99 final est arrondi à 0x0.a). Lorsque vous multipliez cela par 4, une puissance de deux, l'exposant se décale (de 2 ^ -4 à 2 ^ -2) mais le nombre reste inchangé par ailleurs, donc
4*0.1 == 0.4
.Cependant, lorsque vous multipliez par 3, la petite différence minuscule entre 0x0.99 et 0x0.a0 (0x0.07) s'agrandit en une erreur 0x0.15, qui apparaît comme une erreur à un chiffre à la dernière position. Cela fait que 0,1 * 3 est très légèrement supérieur à la valeur arrondie de 0,3.
Le flotteur de Python 3
repr
est conçu pour être allers-retours , c'est -à-dire que la valeur affichée doit être exactement convertible en valeur d'origine. Par conséquent, il ne peut pas afficher0.3
et0.1*3
exactement de la même manière, ou les deux nombres différents finiraient de la même manière après un aller-retour. Par conséquent, lerepr
moteur de Python 3 choisit d'en afficher un avec une légère erreur apparente.la source
.hex()
; je ne savais pas que cela existait.)e
parce que c'est déjà un chiffre hexadécimal. Peut-êtrep
pour la puissance au lieu de l' exposant .p
dans ce contexte remonte (au moins) à C99, et apparaît également dans IEEE 754 et dans divers autres langages (y compris Java). Quandfloat.hex
etfloat.fromhex
ont été implémentés (par moi :-), Python copiait simplement ce qui était alors une pratique établie. Je ne sais pas si l'intention était «p» pour «Power», mais cela semble être une bonne façon d'y penser.repr
(etstr
en Python 3) affichera autant de chiffres que nécessaire pour rendre la valeur sans ambiguïté. Dans ce cas, le résultat de la multiplication3*0.1
n'est pas la valeur la plus proche de 0,3 (0x1.3333333333333p-2 en hexadécimal), c'est en fait un LSB plus élevé (0x1.3333333333334p-2), il a donc besoin de plus de chiffres pour le distinguer de 0,3.D'autre part, la multiplication n'obtenir la valeur la plus proche de 0,4 (0x1.999999999999ap-2 dans l' hexagone), donc il n'a pas besoin de chiffres supplémentaires.
4*0.1
Vous pouvez le vérifier assez facilement:
J'ai utilisé la notation hexadécimale ci-dessus car elle est agréable et compacte et montre la différence de bits entre les deux valeurs. Vous pouvez le faire vous-même en utilisant par exemple
(3*0.1).hex()
. Si vous préférez les voir dans toute leur gloire décimale, allez-y:la source
3*0.1 == 0.3
et4*0.1 == 0.4
?Voici une conclusion simplifiée à partir d'autres réponses.
la source
str
etrepr
sont identiques pour les flottants. Pour Python 2.7,repr
a les propriétés que vous identifiez, maisstr
c'est beaucoup plus simple - il calcule simplement 12 chiffres significatifs et produit une chaîne de sortie basée sur ceux-ci. Pour Python <= 2.6, les deuxrepr
etstr
sont basés sur un nombre fixe de chiffres significatifs (17 pourrepr
, 12 pourstr
). (Et personne ne se soucie de Python 3.0 ou Python 3.1 :-)repr
donc le comportement de Python 2.7 serait identique ...Pas vraiment spécifique à l'implémentation de Python mais devrait s'appliquer à toutes les fonctions de chaîne flottante à décimale.
Un nombre à virgule flottante est essentiellement un nombre binaire, mais en notation scientifique avec une limite fixe de chiffres significatifs.
L'inverse de tout nombre qui a un facteur de nombre premier qui n'est pas partagé avec la base se traduira toujours par une représentation de point de point récurrente. Par exemple 1/7 a un facteur premier, 7, qui n'est pas partagé avec 10, et a donc une représentation décimale récurrente, et il en va de même pour 1/10 avec les facteurs premiers 2 et 5, ce dernier n'étant pas partagé avec 2 ; cela signifie que 0,1 ne peut pas être représenté exactement par un nombre fini de bits après le point.
Étant donné que 0,1 n'a pas de représentation exacte, une fonction qui convertit l'approximation en chaîne de virgule décimale essaiera généralement d'approximer certaines valeurs afin qu'elles n'obtiennent pas de résultats non intuitifs tels que 0.1000000000004121.
Puisque la virgule flottante est en notation scientifique, toute multiplication par une puissance de la base n'affecte que la partie exposante du nombre. Par exemple 1.231e + 2 * 100 = 1.231e + 4 pour la notation décimale, et de même, 1.00101010e11 * 100 = 1.00101010e101 en notation binaire. Si je multiplie par une non-puissance de la base, les chiffres significatifs seront également affectés. Par exemple 1,2e1 * 3 = 3,6e1
Selon l'algorithme utilisé, il peut essayer de deviner les décimales communes en se basant uniquement sur les chiffres significatifs. 0,1 et 0,4 ont les mêmes chiffres significatifs en binaire, car leurs flotteurs sont essentiellement des troncatures de (8/5) (2 ^ -4) et (8/5) (2 ^ -6) respectivement. Si l'algorithme identifie le motif 8/5 sigfig comme le décimal 1,6, alors il fonctionnera sur 0,1, 0,2, 0,4, 0,8, etc. et d'autres modèles magiques statistiquement susceptibles d'être formés par division par 10.
Dans le cas de 3 * 0,1, les derniers chiffres significatifs seront probablement différents de la division d'un flotteur 3 par un flotteur 10, ce qui empêchera l'algorithme de reconnaître le nombre magique de la constante 0,3 en fonction de sa tolérance à la perte de précision.
Modifier: https://docs.python.org/3.1/tutorial/floatingpoint.html
Il n'y a pas de tolérance pour la perte de précision, si float x (0,3) n'est pas exactement égal à float y (0,1 * 3), alors repr (x) n'est pas exactement égal à repr (y).
la source