Maison  >  Article  >  développement back-end  >  PHP réalise des calculs de haute précision

PHP réalise des calculs de haute précision

小云云
小云云original
2018-03-27 14:58:474613parcourir

Cet article vous explique principalement comment réaliser des calculs de haute précision en PHP. Je travaille dans le secteur financier et je calcule fréquemment des fonds. Ici, je vais parler des pièges que j'ai rencontrés... Si vous ne faites pas attention, les utilisateurs peuvent. perdre des centaines de milliers de fonds, voire plus terrifiant..... Passons directement à l'exemple :

javascript

0,1 + 0,2 Pourquoi n'est-il pas égal à 0,3 (Correct ? Résultat: 0,30000000000000004)

0,8 * 7 Pourquoi pas égal à 5,6? >

Le résultat correct est 57, pas 58

Problèmes causés par les opérations en virgule flottante

En fait, ces résultats ne sont pas des bugs dans le langage, mais sont liés au principe d'implémentation du langage.Tous les nombres dans js sont unifiés en tant que Nombre, y compris les entiers. En fait, tous les nombres sont de type double.

Et PHP fera la distinction entre int et float. Quelle que soit la langue, tant que des opérations en virgule flottante sont impliquées, il existe des problèmes similaires, vous devez donc y prêter attention lorsque vous les utilisez.

Remarque : si vous utilisez +-*/ de PHP pour calculer des nombres à virgule flottante, vous pouvez rencontrer des problèmes avec des résultats de calcul incorrects. Par exemple, l'écho intval(0.58*100) ci-dessus affichera 57 au lieu de. 58. Il s'agit en fait d'un bug selon lequel le système binaire sous-jacent de l'ordinateur ne peut pas représenter avec précision les nombres à virgule flottante. J'ai également rencontré ce problème en utilisant Python. Donc, fondamentalement, la plupart des langages fournissent des bibliothèques de classes ou des bibliothèques de fonctions pour des calculs précis. Par exemple, PHP dispose d'une bibliothèque de fonctions de haute précision BC. Plus tard, je présenterai certaines fonctions de haute précision BC couramment utilisées.

Revenons aux questions 57 et 58 ci-dessus.

Pourquoi la sortie est-elle 57 ? Est-ce un bug PHP ?

Pour comprendre cette raison, nous devons d'abord connaître la représentation des nombres à virgule flottante (IEEE 754) :

Les virgules flottantes, en prenant comme exemple une longueur de 64 bits (double précision), seront représentées par 1 bit de signe (E), 11 bits d'exposant (Q) et 52 bits de mantisse (M) (un total de 64 bits) .

Bit de signe : Le bit le plus élevé indique le signe des données, 0 indique un nombre positif et 1 indique un nombre négatif.

Bit exposant : représente la puissance des données en base 2, et l'exposant est représenté par un code de décalage

Mantisse : représente les chiffres significatifs après la virgule décimale

Les points clés ici Cela réside dans la représentation des décimales en binaire. Concernant la façon de représenter les décimales en binaire, vous pouvez effectuer une recherche sur Baidu. Je n'entrerai pas dans les détails ici. représentation binaire, 0,58 est une valeur infinie (les nombres ci-dessous en omettant le 1 implicite)..

La représentation binaire de 0,58 est essentiellement (52 bits) : 00101000111101011100001010001111010111000010100011110,57 est essentiellement (52 bits) : 0011110 1011100001010001111010111000010100011110 Et le binaire des deux, si juste passer Pour les calculs de ces 52 bits, ils sont : www.111cn.net

 0.58 -> 0.579999999999999960.57 -> nous ne le considérons pas en détail, si cela vous intéresse Vous pouvez voir (virgule flottante), regardons-le vaguement avec le calcul mental... 0,58 * 100 = 57,999999999

Alors si vous l'intalisez, ce sera naturellement être 57...

On peut voir que la clé de ce problème Le fait est : "Votre décimale apparemment finie est en fait infinie dans la représentation binaire de l'ordinateur"

Par conséquent, ne pensez plus que ce soit un bug PHP, c'est ce que c'est...

Le type à virgule flottante PHP a une inexactitude de +-*%/

Continuez à regarder un morceau de code :

La valeur imprimée est booléenne fausse

