web-dev-qa-db-fra.com

Puis-je compter sur PHP Solution de contournement de précision php.ini pour les problèmes de virgule flottante

J'ai trouvé une solution de contournement pour problème de virgule flottante en PHP:

paramètre php.ini precision = 14

342349.23 - 341765.07 = 584.15999999992 // floating point problem

paramètre php.ini, disons precision = 8 

342349.23 - 341765.07 = 584.16 // voila!

Démo: http://codepad.org/r7o086sS

C'est comment ça?

1. Puis-je compter sur cette solution si je souhaite juste des calculs précis à 2 chiffres (argent)?

2. Sinon, pouvez-vous me donner un exemple clair lorsque cette solution échoue?  

Edit: 3. Quelle valeur de php.ini.precision convient le mieux aux deux chiffres, calculs monétaires  


  • Veuillez noter que je ne peux pas utiliser de calculs entiers (float * 100 = cents), il est beaucoup trop tard pour cela.
  • Je ne vais pas travailler sur des nombres supérieurs à 10 ^ 6
  • Je n'ai pas besoin de comparer les chiffres

METTRE À JOUR

La réponse de @Baba est bonne, mais il a utilisé precision=20, precision=6 dans ses tests ... Donc, je ne suis toujours pas sûr que ça va marcher ou non. 

S'il vous plaît envisager de suivre:

Disons precision = 8 et la seule chose que je fais est addition + et soustraction -

A + B = C

A - B = C

Question 1: La solution de précision va-t-elle échouer pour les nombres compris entre 0..999999.99, où A et B sont des nombres avec des décimales? Si oui, donnez-moi un exemple. 

Un simple test ferait le travail:

// if it fails what if I use 9,10,11 ???
// **how to find when it fails??? **
ini_set('precision', 8); 
for($a=0;$a<999999.99;$a+=0.01) {
  for($b=0;$b<999999.99;$b+=0.01) {
     // mind I don't need to test comparision (round($a-$b,2) == ($a-$b))
     echo ($a + $b).','.($a - $b)." vs ";
     echo round($a + $b, 2).','.round($a - $b, 2)."\n";
  }
}

mais évidemment 99999999 * 2 est un travail trop important, je ne peux donc pas exécuter ce test

Question 2: Comment estimer/calculer lorsque la solution de contournement de la précision échoue? Sans de tels tests fous? Existe-t-il un calcul mathématique *, une réponse directe? Comment calculer va échouer ou pas?

* Je n'ai pas besoin de savoir que les calculs en virgule flottante fonctionnent, mais lorsque la solution de contournement échoue si vous connaissez la précision et la plage de A et B


S'il vous plaît l'esprit Je sais vraiment cents et bcmath sont la meilleure solution. Mais je ne suis toujours pas sûr que la solution de contournement va échouer ou non pour la soustraction et l'addition

28
Peter

Introduction

L'arithmétique en virgule flottante est considérée par beaucoup comme un sujet ésotérique. Ceci est plutôt surprenant car la virgule flottante est omniprésente dans les systèmes informatiques. La plupart des nombres fractionnaires n'ont pas une représentation exacte en tant que fraction binaire, il y a donc des arrondis en cours. Un bon début est Ce que tout informaticien devrait savoir sur l'arithmétique en virgule flottante } _

Des questions

Question 1

Puis-je compter sur cette solution si je n'ai besoin que de calculs précis à 2 chiffres (argent)?

Réponse 1

Si vous avez besoin de 2 chiffres précis alors la réponse estNONvous ne pouvez pas utiliser les paramètres de précision php pour déterminer un 2 chiffres décimal tout le temps même si vous êtes not going to work on numbers higher than 10^6

Pendant les calculs, il est possible que la longueur de précision puisse être augmentée si la longueur est inférieure à 8

Question 2

Sinon, pouvez-vous me donner un exemple clair lorsque cette solution échoue?

Réponse 2

