php精度计算的问题解析


Posted in PHP onJune 21, 2019

PHP

var_dump(intval(0.58 * 100));

正确结果是 57,而不是 58

浮点运算惹的祸

其实这些结果都并非语言的 bug,但和语言的实现原理有关, js 所有数字统一为 Number, 包括整形实际上全都是双精度(double)类型。

而PHP会区分 int 还是 float。不管什么语言,只要涉及浮点运算,都是存在类似的问题,使用时一定要注意。

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

还是回到上面的57,58问题。

为啥输出是57啊? PHP的bug么?

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

  • 浮点数, 以64位的长度(双精度)为例, 会采用1位符号位(E), 11指数位(Q), 52位尾数(M)表示(一共64位).
  • 符号位:最高位表示数据的正负,0表示正数,1表示负数。
  • 指数位:表示数据以2为底的幂,指数采用偏移码表示
  • 数:表示数据小数点后的有效数字.

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

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

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

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

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

因此, 不要再以为这是PHP的bug了, 这就是这样的…..

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

继续看一段代码:

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

打印出来的值为 boolean false

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

Warning

浮点数精度

显然简单的十进制分数如同 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

常用的高精度函数如下:

  • bcadd — 将两个高精度数字相加
  • bccomp — 比较两个高精度数字,返回-1, 0, 1
  • bcdiv — 将两个高精度数字相除
  • 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(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')); 
//php7.1 16

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
fleaphp下不确定的多条件查询的巧妙解决方法
Sep 11 PHP
php下关于中英数字混排的字符串分割问题
Apr 06 PHP
php多种形式发送邮件(mail qmail邮件系统 phpmailer类)
Jan 22 PHP
php实现建立多层级目录的方法
Jul 19 PHP
PHP编译安装中遇到的两个错误和解决方法
Aug 20 PHP
thinkphp3.2.2实现生成多张缩略图的方法
Dec 19 PHP
PHP获得数组交集与差集的方法
Jun 10 PHP
Yii2 输出xml格式数据的方法
May 03 PHP
PHP迭代与递归实现无限级分类
Aug 28 PHP
PHP使用PDO访问oracle数据库的步骤详解
Sep 29 PHP
php使用curl模拟浏览器表单上传文件或者图片的方法
Nov 10 PHP
PHP fopen中文文件名乱码问题解决方案
Oct 28 PHP
laravel异步监控定时调度器实例详解
Jun 21 #PHP
apache集成php7.3.5的详细步骤
Jun 20 #PHP
PHP基础之输出缓冲区基本概念、原理分析
Jun 19 #PHP
PHP进阶学习之Geo的地图定位算法详解
Jun 19 #PHP
PHP进阶学习之依赖注入与Ioc容器详解
Jun 19 #PHP
yii2 在控制器中验证请求参数的使用方法
Jun 19 #PHP
php自定义排序uasort函数示例【二维数组按指定键值排序】
Jun 19 #PHP
You might like
一个PHP模板,主要想体现一下思路
2006/12/25 PHP
PHP中读写文件实现代码
2011/10/20 PHP
通过Unicode转义序列来加密,按你说的可以算是混淆吧
2007/05/06 Javascript
JavaScript Cookie的读取和写入函数
2009/12/08 Javascript
JavaScript 弹出窗体点击按钮返回选择数据的实现
2010/04/01 Javascript
JQuery UI的拖拽功能实现方法小结
2012/03/14 Javascript
使用非html5实现js板连连看游戏示例代码
2013/09/22 Javascript
JS实现模仿微博发布效果实例代码
2013/12/16 Javascript
自制的文件上传JS控件可支持IE、chrome、firefox etc
2014/04/18 Javascript
标题过长使用javascript按字节截取字符串
2014/04/24 Javascript
js生成缩略图后上传并利用canvas重绘
2014/05/15 Javascript
js确认删除对话框适用于a标签及submit
2014/07/10 Javascript
jQuery的基本概念与高级编程
2015/05/14 Javascript
JS文字球状放大效果代码分享
2015/08/19 Javascript
微信小程序 网络API Websocket详解
2016/11/09 Javascript
借助node实战JSONP跨域实例
2017/03/30 Javascript
jQuery常用选择器详解
2017/07/17 jQuery
Vue.js 2.5新特性介绍(推荐)
2017/10/24 Javascript
nodejs基于mssql模块连接sqlserver数据库的简单封装操作示例
2018/01/05 NodeJs
微信小程序实现页面跳转传递参数(实体,对象)
2019/08/12 Javascript
[04:31]2016国际邀请赛中国区预选赛妖精采访
2016/06/27 DOTA
[01:04:05]Mineski vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
linux下安装easy_install的方法
2013/02/10 Python
状态机的概念和在Python下使用状态机的教程
2015/04/11 Python
python获取一组数据里最大值max函数用法实例
2015/05/26 Python
seek引发的python文件读写的问题及解决
2019/07/26 Python
Python3的unicode编码转换成中文的问题及解决方案
2019/12/10 Python
Python sql注入 过滤字符串的非法字符实例
2020/04/03 Python
requests在python中发送请求的实例讲解
2021/02/17 Python
世界上最大的罕见唱片、CD和音乐纪念品网上商店:991.com
2018/05/03 全球购物
教师专业自荐书范文
2014/02/10 职场文书
优秀求职信
2014/05/29 职场文书
简历自我评价模板
2015/03/11 职场文书
装饰技术负责人岗位职责
2015/04/13 职场文书
自我检讨书怎么写
2015/05/07 职场文书
Python基础之元组与文件知识总结
2021/05/19 Python