Pourquoi 1 // 0,01 == 99 en Python?


31

J'imagine que c'est une question classique de précision en virgule flottante, mais j'essaie de comprendre ce résultat, en exécutant 1//0.01les rendements Python 3.7.5 99.

J'imagine que c'est un résultat attendu, mais existe-t-il un moyen de décider quand il est plus sûr d'utiliser int(1/f)plutôt que 1//f?


4
Oui, c'est toujours plus sûr int (1 / f). Tout simplement parce que // est la division FLOOR, et vous la considérez à tort comme RONDE.
Perdi Estaquel


2
Pas un doublon. Cela peut fonctionner comme prévu à 99,99% en utilisant toujours round()et jamais //ou int(). La question liée concerne la comparaison flottante n'a rien à voir avec la troncature et aucune solution aussi facile.
max

Réponses:


23

S'il s'agissait d'une division avec des nombres réels, ce 1//0.01serait exactement 100. Comme il s'agit d'approximations à virgule flottante, cependant, il 0.01est légèrement supérieur à 1/100, ce qui signifie que le quotient est légèrement inférieur à 100. C'est cette valeur de 99.qui est ensuite plancher à 99.


3
Cela ne traite pas de la partie "existe-t-il un moyen de décider quand il est plus sûr".
Scott Hunter

10
"Plus sûr" n'est pas bien défini.
chepner

1
Assez pour l'ignorer complètement, esp. lorsque l'OP est conscient des problèmes de virgule flottante?
Scott Hunter

3
@chepner Si "plus sûr" n'est pas bien défini, alors il vaut peut-être mieux demander des éclaircissements: /

2
il est assez clair pour moi que "plus sûr" signifie "une erreur pas pire qu'une calculatrice de poche bon marché"
max

9

Les raisons de ce résultat sont comme vous le dites et sont expliquées dans Les mathématiques en virgule flottante sont-elles cassées? et de nombreuses autres questions / réponses similaires.

Lorsque vous connaissez le nombre de décimales du numérateur et du dénominateur, un moyen plus fiable consiste à multiplier d'abord ces nombres afin qu'ils puissent être traités comme des nombres entiers, puis à effectuer une division entière sur eux:

Donc, dans votre cas, 1//0.01vous devez d'abord convertir 1*100//(0.01*100)ce qui est 100.

Dans des cas plus extrêmes, vous pouvez toujours obtenir des résultats "inattendus". Il peut être nécessaire d'ajouter un roundappel au numérateur et au dénominateur avant d'effectuer la division entière:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Mais, s'il s'agit de travailler avec des décimales fixes (argent, cents), envisagez de travailler avec des cents comme unité , de sorte que toute l'arithmétique puisse être effectuée sous forme d'arithmétique entière, et ne convertir que vers / depuis l'unité monétaire principale (dollar) lors de l'exécution. E / S.

Ou bien, utilisez une bibliothèque de décimales, comme décimale , qui:

... prend en charge l'arithmétique décimale décimale flottante correctement arrondie.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100

3
"qui est évidemment 100." Pas nécessairement: si le .01 n'est pas exact, alors le .01 * 100 ne l'est pas aussi. Il doit être "réglé" manuellement.
glglgl

8

Ce que vous devez prendre en compte, c'est que //c'est l' flooropérateur et en tant que tel, vous devez d'abord penser comme si vous avez une probabilité égale de tomber à 100 comme à 99 (*) (car l'opération sera 100 ± epsilonavec à epsilon>0condition que les chances d'obtenir exactement 100,00 ..0 sont extrêmement faibles.)

Vous pouvez en fait voir la même chose avec un signe moins,

>>> 1//.01
99.0
>>> -1//.01
-100.0

et vous devriez être (non) surpris.

D'un autre côté, int(-1/.01)effectue d'abord la division puis applique le int()dans le nombre, qui n'est pas le plancher mais une troncature vers 0 ! ce qui signifie que dans ce cas,

>>> 1/.01
100.0
>>> -1/.01
-100.0

Par conséquent,

>>> int(1/.01)
100
>>> int(-1/.01)
-100

Arrondir cependant, vous donnerait le résultat attendu pour cet opérateur car, encore une fois, l'erreur est faible pour ces chiffres.

(*) Je ne dis pas que la probabilité est la même, je dis juste que a priori lorsque vous effectuez un tel calcul avec une arithmétique flottante qui est une estimation de ce que vous obtenez.


7

Les nombres à virgule flottante ne peuvent pas représenter exactement la plupart des nombres décimaux, donc lorsque vous tapez un littéral à virgule flottante, vous obtenez en fait une approximation de ce littéral. L'approximation peut être plus grande ou plus petite que le nombre que vous avez tapé.

Vous pouvez voir la valeur exacte d'un nombre à virgule flottante en le convertissant en décimal ou en fraction.

>>> from decimal import Decimal
>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> from fractions import Fractio
>>> Fraction(0.01)
Fraction(5764607523034235, 576460752303423488) 

Nous pouvons utiliser le type Fraction pour trouver l'erreur causée par notre littéral inexact.

>>> float((Fraction(1)/Fraction(0.01)) - 100)
-2.0816681711721685e-15

Nous pouvons également découvrir comment les nombres à virgule flottante double précision granulaires autour de 100 sont en utilisant nextafter de numpy.

>>> from numpy import nextafter
>>> nextafter(100,0)-100
-1.4210854715202004e-14

De cela, nous pouvons supposer que le nombre à virgule flottante le plus proche de 1/0.01000000000000000020816681711721685132943093776702880859375est en fait exactement 100.

La différence entre 1//0.01et int(1/0.01)est l'arrondi. 1 // 0,01 arrondit le résultat exact au nombre entier suivant en une seule étape. Nous obtenons donc un résultat de 99.

int (1 / 0,01), d'autre part, arrondit en deux étapes, d'abord il arrondit le résultat au nombre à virgule flottante double précision le plus proche (qui est exactement 100), puis il arrondit ce nombre à virgule flottante à l'entier suivant (qui est à nouveau exactement 100).


Appeler cela simplement un arrondi est trompeur. Elle doit être appelée soit troncature, soit arrondie vers zéro : int(0.9) == 0etint(-0.9) == 0
max

Ce sont des types binaires à virgule flottante dont vous parlez ici. (Il existe également des types décimaux en virgule flottante.)
Stephen C

3

Si vous exécutez ce qui suit

from decimal import *

num = Decimal(1) / Decimal(0.01)
print(num)

La sortie sera:

99.99999999999999791833182883

Voilà comment il est représenté en interne, donc l'arrondir vers //le bas donnera99


2
Il est suffisamment précis pour montrer l'erreur dans ce cas, mais sachez que l'arithmétique "décimale" n'est pas exacte non plus.
plugwash

Comme Decimal(0.01)vous êtes trop en retard, l'erreur s'est déjà glissée avant d'appeler Decimal. Je ne sais pas comment c'est une réponse à la question ... Vous devez d'abord calculer un 0,01 précis avec Decimal(1) / Decimal(100), comme je l'ai montré dans ma réponse.
trincot

@trincot Ma réponse est à la question du titre "Pourquoi 1 // 0,01 == 99". J'ai essayé de montrer au PO comment les nombres flottants sont traités en interne.
Pluie
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.