이 글에서는 주로 PHP에서 고정밀 계산을 수행하는 방법을 공유합니다. 저는 금융 업계에 종사하며 자주 자금 계산을 합니다. 여기에서는 제가 겪은 함정에 대해 이야기하겠습니다... 주의하지 않으면 사용자가 손해를 볼 수 있습니다. 수십만 개의 자금, 또는 훨씬 더 무서운... ... 바로 예를 들어보겠습니다:
0.1 + 0.2 왜 0.3과 같지 않습니까? (올바른 결과: 0.30000000000000004)
0.8 * 7 5.6이 아닌 이유는 무엇인가요? (올바른 결과: 5.6000000000000005)
PHP
var_dump(intval(0.58 * 100));
올바른 결과는 58이 아닌 57입니다
부동 소수점 연산으로 인한 문제
사실 이러한 결과는 언어의 버그가 아니라 언어의 구현 원리와 관련이 있습니다. js 정수를 포함하여 모든 숫자가 Number로 통합되었으며 실제로는 모두 double 유형입니다.
그리고 PHP는 int와 float를 구별합니다. 어떤 언어를 사용하더라도 부동소수점 연산이 포함되는 한 비슷한 문제가 있으므로 사용 시 주의가 필요합니다.
참고: 부동 소수점 숫자를 계산하기 위해 PHP의 +-*/를 사용하는 경우 잘못된 계산 결과로 인해 문제가 발생할 수 있습니다. 예를 들어 위의 echo intval(0.58*100)은 58 대신 57을 인쇄합니다. 실제로 컴퓨터의 기본 바이너리가 부동 소수점 숫자를 정확하게 표현할 수 없는 버그입니다. Python을 사용할 때도 이 문제가 발생했습니다. 그래서 기본적으로 대부분의 언어는 정확한 계산을 위해 클래스 라이브러리나 함수 라이브러리를 제공합니다. 예를 들어 PHP에는 BC 고정밀 함수 라이브러리가 있습니다. 나중에 일반적으로 사용되는 BC 고정밀 함수를 소개하겠습니다.
위의 57,58개 질문으로 돌아가 보겠습니다.
출력이 57인 이유는 무엇입니까?
이 이유를 이해하려면 먼저 부동 소수점 숫자(IEEE 754)의 표현을 알아야 합니다.
길이가 64비트인 부동 소수점 숫자 (배정밀도)는 예를 들어 1개의 부호 비트(E), 11개의 지수 비트(Q), 52개의 가수 비트(M)를 사용하여 (총 64비트)
부호 비트: 최상위 비트를 나타냅니다. 는 데이터의 부호를 나타내고, 0은 양수를 나타내고, 1은 음수를 나타냅니다.
지수 비트: 2진법으로 데이터의 거듭제곱을 나타내며, 지수는 오프셋 코드로 표현됩니다.
가수: 데이터의 소수점 이하 유효 숫자를 나타냅니다.
여기서 핵심은 표현입니다. 이진수로 표현하면 Baidu에서 검색할 수 있습니다. 여기서는 자세히 설명하지 않겠습니다. 우리가 이해해야 할 핵심은 이진수 표현의 경우 0.58이 무한히 긴 값이라는 것입니다. 다음 숫자는 암시적 1)을 생략합니다..
0.58의 이진 표현은 기본적으로(52비트)입니다. 00101000111101011100001010001111010111000010100011110 0.57(52비트)의 이진 표현은 기본적으로 00100011110101입니다. 11000010100011110101 11000010100011110 그리고 둘의 이진수는 다음을 통해서만 계산됩니다. 이 52비트는 다음과 같습니다. www.111cn.net
0.58 -> 0.57999999999999960.57 -> 0.569999999999999 0.58 * 100의 구체적인 부동 소수점 곱셈에 대해서는 자세히 살펴보지 않겠습니다. 그것에(부동소수점) 암산을 해서 막연히 살펴보자면... 0.58 * 100 = 57.999999999
그럼 인테이크하면 자연스럽게 57이 되겠죠...
핵심은 바로 이겁니다. 이 문제의 내용은 다음과 같습니다. "유한해 보이는 소수는 컴퓨터의 이진수 표현에서 무한합니다."
따라서 더 이상 이것이 PHP 버그라고 생각하지 않았지만 이것이 바로 이것이었습니다...
있습니다. PHP 부동 소수점 처리에서 +-*%/의 부정확함
계속해서 코드를 살펴보세요:
$a = 0.1; $b = 0.7; var_dump(($a + $b) == 0.8); // false
인쇄된 값은 부울 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('105', '6.55957')); //php7.1 16
所以平时程序要要封装方法:
/** * 精确加法 * @param [type] $a [description] * @param [type] $b [description] */ function math_add($a,$b,$scale = '2') { return bcadd($a,$b,$scale); } /** * 精确减法 * @param [type] $a [description] * @param [type] $b [description] */ function math_sub($a,$b,$scale = '2') { return bcsub($a,$b,$scale); } /** * 精确乘法 * @param [type] $a [description] * @param [type] $b [description] */ function math_mul($a,$b,$scale = '2') { return bcmul($a,$b,$scale); } /** * 精确除法 * @param [type] $a [description] * @param [type] $b [description] */ function math_p($a,$b,$scale = '2') { 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 = '5') { return bccomp($a,$b,$scale); // 比较到小数点位数 }
相关推荐:
위 내용은 PHP는 고정밀 계산을 달성합니다의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!