ini_set('precision', 8); // your precision
$a =  5.88 ; // cost of 1kg
$q = 2.49 ;// User buys 2.49 kg
$b = $a * 0.01 ; // 10% Discount only on first kg ;
echo ($a * $q) - $b;

Sortie 

14.5824 <---- not precise 2 digits calculations even if precision is 8

Question 3

Quelle est la valeur de php.ini.precision qui convient le mieux aux calculs à deux chiffres, les calculs monétaires?

Réponse 3

La précision et le calcul de l'argent sont deux choses différentes ... ce n'est pas une bonne idée d'utiliser la précision PHP comme base pour vos calculs financiers ou votre longueur en virgule flottante 

Test simple

Lest Exécuter quelques exemples ensemble en utilisant bcmath, number_format et simple minus

Base 

$a = 342349.23;
$b = 341765.07;

Example A

ini_set('precision', 20); // set to 20 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Sortie 

584.15999999997438863
584.15999999999996817    <----- Round having a party 
584.16
584.15  <-------- here is 15 because precision value is 20

Example B

ini_set('precision', 14); // change to  14 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Sortie 

584.15999999997
584.16
584.16
584.16  <-------- at 14 it changed to 16

Example C

ini_set('precision', 6); // change to  6 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Sortie 

584.16
584.16
584.16
584.00  <--- at 6 it changed to 00 

Example D

ini_set('precision', 3); // change to 3
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Sortie

584
584
584.16   <-------------------------------- They only consistent value 
0.00  <--- at 3 .. everything is gone 

Conclusion

Oubliez les virgules flottantes et calculez simplement dans cents puis divisé par 100 s'il est trop tard, utilisez simplement number_format, cela me semble cohérent.

Mettre à jour

Question 1: La solution de contournement de précision va-t-elle échouer pour les nombres compris entre 0..999999.99, où A et B sont des nombres avec des décimales? Si oui, donnez-moi un exemple

La forme 0 à 999999.99 à l'incrément de 0.01 est d'environ 99,999,999 la possibilité de combinaison de votre boucle est 9,999,999,800,000,000 Je ne pense vraiment pas que quiconque veuille faire un tel test pour vous.

Puisque les nombres à virgule flottante sont des nombres binaires avec une précision finie, essayer de définir precision aurait un effet limité sur la précision. Voici un test simple:

ini_set('precision', 8);

$a = 0.19;
$b = 0.16;
$c = 0.01;
$d = 0.01;
$e = 0.01;
$f = 0.01;
$g = 0.01;

$h = $a + $b + $c + $d + $e + $f + $g;

echo "Total: " , $h , PHP_EOL;


$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;

echo $i , PHP_EOL;

Sortie 

Total: 0.4
1.0408341E-17     <--- am sure you would expect 0.00 here ;

Essayer 

echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;

Sortie 

0
0.00    <------ still confirms number_format is most accurate to maintain 2 digit 

Question 2: Comment estimer/calculer lorsque la solution de contournement de la précision échoue? Sans de tels tests fous? Existe-t-il une réponse mathématique *, simple? Comment calculer va échouer ou pas?

Le fait demeure toujours { point flottant ont problèmes de précision mais pour les solutions mathématiques, vous pouvez regarder

je n'ai pas besoin de savoir que les calculs en virgule flottante fonctionnent, mais lorsque la solution de contournement échoue si vous connaissez la précision et la plage de A et B

enter image description here

Pas sûr de ce que cette déclaration signifie :)

41
Baba

Je viens de citer this site intéressant au problème. (Aucune réputation attendue :) mais cela devrait être mentionné:

Que puis-je faire pour éviter ce problème (virgule flottante)?

