首页 > PHP浮点型的理解

PHP浮点型的理解

2011-09-03   hisenKing

计算机程序处理浮点数的时候遵循的标准是IEEE Std 754-1985。这个标准定义了两种基本的格式:以4个字节表示的单精度格式和以8个字节表示的双精度格式。 单精度格式4个字节有三部分组成:1位的符号位(0代表正式,1代表负数),8位用做指数,最后23位用做有效数。 其中二进制的科学计算法中,规范化式的有效数应该大于或等于1且不小于10(十进制的2)。如:101.1101=>1.0111012^2 在IEEE浮点数标准中,小数点左边的这一位(有且仅有一个1)没有分配存储空间。因此称其精度为24位。 单精度浮点格式下可以表示的最小二进制数为1.000000000000000000000002^-126,最大为1.11111111111111111111111*2^127,小数点右边23位。 单精度浮点数有效位为23,精度为1-23^24,1-16777216,所以16777216和16777217两个数之间的所有的数都将被表示成同一个数。

双精度浮点数使用8个字节来不表示:1位的符号位(0代表正式,1代表负数),11位用做指数,最后52位用做有效数。因此在精度上比4字节的单精度浮点有所提升。

以上大致可以如下图表示: 单双精度浮点

  
<?php
$a   =   15521.42;
$b   =   15480.3;
$c = $a-$b;
var_dump($c);    //php4:float(41.120000000001)   php5:float(41.12)
var_dump($c == 41.12);     //bool(false)

var_dump(floor((0.1+0.7)*10)); //7

var_dump(array_sum(array(81.31,6.23,6.23,6.23))>100) //true
//可以用printf进行格式化出去查看具体的数字,如:printf("%.20f",array_sum(array(81.31,6.23,6.23,6.23)));

$a = 0.1;
for($i=1;$i<10;$i++)
{
      $a += 0.1;
      printf('%.20f',$a);echo "<br>";
}

php手册的说明:

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

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

所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。

比较解决方法:

  
<?php
function floatcmp($f1,$f2,$precision = 10) // are 2 floats equal
 {
     $e = pow(10,$precision);
     $i1 = intval($f1 * $e);
     $i2 = intval($f2 * $e);
     return ($i1 == $i2);
 }

function floatgtr($big,$small,$precision = 10) // is one float bigger than another
 {
     $e = pow(10,$precision);
     $ibig = intval($big * $e);
     $ismall = intval($small * $e);
     return ($ibig > $ismall);
 }

function floatgtre($big,$small,$precision = 10) // is on float bigger or equal to another
 {
     $e = pow(10,$precision);
     $ibig = intval($big * $e);
     $ismall = intval($small * $e);
     return ($ibig >= $ismall);
 }

更加完善的php浮点运算相关的代码可以参考zend framework的Zend\Locale\Math.php以及Zend\Locale\Math\PhpMath.php

以下记录是《代码大全》中使用浮点数时应该遵循的知道原则:

  • 避免数量级相差巨大的数之间的加减运算;
  • 避免等量判断;
  • 处理舍入误差问题 1)换用一种精度更高的变量类型,如单精度浮点值换成双精度浮点值; 2)换用二进制编码的十进制变量; 3)把浮点变量变成整数型变量,如美元和美分的处理方式,将美分乘以100,使其保存到变量值中的0到99的范围内;
  • 检查语言和函数库对特定数据类型的支持;

参考资料:

http://zh.wikipedia.org/wiki/IEEE_754

《编码——隐匿在计算机软硬件背后的语言》

《代码大全》

好久没更新了,需要除除草