你应该知道PHP浮点数知识


Posted in PHP onMay 13, 2015

PHP是一种弱类型语言, 这样的特性, 必然要求有无缝透明的隐式类型转换, PHP内部使用zval来保存任意类型的数值, zval的结构如下(5.2为例):

struct _zval_struct {

    /* Variable information */

    zvalue_value value;     /* value */

    zend_uint refcount;

    zend_uchar type;    /* active type */

    zend_uchar is_ref;

};

上面的结构中, 实际保存数值本身的是zvalue_value联合体:
typedef union _zvalue_value {

    long lval;                  /* long value */

    double dval;                /* double value */

    struct {

        char *val;

        int len;

    } str;

    HashTable *ht;              /* hash table value */

    zend_object_value obj;

} zvalue_value;

今天的话题, 我们只关注其中的俩个成员, lval和dval, 我们要意识到, long lval是随着编译器, OS的字长不同而不定长的, 它有可能是32bits或者64bits, 而double dval(双精度)由IEEE 754规定, 是定长的, 一定是64bits.

请记住这一点, 造就了PHP的一些代码的”非平台无关性”. 我们接下来的讨论, 除了特别指明, 都是假设long为64bits

IEEE 754的浮点计数法, 我这里就不引用了, 大家有兴趣的可以自己查看, 关键的一点是, double的尾数采用52位bit来保存, 算上隐藏的1位有效位, 一共是53bits.

在这里, 引出一个很有意思的问题, 我们用c代码举例(假设long为64bits):

    long a = x;

    assert(a == (long)(double)a);

请问, a的取值在什么范围内的时候, 上面的代码可以断言成功?(留在文章最后解答)

现在我们回归正题, PHP在执行一个脚本之前, 首先需要读入脚本, 分析脚本, 这个过程中也包含着, 对脚本中的字面量进行zval化, 比如对于如下脚本:

<?php

$a = 9223372036854775807; //64位有符号数最大值

$b = 9223372036854775808; //最大值+1

var_dump($a);

var_dump($b);

输出:
int(9223372036854775807)

float(9.22337203685E+18)

也就说, PHP在词法分析阶段, 对于一个字面量的数值, 会去判断, 是否超出了当前系统的long的表值范围, 如果不是, 则用lval来保存, zval为IS_LONG, 否则就用dval表示, zval IS_FLOAT.

凡是大于最大的整数值的数值, 我们都要小心, 因为它可能会有精度损失:

<?php

$a = 9223372036854775807;

$b = 9223372036854775808;

 

var_dump($a === ($b - 1));

输出是false.

现在接上开头的讨论, 之前说过, PHP的整数, 可能是32位, 也可能是64位, 那么就决定了, 一些在64位上可以运行正常的代码, 可能会因为隐形的类型转换, 发生精度丢失, 从而造成代码不能正常的运行在32位系统上.

所以, 我们一定要警惕这个临界值, 好在PHP中已经定义了这个临界值:

<?php

    echo PHP_INT_MAX;

 ?>

 

当然, 为了保险起见, 我们应该使用字符串来保存大整数, 并且采用比如bcmath这样的数学函数库来进行计算.

另外, 还有一个关键的配置, 会让我们产生迷惑, 这个配置就是php.precision, 这配置决定了PHP再输出一个float值的时候, 输出多少有效位.

最后, 我们再来回头看上面提出的问题, 也就是一个long的整数, 最大的值是多少, 才能保证转到float以后再转回long不会发生精度丢失?

比如, 对于整数, 我们知道它的二进制表示是, 101, 现在, 让我们右移俩位, 变成1.01, 舍去高位的隐含有效位1, 我们得到在double中存储5的二进制数值为:

0/*符号位*/ 10000000001/*指数位*/ 0100000000000000000000000000000000000000000000000000

5的二进制表示, 丝毫未损的保存在了尾数部分, 这个情况下, 从double转会回long, 不会发生精度丢失.