Cela dépend du type de calcul que vous faites.

  • Si vous avez vraiment besoin que vos résultats s’additionnent exactement, en particulier lorsque vous travaillez avec Money: utilisez un type de données décimal spécial.

  • Si vous ne voulez simplement pas voir toutes ces décimales supplémentaires: formatez simplement votre résultat arrondi à un nombre fixe de décimales lors de son affichage.

  • Si vous ne disposez d'aucun type de données décimal, vous pouvez également utiliser des entiers, par exemple. faire des calculs d'argent entièrement en cents. Mais cela représente plus de travail et présente certains inconvénients.

Le site contient également quelques conseils de base pour PHP

Je voudrais utiliser des entiers ou créer un type spécial Decimal pour cela.

Si vous décidez d'utiliser bcmath: Faites attention si vous transmettez ces valeurs à des requêtes SQL ou à d'autres programmes externes. Cela peut entraîner des effets secondaires indésirables s'ils ne sont pas conscients de la précision. (Ce qui est probable)

4
hek2mgl

Selon la documentation, la directive precision ne modifie que les chiffres affichés lors de la conversion de nombres en chaînes:

precision integer
Nombre de chiffres significatifs affichés en nombres à virgule flottante. 

Il s’agit donc d’une alternative très compliquée à number_format () ou money_format () , sauf qu’elle a moins d’options de formatage et qu’elle peut avoir d’autres effets secondaires que vous ignorez peut-être:

<?php

$_POST['amount'] = '1234567.89';

$amount = floatval($_POST['amount']);
var_dump($amount);

ini_set('precision', 5);
$amount = floatval($_POST['amount']);
var_dump($amount);

...

float(1234567.89)
float(1.2346E+6)

Modifier:

J'insiste: ce paramètre ne modifie pas la manière dont PHP effectue des calculs mathématiques avec des nombres. C'est juste une façon magique de changer les options de format lorsque conversion de nombres à virgule flottante (pas même d'entiers!) En chaînes. Exemple:

<?php

ini_set('precision', 2);

$amount = 1000;
$price = 98.76;
$total = $amount*$price;

var_dump($amount, $total);

ini_set('precision', 15);
var_dump($amount, $total);

... impressions:

int(1000)
float(9.9E+4)
int(1000)
float(98760)

Ce qui illustre cela:

  1. Les calculs en virgule flottante ne sont pas affectés, seul le affichage change
  2. Les entiers ne sont pas affectés dans tous les cas
3
Álvaro González

Je pense que si vous arrondissez simplement le résultat obtenu, cela résoudra votre problème de virgule flottante sans qu'il soit nécessaire de modifier votre configuration à l'échelle du serveur.

round(342349.23 - 341765.07, 2) = 584.16
1
Jeff Lambert

Si vous utilisez precision = 8, si vous utilisez un nombre à 8 chiffres, vous ne pouvez pas être certain du 8ème chiffre. Cela pourrait être désactivé de 1 en arrondissant le 9ème chiffre.

Par exemple

12345678.1 -> 12345678
12345678.9 -> 12345679

Cela ne semble pas si grave, mais considérez

   (11111111.2 + 11111111.2) + 11111111.4
-> (11111111)                + 11111111.4
-> 22222222.4
-> 22222222

Par contre, si vous utilisiez précision = 9, ce serait 22222222.8 qui serait arrondi à 22222223.

Si vous ne faites que des additions et des soustractions, vous devez utiliser au moins 2 chiffres de précision de plus que nécessaire pour éviter d’arrondir dans ce type de calcul. Si vous faites la multiplication ou la division, vous aurez peut-être besoin de plus. L'utilisation du strict minimum peut entraîner la perte de chiffres ici et là.

Donc, pour répondre à votre question, vous pourriez vous en tirer si vous êtes chanceux et php utilise une grande précision dans les calculs et stocke ensuite le résultat dans une précision inférieure (et vous n'utilisez pas ce nombre pour continuer. autres calculs), mais en général, c’est une très mauvaise idée car (au moins) le dernier chiffre de votre calcul n’est absolument pas fiable.

0
P O'Conbhui