Oubliez vos cours de mathématiques et apprenez à compter avec l'informatique.
Si je vous dis que 0.1 + 0.2
n'est pas égale à 0.3
vous allez sans doute me prendre pour un débile ou alors penser à la fameuse réplique de JCVD que 1 + 1 égale à 11. Et bien détrompez-vous car en fait j'ai bien raison.
<?php
if (0.1 + 0.2 != 0.3) {
echo "0.1 + 0.2 is NOT EQUAL to 0.3";
}
<?php
if (0.1 + 0.2 != 0.3) {
echo "0.1 + 0.2 is NOT EQUAL to 0.3";
}
Vous pouvez même le tester par vous-même.
La raison est un peu complexe à expliquer mais on va essayer.
Le binaire c'est un système de numérotation qui est utilisé dans nos ordinateurs et qui se compose de 2 chiffres (d'où le terme base 2), à savoir 0 ou 1. Mais si tout n'est que 0 ou 1, comment représenter un nombre qui est justement entre 0 et 1 ?
J'ai par exemple le nombre 0.006458, si je le représente en notation scientifique j'ai 6.458 x 10
Si on prend par exemple ce chiffre à virgule sous forme binaire 0.0110111, qui peut s'écrire 1.10111 x 2
Dans la vraie vie, si on prend les nombres réels qui sont compris entre 0 et 0,1, on va avoir par exemple
Mais en informatique, pas du tout. Cela ne va être qu'un échantillon de ces nombres réels. Voici un petit schéma pour peut comprendre. En bleu vous avez les nombres réels (il y en a tellement que cela fait une barre bleue continue) et les nombres à virgules en informatique en rouge.
Donc si je dis $a = 0.1;
, comme vous pouvez le voir, il n'y a pas de représentation de 0.1 dans les barres rouges. Donc le système va prendre la plus proche de 0.1 et donc nous aurons comme résultat 0.10000000000555.
Maintenant si on regarde la valeur de 0.2 c'est 0.2000000000000000111 et pour 0.3 c'est 0.2999999999999999889.
Et si on fait 0.10000000000555 + 0.2000000000000000111, vous êtes bien d'accord que ce n'est pas égal à 0.3 ni à 0.2999999999999999889 et encore moins à la somme des deux valeurs qui est 0.30000000000000004441.
A la place de gérer des floats, gérez plutôt des integers. Des nombres sans virgules en somme. Par exemple, si je dois gérer le prix d'un produit sur un site e-commerce. Je ne vais pas enregistrer dans ma base de données son nombre réel mais son nombre multiplié par un autre pour avoir un entier. Le plus simple est de le multiplier par 100.
Ainsi si j'ai un produit qui coûte 9,99€, je vais stocker 999. Ainsi, si je dois faire des calculs par la suite pour ajouter le prix de 2 produits dans mon panier, j'aurai comme montant 1998, et si je veux l'afficher à n'utilisateur, là je pourrais diviser par 100 et faire un arrondi à 2 chiffres après la virgule. Je réduis ainsi les problèmes de calculs et d'arrondis du langage.
Une autre solution consiste (en PHP) à utiliser la librairie PHP Decimal qui permet de manipuler justement des nombres à virgules mais sans avoir tous ces problèmes. Elle demande juste à utiliser PHP7 (ce que normalement vous avez tous) et la librairie libmpdec 2.4+ qui est librairie écrite en C qui permet de gérer correctement les arrondis arbitraires des floats.
Dans une API, utilisez de préférence des strings pour éviter d'avoir des erreurs à la fois dans l'affichage de votre JSON mais à la fois dans la récupération des informations du JSON que votre utilisateur va vous envoyer.
L'extension BCMath de PHP, permet justement de compter et comparer des nombres de grande taille.
Si vous souhaitez aller plus loin, je vous conseille de regarder la conférence de Benoît JACQUEMONT qu'il a donné en 2020 au forum PHP. Il vous explique aussi ce que ces approximatins pose comme problèmes dans la vie de tous les jours et notamment dans l'espace avec la fusée Ariane 5.