PHP-précision du nombre flottant [dupliquer]

cette question a déjà une réponse ici:

  • est-ce que les maths à virgule flottante sont cassées? 27 Réponses
$a = '35';
$b = '-34.99';
echo ($a + $b);

résultats dans 0.0099999999998

Qu'est-ce qui se passe avec ça? Je me demandais Pourquoi mon programme continuait à rapporter des résultats étranges.

pourquoi PHP ne renvoie-t-il pas le 0,01 attendu?

71
demandé sur Joe DF 2010-09-16 16:39:59

9 réponses

parce que l'arithmétique flottante != arithmétique des nombres réels. Une illustration de la différence due à l'imprécision est, pour certains flotteurs a et b , (a+b)-b != a . Ceci s'applique à toutes les langues utilisant des flotteurs.

depuis point flottant sont des nombres binaires avec une précision finie, il ya une quantité finie de nombres représentables , qui conduit problèmes de précision et les surprises de ce genre. Voici une autre lecture intéressante: ce que tout informaticien devrait savoir sur L'arithmétique flottante .


retour à votre problème, fondamentalement, il n'y a aucun moyen de représenter avec précision 34.99 ou 0,01 en binaire (tout comme en décimale, 1/3 = 0.3333...), de sorte que des approximations sont utilisées à la place. Pour contourner le problème, vous pouvez:

  1. Utiliser round($result, 2) sur le résultat d'arrondir à 2 chiffres après la virgule.

  2. utiliser des entiers. Si c'est la monnaie, disons dollars américains, alors stocker 35,00 $comme 3500 et 34,99 $comme 3499, puis diviser le résultat par 100.

c'est dommage que PHP n'ait pas un type de données décimal comme autre langues do.

113
répondu NullUserException 2017-12-11 23:39:47

les nombres à virgule flottante, comme tous les nombres, doivent être stockés en mémoire sous forme de chaîne de 0 et de 1. C'est tout pour l'ordinateur. Comment virgule flottante diffère entier est dans la façon dont nous interprétons les valeurs 0 et 1 lorsque l'on veut regarder.

Un bit est le "signe" (0 = positif, 1 = négatif), 8 bits de l'exposant (allant de -128 à +127), 23 bits sont le nombre connu comme le "mantisse" (fraction). Ainsi la représentation binaire de (S1) (P8) (M23) a le la valeur (-1^S)M*2^P

le "mantissa" prend une forme spéciale. En notation scientifique normale, nous affichons la "place de quelqu'un" avec la fraction. Par exemple:

4,39 x 10^2 = 439

binaire "la place" est un peu unique. Puisque nous ignorons tous les 0 de gauche dans la notation scientifique (nous ignorons les chiffres insignifiants) le premier bit est garanti pour être un 1

1.101 x 2^3 = 1101 = 13

puisque nous sommes assurés que le premier bit sera un 1, nous supprimons ce bit lors du stockage du numéro pour économiser de l'espace. Ainsi le nombre ci-dessus est stocké comme seulement 101 (pour le mantissa). Le premier est supposé

prenons comme exemple la chaîne binaire

00000010010110000000000000000000

le découper en ses composants:

Sign    Power           Mantissa
 0     00000100   10110000000000000000000
 +        +4             1.1011
 +        +4       1 + .5 + .125 + .0625
 +        +4             1.6875

application de notre formule simple:

