PHP浮点数精度问题汇总


Posted in PHP onMay 13, 2015

一、PHP浮点数精度损失问题

先看下面这段代码:

$f = 0.57;

echo intval($f * 100);  //56

结果可能有点出乎你的意外,PHP遵循IEEE 754双精度:

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

再来看看小数用二进制怎么表示:

乘2取整,顺序排列,即将小数部分乘以2,然后取整数部分,剩下的小数部分继续乘以2,然后取整数部分,剩下的小数部分又乘以2,一直取到小数部分,但是像0.57这样的小数像这样一直乘下去,小数部分不可能为0.有效位的小数用二进制表示却是无穷的。

0.57的二进制表示基本上(52位)是: 0010001111010111000010100011110101110000101000111101

如果只有52位的话,0.57 =》 0.56999999999999995

不难看出上面意外的结果了吧。

二、PHP浮点数的精度问题

先看问题:

$f = 0.58;

var_dump(intval($f * 100)); //为啥输出57

我相信有很多的同学有过这样的疑问。

具体原理可阅读“鸟哥”的一篇文章,那里有详细的解说:PHP浮点数的一个常见问题的解答

那么如何避免这种问题呢?
办法有很多,这里列举两个:
1. sprintf

substr(sprintf("%.10f", ($a/ $b)), 0, -7);

2. round (注意会进行四舍五入)
round($a/$b, 3);

或者你有更好的办法,也可以了留言告诉我。

三、PHP浮点数的一个常见问题的解答

关于PHP的浮点数, 我之前写过一篇文章: 关于PHP浮点数你应该知道的(All ‘bogus' about the float in PHP)

不过, 我当时遗漏了一点, 也就是对于如下的这个常见问题的回答:

<?php

    $f = 0.58;

    var_dump(intval($f * 100)); //为啥输出57

?>

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

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

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

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

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

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

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

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

0.58的二进制表示基本上(52位)是: 0010100011110101110000101000111101011100001010001111
0.57的二进制表示基本上(52位)是: 0010001111010111000010100011110101110000101000111101
而两者的二进制, 如果只是通过这52位计算的话,分别是:

0.58 -> 0.57999999999999996

0.57 -> 0.56999999999999995

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

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

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

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

PHP 相关文章推荐
一个高ai的分页函数和一个url函数
Oct 09 PHP
php桌面中心(四) 数据显示
Mar 11 PHP
PHP中获取变量的变量名的一段代码的bug分析
Jul 07 PHP
浅析PKI加密解密 OpenSSL
Jul 01 PHP
php计算当前程序执行时间示例
Apr 24 PHP
php上传图片之时间戳命名(保存路径)
Aug 15 PHP
PHP计算指定日期所在周的开始和结束日期的方法
Mar 24 PHP
PHP文件下载实例代码浅析
Aug 17 PHP
PHP中让json_encode不自动转义斜杠“/”的方法
Feb 28 PHP
Yii2 队列 shmilyzxt/yii2-queue 简单概述
Aug 02 PHP
利用Homestead快速运行一个Laravel项目的方法详解
Nov 14 PHP
Yii框架日志操作图文与实例详解
Sep 09 PHP
PHP生成器简单实例
May 13 #PHP
php实现比较两个字符串日期大小的方法
May 12 #PHP
php使用substr()和strpos()联合查找字符串中某一特定字符的方法
May 12 #PHP
PHP异常处理浅析
May 12 #PHP
php猴子选大王问题解决方法
May 12 #PHP
PHP嵌套输出缓冲代码实例
May 12 #PHP
php实现修改新闻时删除图片的方法
May 12 #PHP
You might like
php面向对象全攻略 (十一)__toString()用法 克隆对象 __call处理调用错误
2009/09/30 PHP
PHP二维数组去重算法
2016/12/17 PHP
PHP基于phpqrcode类生成二维码的方法详解
2018/03/14 PHP
ie与ff下的event事件使用介绍
2013/11/25 Javascript
jquery做的一个简单的屏幕锁定提示框
2014/03/26 Javascript
Jquery性能优化详解
2014/05/15 Javascript
javascript学习总结之js使用技巧
2015/09/02 Javascript
JS中改变this指向的方法(call和apply、bind)
2016/03/26 Javascript
Nodejs抓取html页面内容(推荐)
2016/08/11 NodeJs
js实现添加删除表格(两种方法)
2017/04/27 Javascript
浅谈es6 javascript的map数据结构
2017/12/14 Javascript
react router4+redux实现路由权限控制的方法
2018/05/03 Javascript
vue-video-player 通过自定义按钮组件实现全屏切换效果【推荐】
2018/08/29 Javascript
小程序从手动埋点到自动埋点的实现方法
2019/01/24 Javascript
vue全局自定义指令-元素拖拽的实现代码
2019/04/14 Javascript
node静态服务器实现静态读取文件或文件夹
2019/12/03 Javascript
从零开始在vue-cli4配置自适应vw布局的实现
2020/06/08 Javascript
mapboxgl区划标签避让不遮盖实现的代码详解
2020/07/01 Javascript
Vue时间轴 vue-light-timeline的用法说明
2020/10/29 Javascript
[03:13]DOTA2-DPC中国联赛1月25日Recap集锦
2021/03/11 DOTA
使用python实现生成用户信息
2017/03/20 Python
Django rest framework工具包简单用法示例
2018/07/20 Python
Python类中方法getitem和getattr详解
2019/08/30 Python
html5借用repeating-linear-gradient实现一把刻度尺(ruler)
2019/09/09 HTML / CSS
官方授权图形T恤和服装:Fifth Sun
2019/06/12 全球购物
什么是网络协议
2016/04/07 面试题
土木工程毕业生推荐信
2013/10/28 职场文书
汽车维修专业个人求职信范文
2014/01/01 职场文书
《雨霖铃》听课反思
2014/02/13 职场文书
关工委先进个人事迹材料
2014/05/23 职场文书
2014年后勤管理工作总结
2014/12/01 职场文书
2014年保卫工作总结
2014/12/05 职场文书
社区服务活动报告
2015/02/05 职场文书
安全生产先进个人总结
2015/02/15 职场文书
忠诚与背叛观后感
2015/06/04 职场文书
紧急迫降观后感
2015/06/15 职场文书