Question

Parce que le type de données float en PHP est inexact, et qu'un FLOAT dans MySQL prend plus de place qu'un INT (et qu'il est inexact), je stocke toujours les prix sous la forme d'INT, en les multipliant par 100 avant de stocker pour garantir que nous avons exactement 2 décimales. lieux de précision. Cependant, je crois que PHP se comporte mal. Exemple de code:

echo "<pre>";

$price = "1.15";
echo "Price = ";
var_dump($price);

$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);


$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);

echo "</pre>";

Sortie produite:

Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)

J'ai été surpris. Lorsque le résultat final était inférieur aux attentes de 1, je m'attendais à ce que le résultat de mon test ressemble davantage à:

Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)

qui démontrerait l'inexactitude du type float. Mais pourquoi floor (115) renvoie-t-il 114 ??

Était-ce utile?

La solution

Essayez ceci comme solution rapide:

$price_int = intval(floor($price_corrected + 0.5));

Le problème que vous rencontrez n'est pas dû à PHP, tous les langages de programmation utilisant des nombres réels avec arithmétique à virgule flottante ont des problèmes similaires.

La règle générale pour les calculs monétaires est de ne jamais utiliser de float (ni dans la base de données ni dans votre script). Vous pouvez éviter toutes sortes de problèmes en stockant toujours les centimes au lieu de dollars. Les centimes sont des entiers que vous pouvez librement ajouter et multiplier par d'autres entiers. Lorsque vous affichez le numéro, veillez à insérer un point devant les deux derniers chiffres.

Si vous obtenez 114 au lieu de 115, c'est parce que floor arrondit au nombre entier le plus proche, ainsi floor (114.999999999) devient 114. La question la plus intéressante est de savoir pourquoi 1.15 * 100 est 114.999999999. au lieu de 115. La raison en est que 1,15 n'est pas exactement 115/100, mais c'est un tout petit peu moins, donc si vous multipliez par 100, vous obtenez un nombre un peu plus petit que 115.

Voici une explication plus détaillée de ce que echo 1.15 * 100; fait:

  • Il analyse 1.15 en un nombre à virgule flottante. Cela implique d'arrondir, il arrive d'arrondir un peu pour obtenir le nombre à virgule flottante le plus proche de 1,15. La raison pour laquelle vous ne pouvez pas obtenir un nombre exact (sans erreur d’arrondi) est que 1.15 a un nombre infini de nombres en base 2.
  • Il analyse 100 en un nombre à virgule flottante binaire. Ceci implique d’arrondir, mais comme 100 est un petit nombre entier, l’erreur d’arrondi est zéro.
  • Il calcule le produit des deux nombres précédents. Cela implique également un peu d'arrondi, pour trouver le nombre à virgule flottante binaire le plus proche. L’erreur d’arrondi est zéro dans cette opération.
  • Il convertit le nombre à virgule flottante binaire en un nombre décimal de base 10 avec un point et affiche cette représentation. Cela implique également un peu d'arrondissement.

La raison pour laquelle PHP imprime le surprenant Prix corrigé = float (115) (au lieu de 114,999 ...) est que var_dump n'imprime pas le nombre exact ( !), mais le nombre est arrondi à n - 2 (ou n - 1 ), où n chiffres est la précision du calcul. Vous pouvez facilement vérifier ceci:

echo 1.15 * 100;  # this prints 115
printf("%.30f", 1.15 * 100);  # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different";  # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less";    # this prints `less'

Si vous imprimez des flottants, rappelez-vous: vous ne voyez pas toujours tous les chiffres lorsque vous imprimez le flottant .

Voir aussi le gros avertissement au début des flottant PHP documents.

Autres conseils

Les autres réponses ont couvert la cause et une bonne solution de contournement au problème, je crois.

Viser à résoudre le problème sous un angle différent:

Pour stocker les valeurs de prix dans MySQL, vous devriez probablement consulter Type DECIMAL , qui vous permet de stocker des valeurs exactes avec des décimales.

PHP arrondit en fonction des chiffres significatifs. Cela cache l'inexactitude (ligne 2). Bien sûr, quand le sol monte, il ne sait pas mieux et le laisse tomber complètement.

C’est peut-être une autre solution possible à ce "problème":

intval(number_format($problematic_float, 0, '', ''));

Comme indiqué, ce n'est pas un problème avec PHP en soi, mais plutôt un problème de traitement des fractions qui ne peuvent pas être exprimées sous forme de valeurs à virgule flottante finies, ce qui entraîne une perte de caractère lors de l'arrondi.

La solution consiste à s'assurer que lorsque vous travaillez sur des valeurs à virgule flottante et que vous devez conserver la précision - utilisez les fonctions gmp ou BC Maths - bcpow, bcmul et al. et le problème sera résolu facilement.

E.g au lieu de $ price_corrected = $ price * 100;

utilisez $ price_corrected = bcmul ($ price, 100);

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top