php精度计算问题


如果用php+-*/计算浮点数的时候,可能会遇到一些计算结果错误的问题,比如echo intval( 0.58*100 );会打印57,而不是58,这个其实是计算机底层二进制无法精确表示浮点数的一个bug,是跨语言的,我用python也遇到这个问题。所以基本上大部 分语言都提供了精准计算的类库或函数库,比如phpBC高精确度函数库,下面达内php培训老师介绍一下一些常用的BC高精确度函数使用。

  例子

 代码如下

 

<?php    $f = 0.58;    var_dump(intval($f * 100)); //为啥输出57?>

  为啥输出是57? PHPbug?

  我相信有很多的同学有过这样的疑问, 因为光问我类似问题的人就很多, 更不用说bugs.php.net上经常有人问

  要搞明白这个原因, 首先我们要知道浮点数的表示(IEEE 754):

  浮点数, 64位的长度(双精度)为例, 会采用1位符号位(E), 11指数位(Q), 52位尾数(M)表示(一共64).

  符号位:最高位表示数据的正负,0表示正数,1表示负数。

  指数位:表示数据以2为底的幂,指数采用偏移码表示

  尾数:表示数据小数点后的有效数字.

  这里的关键点就在于, 小数在二进制的表示, 关于小数如何用二进制表示, 大家可以百度一下, 我这里就不再赘述, 我们关键的要了解, 0.58 对于二进制表示来说, 是无限长的值(下面的数字省掉了隐含的1)..

0.58的二进制表示基本上(52): 00101000111101011100001010001111010111000010100011110.57的二进制表示基本上(52): 001000111101011100001010001111010111000010100011110而两者的二进制, 如果只是通过这52位计算的话,分别是:www.111cn.net

0.58 -> 0.579999999999999960.57 -> 0.5699999999999999至于0.58 * 100的具体浮点数乘法, 我们不考虑那么细, 有兴趣的可以看(Floating point), 我们就模糊的以心算来看… 0.58 * 100 = 57.999999999

  那你intval一下, 自然就是57….

  可见, 这个问题的关键点就是: “你看似有穷的小数, 在计算机的二进制表示里却是无穷的

so, 不要再以为这是PHPbug, 这就是这样的…..

PHP浮点型在进行+-*%/存在不准确的问题

  例如:

1.

$a = 0.1;

$b = 0.7;

var_dump(($a + $b) == 0.8);

  打印出来的值为 boolean false

  这是为啥?PHP手册对于浮点数有以下警告信息:

Warning

  浮点数精度

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

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

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

 代码如下

 

<?php

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

bcadd — 将两个高精度数字相加

bccomp — 比较两个高精度数字,返回-1, 0, 1

bcdiv — 将两个高精度数字相除

bcmod — 求高精度数字余数

bcmul — 将两个高精度数字相乘

bcpow — 求高精度数字乘方

bcpowmod — 求高精度数字乘方求模,数论里非常常用

bcscale — 配置默认小数点位数,相当于就是Linux bc中的”scale=”

bcsqrt — 求高精度数字平方根

bcsub — 将两个高精度数字相减

  整理了一些实例

php 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.04
 
  /**
  * 两个高精度数相减
  * 
  * @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(bcdiv($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(bcdiv('105', '6.55957')); 
// 16.007

 

感谢原著作者,本人只是收集资料,谢谢

在CTF竞赛中,PHP浮点数精度问题常因计算机无法精确表示浮点数而产生。PHP内部存储浮点数机制使得小数小于某个值(如10^ - 16)后,比较时难以区分大小。 ### 常见问题 - **比较判断失效**:在CTF竞赛的代码审计题目中,若存在对浮点数的比较判断,如 `if ($req["number"] != intval($req["number"]))`,由于浮点数存储不精确,会先将右值转换成整数,再与左值比较。左值是浮点数,右值又会被隐式强制转换成浮点数,而PHP比较浮点数时不能精准比较,会“忽略”比10的 - 16次方更小的部分,导致判断结果与预期不符 [^1]。 - **绕过验证机制**:攻击者可利用浮点数精度问题绕过一些基于数值比较的验证机制。例如,某些题目要求输入一个非整数的数值进行验证,但由于浮点数精度问题,看似非整数的浮点数(实际存储不精确)在比较时可能被判定为整数,从而绕过验证。 ### 解决方案 - **使用误差范围比较**:可定义一个误差范围,判断两个浮点数的差值是否在该范围内,若在则认为两数相等。示例代码如下: ```php $a = 1.000000000000001; $b = 1.0; $epsilon = 1e-16; if (abs($a - $b) < $epsilon) { // 认为 $a 和 $b 相等 echo "相等"; } else { echo "不相等"; } ``` - **转换为字符串比较**:将浮点数转换为字符串后进行比较,避免浮点数计算和比较时的精度问题。示例代码如下: ```php $a = 1.000000000000001; $b = 1.0; if ((string)$a == (string)$b) { echo "相等"; } else { echo "不相等"; } ``` - **使用高精度数学库**:PHP 提供了 `BCMath` 和 `GMP` 等高精度数学库,可进行任意精度的数学运算。示例代码如下: ```php $a = "1.000000000000001"; $b = "1.0"; if (bccomp($a, $b, 16) == 0) { echo "相等"; } else { echo "不相等"; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值