Pourquoi ? Le manuel PHP contient le message d'avertissement suivant pour les nombres à virgule flottante :

Avertissement
$a = 0.1;
$b = 0.7;
var_dump(($a + $b) == 0.8); // false

Précision en virgule flottante

  显然简单的十进制分数如同 0.1 或 0.7 不能在不丢失一点点精度的情况下转换为内部二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999…。

  这和一个事实有关,那就是不可能精确的用有限位数表达某些十进制分数。例如,十进制的 1/3 变成了 0.3333333. . .。

 所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数

那么上面的算式我们应该改写为

$a = 0.1;
$b = 0.7;
var_dump(bcadd($a,$b,2) == 0.8); // true

常用的高精度函数如下:

bc是Binary Calculator的缩写

bcadd — 将两个高精度数字相加
  bccomp — 比较两个高精度数字,返回-1, 0, 1
  bcp — 将两个高精度数字相除
  bcmod — 求高精度数字余数
  bcmul — 将两个高精度数字相乘
  bcpow — 求高精度数字乘方
  bcpowmod — 求高精度数字乘方求模,数论里非常常用
  bcscale — 配置默认小数点位数,相当于就是Linux bc中的”scale=”
  bcsqrt — 求高精度数字平方根
  bcsub — 将两个高精度数字相减
BC高精确度函数库包含了:相加,比较,相除,相减,求余,相乘,n次方,配置默认小数点数目,求平方。这些函数在涉及到有关金钱计算时比较有用,比如电商的价格计算。
/**
  * 两个高精度数比较
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale 精确到的小数点位数
  * 
  * @return int $left==$right 返回 0 | $left<$right 返回 -1 | $left>$right 返回 1
  */
var_dump(bccomp($left=4.45, $right=5.54, 2));
// -1
  
 /**
  * 两个高精度数相加
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale 精确到的小数点位数
  * 
  * @return string 
  */
var_dump(bcadd($left=1.0321456, $right=0.0243456, 2));
//1.05
 
  /**
  * 两个高精度数相减
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale 精确到的小数点位数
  * 
  * @return string 
  */
var_dump(bcsub($left=1.0321456, $right=3.0123456, 2));
//-1.98
  
 /**
  * 两个高精度数相除
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale 精确到的小数点位数
  * 
  * @return string 
  */
var_dump(bcp($left=6, $right=5, 2));
//1.20
 
 /**
  * 两个高精度数相乘
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale 精确到的小数点位数
  * 
  * @return string 
  */
var_dump(bcmul($left=3.1415926, $right=2.4569874566, 2));
//7.71
 
 /**
  * 设置bc函数的小数点位数
  * 
  * @access global
  * @param int $scale 精确到的小数点位数
  * 
  * @return void 
  */ 
bcscale(3);
var_dump(bcp(&#39;105&#39;, &#39;6.55957&#39;)); 
//php7.1 16

所以平时程序要要封装方法:

/**
 * 精确加法
 * @param [type] $a [description]
 * @param [type] $b [description]
 */
function math_add($a,$b,$scale = &#39;2&#39;) {
  return bcadd($a,$b,$scale);
}
/**
 * 精确减法
 * @param [type] $a [description]
 * @param [type] $b [description]
 */
function math_sub($a,$b,$scale = &#39;2&#39;) {
  return bcsub($a,$b,$scale);
}
/**
 * 精确乘法
 * @param [type] $a [description]
 * @param [type] $b [description]
 */
function math_mul($a,$b,$scale = &#39;2&#39;) {
  return bcmul($a,$b,$scale);
}
/**
 * 精确除法
 * @param [type] $a [description]
 * @param [type] $b [description]
 */
function math_p($a,$b,$scale = &#39;2&#39;) {
  return bcp($a,$b,$scale);
}
/**
 * 精确求余/取模
 * @param [type] $a [description]
 * @param [type] $b [description]
 */
function math_mod($a,$b) {
  return bcmod($a,$b);
}
/**
 * 比较大小
 * @param [type] $a [description]
 * @param [type] $b [description]
 * 大于 返回 1 等于返回 0 小于返回 -1
 */
function math_comp($a,$b,$scale = &#39;5&#39;) {
  return bccomp($a,$b,$scale); // 比较到小数点位数
}

相关推荐:

php如何处理高精度计算函数

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn