web-dev-qa-db-fra.com

Python modulo sur flotteurs

Quelqu'un peut-il expliquer comment fonctionne l'opérateur modulo en Python? Je ne comprends pas pourquoi 3.5 % 0.1 = 0.1.

34
beruic

En fait, ce n'est pas vrai que 3.5 % 0.1 Est 0.1. Vous pouvez tester cela très facilement:

>>> print(3.5 % 0.1)
0.1
>>> print(3.5 % 0.1 == 0.1)
False

En réalité, sur la plupart des systèmes, 3.5 % 0.1 Est 0.099999999999999811. Mais, sur certaines versions de Python, str(0.099999999999999811) est 0.1:

>>> 3.5 % 0.1
0.099999999999999811
>>> repr(3.5 % 0.1)
'0.099999999999999811'
>>> str(3.5 % 0.1)
'0.1'

Maintenant, vous vous demandez probablement pourquoi 3.5 % 0.1 Est 0.099999999999999811 Au lieu de 0.0. Cela est dû aux problèmes d'arrondi à virgule flottante habituels. Si vous n'avez pas lu Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante , vous devriez - ou du moins le bref Wikipedia résumé de ce problème particulier.

Notez également que 3.5/0.1 N'est pas 34, C'est 35. Ainsi, 3.5/0.1 * 0.1 + 3.5%0.1 Est 3.5999999999999996, Ce qui n'est même pas proche de 3.5. C'est à peu près fondamental pour la définition du module, et c'est faux en Python, et à peu près tous les autres langages de programmation.

Mais Python 3 vient à la rescousse là-bas. La plupart des gens qui connaissent // Savent que c'est comme ça que l'on fait la "division entière" entre les entiers, mais ne se rendent pas compte que c'est comme ça vous effectuez une division compatible avec le module entre tous les types . 3.5//0.1 est 34.0, donc 3.5//0.1 * 0.1 + 3.5%0.1 est (au moins dans une petite erreur d'arrondi de) 3.5. Ceci a été rétroporté vers 2.x, donc (selon votre version exacte et votre plate-forme), vous pourrez peut-être vous fier à cela. Et, sinon, vous pouvez utiliser divmod(3.5, 0.1), qui retourne (dans l'erreur d'arrondi) (34.0, 0.09999999999999981) tout le long du chemin. Bien sûr, vous vous attendiez toujours à ce que ce soit (35.0, 0.0), pas (34.0, almost-0.1), Mais vous ne pouvez pas l'avoir à cause d'erreurs d'arrondi.

Si vous cherchez une solution rapide, pensez à utiliser le type Decimal :

>>> from decimal import Decimal
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')
>>> print(Decimal('3.5') % Decimal('0.1'))
0.0
>>> (Decimal(7)/2) % (Decimal(1)/10)
Decimal('0.0')

Ce n'est pas une panacée magique - par exemple, vous devrez toujours faire face à une erreur d'arrondi chaque fois que la valeur exacte d'une opération n'est pas représentable de manière finie en base 10 - mais les erreurs d'arrondi correspondent mieux aux cas auxquels l'intuition humaine s'attend être problématique. (Il y a aussi des avantages à Decimal par rapport à float en ce que vous pouvez spécifier des précisions explicites, suivre les chiffres significatifs, etc., et en ce que c'est en fait la même chose dans tous Python versions de 2.4 à 3.3, tandis que les détails sur float ont changé deux fois en même temps. C'est juste que ce n'est pas parfait, car ce serait impossible.) Mais quand vous savez à l'avance que vos chiffres sont tous exactement représentables en base 10, et ils n'ont pas besoin de plus de chiffres que la précision que vous avez configurée, cela fonctionnera.

64
abarnert

Modulo vous donne le rest d'une division. 3.5 divisé par 0.1 devrait vous donner 35 avec un reste de 0. Mais comme les flottants sont basés sur des puissances de deux, les nombres ne sont pas exacts et vous obtenez des erreurs d'arrondi.

Si vous avez besoin que votre division des nombres décimaux soit exacte, utilisez le module décimal:

>>> from decimal import Decimal
>>> Decimal('3.5') / Decimal('0.1')
Decimal('35')
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')

Comme je suis critiqué que ma réponse soit trompeuse, voici toute l'histoire:

0.1 est légèrement plus grand que 0.1

>>> '%.50f' % 0.1
'0.10000000000000000555111512312578270211815834045410'

Si vous divisez le flotteur 3.5 par un tel nombre, vous obtenez un reste de presque 0.1.

Commençons par le nombre 0.11 et continuez à ajouter des zéros entre les deux 1 chiffres afin de le rendre plus petit tout en le gardant plus grand que 0.1.

>>> '%.10f' % (3.5 % 0.101)
'0.0660000000'
>>> '%.10f' % (3.5 % 0.1001)
'0.0966000000'
>>> '%.10f' % (3.5 % 0.10001)
'0.0996600000'
>>> '%.10f' % (3.5 % 0.100001)
'0.0999660000'
>>> '%.10f' % (3.5 % 0.1000001)
'0.0999966000'
>>> '%.10f' % (3.5 % 0.10000001)
'0.0999996600'
>>> '%.10f' % (3.5 % 0.100000001)
'0.0999999660'
>>> '%.10f' % (3.5 % 0.1000000001)
'0.0999999966'
>>> '%.10f' % (3.5 % 0.10000000001)
'0.0999999997'
>>> '%.10f' % (3.5 % 0.100000000001)
'0.1000000000'

La dernière ligne donne l'impression que nous avons enfin atteint 0.1 mais changer les chaînes de format révèle la vraie nature:

>>> '%.20f' % (3.5 % 0.100000000001)
'0.09999999996600009156'

Le format flottant par défaut de python n'a tout simplement pas assez de précision pour que le 3.5 % 0.1 = 0.1 et 3.5 % 0.1 = 35.0. Ça l'est vraiment 3.5 % 0.100000... = 0.999999... et 3.5 / 0.100000... = 34.999999..... En cas de division, vous vous retrouvez même avec le résultat exact comme 34.9999... est finalement arrondi à 35.0.


Fait amusant: si vous utilisez un nombre légèrement inférieur à 0.1 et effectuez la même opération que vous vous retrouvez avec un nombre légèrement supérieur à 0:

>>> 1.0 - 0.9
0.09999999999999998
>>> 35.0 % (1.0 - 0.9)
7.771561172376096e-15
>>> '%.20f' % (35.0 % (1.0 - 0.9))
'0.00000000000000777156'

En utilisant C++, vous pouvez même montrer que 3.5 divisé par le flotteur 0.1 n'est pas 35 mais quelque chose d'un peu plus petit.

#include <iostream>
#include <iomanip>

int main(int argc, char *argv[]) {
    // double/float, rounding errors do not cancel out
    std::cout << "double/float: " << std::setprecision(20) << 3.5 / 0.1f << std::endl;
    // double/double, rounding errors cancel out
    std::cout << "double/double: " << std::setprecision(20) << 3.5 / 0.1 << std::endl;
    return 0;
}

http://ideone.com/fTNVho

Dans Python 3.5 / 0.1 vous donne le résultat exact de 35 car les erreurs d'arrondi s'annulent. Ça l'est vraiment 3.5 / 0.100000... = 34.9999999.... Et 34.9999... est finalement si longue que vous vous retrouvez avec exactement 35. Le programme C++ le montre bien car vous pouvez mélanger double et flotter et jouer avec les précisions des nombres à virgule flottante.

7
bikeshedder

Cela a à voir avec la nature inexacte de l'arithmétique en virgule flottante. 3.5 % 0.1 me fait 0.099999999999999811, donc Python pense que 0,1 se divise en 3,5 au maximum 34 fois, avec 0,09999999999999999811. Je ne sais pas exactement quel algorithme est utilisé pour atteindre ce résultat, mais c'est le Essentiel.

1
jjlin