我们知道double用52位表示尾数, 算上隐含的首位1, 一共是53位精度.. 那么也就可以得出, 如果一个long的整数, 值小于:

2^53 - 1 == 9007199254740991; //牢记, 我们现在假设是64bits的long

那么, 这个整数, 在发生long->double->long的数值转换时, 不会发生精度丢失.
PHP 相关文章推荐
PHP新手上路(三)
Oct 09 PHP
php中常用字符串处理代码片段整理
Nov 07 PHP
php htmlspecialchars()与shtmlspecialchars()函数的深入分析
Jun 05 PHP
PHP的MVC模式实现原理分析(一相简单的MVC框架范例)
Apr 29 PHP
php银联网页支付实现方法
Mar 04 PHP
PHP输出日历表代码实例
Mar 27 PHP
PHP中通过trigger_error触发PHP错误示例
Jun 23 PHP
PHP使用MPDF类生成PDF的方法
Dec 08 PHP
PHP+MySQL实现的简单投票系统实例
Feb 24 PHP
PHP简单实现二维数组的矩阵转置操作示例
Nov 24 PHP
php 使用ActiveMQ发送消息,与处理消息操作示例
Feb 23 PHP
php实现图片压缩处理
Sep 09 PHP
PHP浮点数精度问题汇总
May 13 #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
You might like
用PHP函数解决SQL injection
2006/10/09 PHP
PHP中的正规表达式(一)
2006/10/09 PHP
Yii实现微信公众号场景二维码的方法实例
2020/08/30 PHP
windows系统下简单nodejs安装及环境配置
2013/01/08 NodeJs
jQuery学习笔记之jQuery构建函数的7种方法
2014/06/03 Javascript
nodejs文件操作模块FS(File System)常用函数简明总结
2014/06/05 NodeJs
js判断登录与否并确定跳转页面的方法
2015/01/30 Javascript
javascript 用函数实现继承详解
2016/05/28 Javascript
详解NodeJs支付宝移动支付签名及验签
2017/01/06 NodeJs
jQuery基本选择器和层次选择器学习使用
2017/02/27 Javascript
Vue单文件组件的如何使用方式介绍
2017/07/28 Javascript
一个月入门Python爬虫学习,轻松爬取大规模数据
2018/01/03 Python
使用apidoc管理RESTful风格Flask项目接口文档方法
2018/02/07 Python
怎么使用pipenv管理你的python项目
2018/03/12 Python
详解Django解决ajax跨域访问问题
2018/08/24 Python
Python对象中__del__方法起作用的条件详解
2018/11/01 Python
Python常见数据结构之栈与队列用法示例
2019/01/14 Python
python opencv进行图像拼接
2020/03/27 Python
解决django FileFIELD的编码问题
2020/03/30 Python
python实现程序重启和系统重启方式
2020/04/16 Python
解决pytorch 交叉熵损失输出为负数的问题
2020/07/07 Python
Python子进程subpocess原理及用法解析
2020/07/16 Python
python 基于opencv 绘制图像轮廓
2020/12/11 Python
HTML4和HTML5之间除了相似以外的10个主要不同
2012/12/13 HTML / CSS
Hotels.com台湾:饭店订房网
2017/09/06 全球购物
意大利大型购物中心:Oliviero.it
2017/10/19 全球购物
公司培训欢迎词
2014/01/10 职场文书
《黄河颂》教学反思
2014/02/07 职场文书
党员2014两会学习心得体会
2014/03/17 职场文书
校园安全演讲稿
2014/05/09 职场文书
陈胜吴广起义口号
2014/06/20 职场文书
企业标语大全
2014/07/01 职场文书
初三学生语文考试作弊检讨书
2014/12/14 职场文书
迟到检讨书范文
2015/01/27 职场文书
2016年小学党支部创先争优活动总结
2016/04/05 职场文书
python - timeit 时间模块
2021/04/06 Python