(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27

dans les autres mots, 0000001001011000000000000000000000 est de 27 en virgule flottante (selon les normes IEEE-754).

pour beaucoup de nombres il n'y a pas de représentation binaire exacte, cependant. Un peu comme 1/3 = 0,333.... répéter pour toujours, 1/100 est 0,00000010100011110101110000..... avec une répétition "10100011110101110000". Un ordinateur 32 bits ne peut pas stocker l'ensemble nombre en virgule flottante, cependant. Donc, il fait de son mieux deviner.

0.0000001010001111010111000010100011110101110000

Sign    Power           Mantissa
 +        -7     1.01000111101011100001010
 0    -00000111   01000111101011100001010
 0     11111001   01000111101011100001010
01111100101000111101011100001010

(notez que négatif 7 est cents cinquante et une million neuf cent cinquante mille neuf cent vingt"

il doit être immédiatement clair que 01111100101000111101011100001010 ressemble à rien 0,01

plus important, cependant, il contient une version tronquée d'une décimale répétée. La décimale originale contenait une répétition "101000111101110000". Nous avons simplifié ceci à 01000111101011100001010

traduisant ce nombre de virgule flottante en décimal via notre formule nous obtenons 0,0099999979 (notez que c'est pour un ordinateur 32 bits. Un ordinateur 64 bits, aurait beaucoup plus de précision)

45
répondu stevendesu 2017-02-15 02:06:42

il y a beaucoup de réponses ici sur les raisons pour lesquelles les nombres à virgule flottante fonctionnent comme ils le font...

mais on ne parle guère de précision arbitraire (Pickle l'a mentionné). Si vous voulez (ou avez besoin) de précision exacte, la seule façon de le faire (pour les nombres rationnels au moins) est d'utiliser le BC Math extension (qui est vraiment juste un bignum, précision arbitraire mise en œuvre...

pour ajouter deux numéros:

$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);

donnera 12345678901235.1234567890 ...

c'est ce qu'on appelle les mathématiques de précision arbitraires. Fondamentalement tous les nombres sont des chaînes qui sont analysées pour chaque opération et les opérations sont effectuées sur une base de chiffre par chiffre (pensez division longue, mais fait par la bibliothèque). Cela signifie donc qu'il est assez lent (par rapport aux constructions mathématiques régulières). Mais il est très puissant. Vous pouvez multiplier, additionner, soustraire, diviser, trouver modulo et exponentiez n'importe quel nombre qui a une représentation exacte de la chaîne.

donc vous ne pouvez pas faire 1/3 avec une précision de 100%, car il a une décimale de répétition (et donc n'est pas rationnel).

mais, si vous voulez savoir ce que 1500.0015 au carré est:

utilisant des flotteurs 32 bits (double précision) donne le résultat estimé de:

2250004.5000023

mais bcmath donne la réponse exacte de:

2250004.50000225

tout dépend de la précision dont vous avez besoin.

Aussi, autre chose à noter ici. PHP ne peut représenter que des entiers 32 ou 64 bits (selon votre installation). Ainsi, si un entier dépasse la taille du type int natif (2.1 milliards pour 32bit, 9.2 x10^18, ou 9.2 milliards pour les ints signés), PHP convertira le int en flottant. Bien que ce ne soit pas immédiatement un problème (puisque tous les ints plus petit que la précision du flotteur du système sont par définition directement représentables comme flotteurs), si vous essayez de multiplier deux ensemble, il perdra une précision significative.

par exemple, étant donné $n = '40000000002' :

comme un nombre, $n sera float(40000000002) , ce qui est très bien car il est exactement représenté. Mais si nous le faisons, nous obtenons: float(1.60000000016E+21)

comme une chaîne (en utilisant BC math), $n sera exactement '40000000002' . Et si on règle ça, on obtient: string(22) "1600000000160000000004" ...

donc si vous avez besoin de la précision avec de grands nombres, ou des points décimaux rationnels, vous pourriez vouloir regarder dans bcmath...

14
répondu ircmaxell 2010-10-01 18:33:54

mon php renvoie 0,01... alt text

peut-être qu'il faut faire avec la version php, (j'utilise 5.2)

2
répondu Fribu - Smart Solutions 2010-09-16 12:46:03

utiliser la fonction round() de PHP: http://php.net/manual/en/function.round.php

Cette réponse résout le problème, mais n'explique pourquoi. J'ai pensé que c'était évident [je programmais aussi en C++, donc c'est évident pour moi ;]], mais si ce n'est pas le cas, disons que PHP a sa propre précision de calcul et dans cette situation particulière il a retourné la plupart des informations conformes concernant ce calcul.

2
répondu Tomasz Kowalczyk 2010-09-16 12:58:29

bcadd () pourrait être utile ici.

<?PHP

$a = '35';
$b = '-34.99';

echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);

?>

(inefficace de sortie pour plus de clarté)

la première ligne me donne 0.0099999999998. La seconde me donne 0,01

2
répondu Pickle 2010-09-29 19:49:15

parce que 0,01 ne peut pas être représenté exactement comme la somme des séries de fractions binaires. Et c'est ainsi que les flotteurs sont stockés en mémoire.

je suppose que ce n'est pas ce que vous voulez entendre, mais il est la réponse à la question. Pour corriger voir d'autres réponses.

1
répondu Andrey 2010-09-16 12:48:24

[résolu]

enter image description here

chaque nombre sera enregistré dans l'ordinateur par une valeur binaire telle que 0, 1. En nombres de précision simple occupent 32 bits.

le nombre de virgule flottante peut être présenté par: 1 bit pour signe, 8 bit pour exposant et 23 bit appelé mantissa (fraction).

regardez l'exemple ci-dessous:

0.15625 = 0.00101 = 1.01*2^(-3)

enter image description here

  • signe: 0 Nombre positif moyen, 1 Nombre Négatif moyen, dans ce cas il est 0.

  • exposant: 01111100 = 127 - 3 = 124.

    Note: le biais = 127 ainsi l'exposant biaisé = -3 + le"biais". En précision simple, le biais est de ,127, donc dans cet exemple le l'exposant biaisé est 124;

  • à la fraction, nous avons: 1.01 moyenne: 0*2^-1 + 1*2^-2

    numéro 1 (première position de 1.01) n'ont pas besoin d'enregistrer parce que lorsqu'il présente le nombre flottant de cette façon le premier nombre est toujours 1. Par exemple, convertir: 0.11 => 1.1*2^(-1), 0.01 => 1*2^(-2).

un autre exemple montre toujours supprimer le premier zéro: 0.1 sera présenté 1*2^(-1). De sorte que le premier toujours être 1. Le nombre actuel de 1*2^(-1) sera:

  • 0: Nombre positif
  • 127-1 = 126 = 01111110
  • fraction: 00000000000000000000000 (nombre 23)

enfin: le binaire brut est: 0 01111110 0000000000000000000000000

Vérifiez ici: http://www.binaryconvert.com/result_float.html?decimal=048046053

maintenant si vous comprenez déjà comment un nombre de virgule flottante sont enregistrés. Qu'advient-il si le nombre ne peut pas enregistrer en 32 bits (simple précision).

Par exemple: en décimal. 1/3 = 0.33333333333333333333333333 et parce qu'il est infini, je suppose que nous avons 5 bits pour enregistrer des données. Répéter encore une fois ce n'est pas vrai. supposez juste. Ainsi, les données sauvegardées dans l'ordinateur seront:

0.33333.

maintenant quand le nombre chargé l'ordinateur calculer à nouveau:

0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 +  3*10^-5.

à propos de ceci:

$a = '35';
$b = '-34.99';
echo ($a + $b);

le résultat est 0,01 ( décimal). Maintenant, laissez-afficher ce nombre en binaire.

0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)

Vérifier ici: http://www.binaryconvert.com/result_double.html?decimal=048046048049

parce que (01011100001010001111) est répété exactement comme 1/3. Donc l'ordinateur ne peut pas enregistrer ce nombre dans leur mémoire. Il doit sacrifier. Cette piste pas de précision dans l'ordinateur.

avancé (Vous devez avoir des connaissances en mathématiques ) Alors pourquoi nous pouvons facilement montrer 0.01 en décimal, mais pas en binaire.

supposons que la fraction en binaire de 0,01 (décimale) est finie.

So 0.01 = 2^x + 2^y... 2^-z
0.01 * (2^(x+y+...z)) =  (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists. 

=> So 0.01 (decimal) must be infine in binary.
1
répondu christian Nguyen 2016-08-01 08:09:27

ne serait-il pas plus facile d'utiliser number_format(0.009999999999998, 2) ou $res = $a+$b; -> number_format($res, 2); ?

0
répondu Jurijs Nesterovs 2013-09-04 15:10:19