ホームページ  >  記事  >  バックエンド開発  >  PHPによる高精度な計算を実現

PHPによる高精度な計算を実現

小云云
小云云オリジナル
2018-03-27 14:58:474667ブラウズ

この記事では、主に PHP で高精度の計算を実現する方法について説明します。私は金融業界で働いており、資金計算を頻繁に行うことがあります。ここでは、私が遭遇した落とし穴についてお話します。注意しないと、ユーザーが損をする可能性があります。何十万もの資金、あるいはさらに恐ろしい... ... 直接例に行きましょう:

javascript

0.1 + 0.2 なぜ 0.3 に等しくないのですか? (正しい結果: 0.30000000000000004)

0.8 * 7なぜ 5.6 に等しくないのでしょうか? (正しい結果: 5.6000000000000005)

PHP

var_dump(intval(0.58 * 100));

正しい結果は 58 ではなく 57 です

浮動小数点演算によって引き起こされる問題

実際、これらの結果は言語のバグではなく、整数を含むすべての数値が実際には double 型であるという言語の実装原則に関連しています。

そして、PHP は int と float を区別します。どの言語であっても、浮動小数点演算を伴うものであれば同様の問題があるので、使用する際には注意が必要です。

注: PHP の +-*/ を使用して浮動小数点数を計算すると、不正確な計算結果によって問題が発生する可能性があります。たとえば、上記の echo intval(0.58*100); は 58 ではなく 57 を出力します。実際には、コンピューターの基礎となるバイナリが浮動小数点数を正確に表現できないというバグです。私も Python を使用していてこの問題に遭遇しました。したがって、基本的にほとんどの言語では、精密な計算を行うためのクラス ライブラリまたは関数ライブラリが提供されています。たとえば、PHP には BC 高精度関数ライブラリがあり、後でよく使用される BC 高精度関数をいくつか紹介します。

上記の 57,58 の質問に戻りましょう。

なぜ出力が 57 なのでしょうか?これは PHP のバグですか?

この理由を理解するには、まず浮動小数点数 (IEEE 754) の表現を知る必要があります:

長さ 64 ビットの浮動小数点数(double precision) are たとえば、1 つの符号ビット (E)、11 の指数ビット (Q)、および 52 の仮数ビット (M) を使用して (合計 64 ビット) を表します

符号ビット: 最上位ビット。はデータの符号を表し、0 は正の数を表し、1 は負の数を表します。

指数ビット:データの累乗を基数2で表し、指数はオフセットコードで表されます

仮数部:データの小数点以下の有効桁を表します

ここで重要なのは、その表現です。 2 進数での 10 進数の表現については、Baidu で検索できます。理解する必要がある重要な点は、2 進数表現では 0.58 が無限に長い値であるということです。次の数値は暗黙の 1) を省略します。 1010111000010100011110101 11000010100011110 そして、次の方法のみで計算した場合の 2 つの 2 進数これらの 52 ビットは次のとおりです: www.111cn.net

0.58 -> 0.579999999999999960.57 -> 0.5699999999999999 0.58 * 100 の具体的な浮動小数点乗算については、詳しくは説明しません。それ(浮動小数点)を暗算でぼんやり見ていきます… 0.58 * 100 = 57.999999999

それを積分すると当然57になります…

重要なポイントであることが分かりますこの問題の主な理由は次のとおりです。「一見有限であるように見える 10 進数が、コンピューターのバイナリ表現では無限大です。」

したがって、これは PHP のバグだとは考えていませんでしたが、これが実際のところです...

PHP 浮動小数点処理における +-*%/ の不正確さ

コードの一部を見てください:

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

出力された値は boolean false

これはなぜですか?PHP マニュアルには浮動小数点に関する次の警告メッセージがあります。数値:

警告

浮動小数点数の精度

  显然简单的十进制分数如同 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如何处理高精度计算函数

以上がPHPによる高精度な計算を実現の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。