On a appris à l'école que 1 + 1 = 2, donc on retrouve cela également en python
Copier le code
1 2 >>> 1 + 1 2
Si on applique ce principe sur des flottants on obtient
Copier le code
1 2 >>> 1.0 + 1.0 2.0
Maintenant prenons un flottant avec une décimale
Copier le code
1 2 3 4 >>> 0.1 + 0.1 0.2 >>> 0.2 + 0.1 0.30000000000000004
Mais pourquoi cette différence dans le résultat ci-dessus?
Prenons l'example de 0.625, pour connaîte la valeur binaire de la partie décimal (625) il faut multiplier par 2 cette valeur et prendre la partie entière comme valeur binaire. On continue tant que la partie décimale n'est pas nulle
0.625 * 2 = 1.25
0.25 * 2 = 0.5
0.5 * 2 = 1.0
C'est ainsi que l'on obtient 101 (base 2) pour la valeur décimale 625 (base 10) du float 0.625
Mais il faut savoir qu'il est assez rare d'otenir une valeur base 2 qui vaut exactement la valeur de la base 10, c'est vrai dans l'exemple juste avant. Mais reprenons notre exemple de
Copier le code
1 2 >>> 0.2 + 0.1 0.30000000000000004
En fait 0.2 (et également 0.1) ne vaut pas exactement cette valeur, pour vérifier tranformont 0.2 (base 10) en base 2 avec le même principe que 0.625 vu au dessus
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
....
Et on peut continuer un bon moment comme cela. Mais alors que vaut vraiment 0.2 ?
On peut utiliser une librairie
Decimal
Copier le code
1 2 3 >>> from decimal import Decimal >>> Decimal(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625')
Et donc pour 0.2 on obtient
Copier le code
1 2 >>> Decimal(0.2) Decimal('0.200000000000000011102230246251565404236316680908203125')
Et c'est ainsi que lorsque l'on additionne les 2 floattants on obtient une valeur proche de 3 : 0.30000000000000004
Et pourquoi autant de chiffres après la virgule ?
Reprenons l'exemple de
Copier le code
1 2 >>> 0.2 + 0.1 0.30000000000000004
Si on compte les décimales on en trouve 17 car python utilise la fonction
repr()On peut également utiliser la fonction
format
Copier le code
1 2 >>> format(0.1+0.2) '0.30000000000000004'
à laquelle on lui indique la précision de 20 chiffres après la virgule
Copier le code
1 2 >>> format(0.1+0.2, '.20f') '0.30000000000000004441'
Et si on veut 80 chiffres après la virgule
Copier le code
1 2 >>> format(0.1+0.2, '.80f') '0.30000000000000004440892098500626161694526672363281250000000000000000000000000000'
On remarque qu'après le 53ème chiffre on obtient des zéro. Pourquoi ?
Python tourne sur un moteur CPython par défaut qui est écrit en C. Et dans ce langage l'équipe dev de python se repose sur un double, pour la gestion des
float
Dans le 64 bits on retrouve :
- 1 bit de signe
- 11 bits d'exposant
- 53 bits de mantisse
Tout est expliqué plus en détail https://en.wikipedia.org/wiki/Double-precision_floating-point_format
On peut retrouver ces informations dans le module
sys
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 >>> sys.float_info sys.float_info( max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1 )
Où :
- dig=15 est le nombre de digit après la virgule
- mant_dig=53 est la mantisse de 53 bits
- max=1.7976931348623157e+308 nous indique le plus grand nombre possible
La valeur 0.3 à une précision de 53 chiffres (qui n'a rien à voir avec mant_dig) qui est bien inférieure à ce que permet un double. On vient de voir que l'on peut avoir jusque 308 chiffres (propriété
max
sys.float_info
Maintenant que l'on connait un peu mieux la précision des floats, regardons comment faire des tests conditionnels
Copier le code
1 2 >>> 0.3 == 0.1 + 0.2 False
Nous venons d'en faire la démonstation juste au dessus, 0.3 est une approximation (il faut réellement 0.3000000000000000444089209850062616169452667236328125) donc il parait pertinent que le résultat vaut
False
Mais comment tester l'égalité d'un flottant ?
Nous allons passé par la lib
Fraction
Copier le code
1 2 3 4 5 >>> from fractions import Fraction >>> Fraction(0.625) Fraction(5, 8) >>> Fraction(0.1+0.2) Fraction(1351079888211149, 4503599627370496)
La fraction peut également être obtenue avec cette fonction
as_integer_ratio()Copier le code
1 2 3 4 >>> 0.625.as_integer_ratio() (5, 8) >>> (0.1 + 0.2).as_integer_ratio() (1351079888211149, 4503599627370496)
Et donc on peut faire les tests
Copier le code
1 2 3 4 5 6 7 8 9 >>> 0.625 == 5/8 True >>> 0.625 == Fraction(0.625) True >>> 0.1 + 0.2 == 1351079888211149 / 4503599627370496 True >>> 0.1 + 0.2 == Fraction(0.1 + 0.2) True
On peut également passer par la lib
Decimal
Copier le code
1 2 3 4 >>> 0.1 + 0.2 == Decimal(0.1 + 0.2) True >>> 0.625 == Decimal(0.625) True
Et bien évidemment comme il s'agit d'approximation les tests suivants échouent logiquement
Copier le code
1 2 3 4 5 6 7 8 >>> 0.1 + 0.2 == 0.3 False >>> 0.1 + 0.2 == Decimal(0.1) + Decimal(0.2) False >>> 0.3 == Decimal(0.1) + Decimal(0.2) False >>> 0.3 == Decimal(0.1 + 0.2) False
J'espère que ces explications vont vous permettre de mieux appréhender la manipulation des floats
Synthèse
Si vous n'avez pas besoin de précision dans les manipulations de
float
python
Decimal
format