你应该知道PHP浮点数知识_php技巧

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的数值转换时, 不会发生精度丢失.

时间: 2024-10-26 20:59:09

你应该知道PHP浮点数知识_php技巧的相关文章

PHP内核介绍及扩展开发指南—基础知识_php技巧

一. 基础知识 本章简要介绍一些Zend引擎的内部机制,这些知识和Extensions密切相关,同时也可以帮助我们写出更加高效的PHP代码. 1.1 PHP变量的存储 1.1.1 zval结构 Zend使用zval结构来存储PHP变量的值,该结构如下所示: 复制代码 代码如下: typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val;

简要剖析PHP的Yii框架的组件化机制的基本知识_php技巧

组件是 Yii 应用的主要基石.是 yii\base\Component 类或其子类的实例.三个用以区分它和其它类的主要功能有: 属性(Property) 事件(Event) 行为(Behavior) 或单独使用,或彼此配合,这些功能的应用让 Yii 的类变得更加灵活和易用.以小部件 yii\jui\DatePicker 来举例,这是个方便你在 视图中生成一个交互式日期选择器的 UI 组件: use yii\jui\DatePicker; echo DatePicker::widget([ 'l

一些关于PHP的知识_php技巧

1.如何配置PhpMyAdmin2.9 网络上很多教程的配置文件是针对PhpMyAdmin底版本的,一开始连2.9配置文件都不知道放哪里? 配置文件相对地址是:config.sample.inc.php  (不是这个libraries/config.default.inc.php) 2.让phpMyAdmin使用密码登陆 在设置config.inc.php设置以下参数: $cfg['Servers'][$i]['auth_type'] = 'cookie'; $cfg['blowfish_sec

新安装的MySQL数据库需要注意的安全知识_php技巧

在Unix(Linux)上,在按照手册的指令安装好MySQL后,你必须运行mysql_install_db脚本建立包含授权 表的mysql数据库和初始权限.在Windows上,运行分发中的Setup程序初始化数据目录和mysql数据库.假 定服务器也在运行. 当你第一次在机器上安装MySQL时,mysql数据库中的授权表是这样初始化的: 你可以从本地主机(localhost)上以root连接而不指定口令.root用户拥有所有权限(包括管理权限) 并可做任何事情.(顺便说明,MySQL超级用户与U

php基础知识:函数基础知识_php技巧

函数,所有的语言都有,所以这里只说重点: 1>定义:php不需要定义返回值类型. 2>函数名是非大小写敏感的,不过在调用函数的时候,通常使用其在定义时相同的形式.  3>PHP 中的所有函数和类都具有全局域,可以在内部定义外部调用,反之亦然. 4>PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数. 5>在 PHP 中可以调用递归函数.但是要避免递归函数/方法调用超过 100-200 层,因为可能会破坏堆栈从而使当前脚本终止.  6>有默认值的参数必须在参数

JavaScript每天必学之基础知识_javascript技巧

基本概念  javascript是一门解释型的语言,浏览器充当解释器.js执行时,在同一个作用域内是先解释再执行.解释的时候会编译function和var这两个关键词定义的变量,编译完成后从上往下执行并向变量赋值.  区分大小写  ECMASCript中的一切(包括变量,函数名和操作符)都区分大小写.  1. 变量  变量在第一次用到时就设置于内存中,便于后来在脚本中引用.使用变量之前先进行声明.可以使用 var 关键字来进行变量声明. var count, amount, level; //

用PHP实现多服务器共享SESSION数据的方法_php技巧

PHP 实现多服务器共享 SESSION 数据 /google 的广告条--> 一.问题起源 稍大一些的网站,通常都会有好几个服务器,每个服务器运行着不同功能的模块,使用不同的二级域名,而一个整体性强的网站,用户系统是统一的,即一套用户名.密码在整个网站的各个模块中都是可以登录使用的.各个服务器共享用户数据是比较容易实现的,只需要在后端放个数据库服务器,各个服务器通过统一接口对用户数据进行访问即可.但还存在一个问题,就是用户在这个服务器登录之后,进入另一个服务器的别的模块时,仍然需要重新登录,这

几篇关于无限分类算法的文章第1/5页_php技巧

http://dev.mysql.com/tech-resources/articles/hierarchical-data.html By Mike Hillyer Introduction Most users at one time or another have dealt with hierarchical data in a SQL database and no doubt learned that the management of hierarchical data is no

Excel单元格自定义格式的知识和技巧

  Excel单元格自定义格式的知识和技巧          下面本文就由浅入深的来介绍一下 Excel单元格自定义格式的知识和技巧. 1."G/通用格式" 以常规的数字显示,相当于"分类"列表中的"常规"选项. 代码:G/通用格式.10 显示为 10;10.1 显示为 10.1 2. "#":数字占位符 只显有意义的零而不显示无意义的零.小数点后数字如大于"#"的数量,则按"#"的位数