最新消息:yaf表单扩展中新增加了浮点数、日期和集合的校验。php yaf框架扩展实践三——表单

php关于金额比较引发的问题

PHP 1965浏览 0评论

做电子商务的时候一般会涉及到金额的比较,按正常的思路来看用><=这些个符号就可以了。可是要是到程序上来搞这个的话就出大事了。现在看下这段代码:

$f = 0.07;
var_dump($f * 100 == 7);//输出false

输出结果会出乎大家意料,输出false,为什么会这样呢?其实这个和电脑中存储小数的原理有关。大家都知道计算机只能存储0和1,我们日常生活习惯使用的是10进制的数据,像0.07这个小数在计算机中存储时会有精度损失,以至于计算出来的结果会有偏差。

那么怎么解决这个问题?虽然计算机存储小数有偏差,但是偏差还是非常小,像上例中0.07 * 100如果显示出小数点后面20位的话,最终的值如下:

$f = 0.07;
//输出7.00000000000000088818
echo number_format($f * 100, 20);

可以看到已经在小数点10多位之后了。在实际中我们通常也不需要精确到后面这么多位数字,在金额方面通常精确到后面3位就好了。如果精确到小数点后面三位的话,0.07*100和7就会相等了。在php中提供了一个bccomp函数用来处理这方面的比较。

$f = 0.07;
var_dump($f * 100 == 7);
//输出0,表示两个数字精度为小数点后3位的时候相等
var_dump(bccomp($f * 100, 7, 3));

虽然最终解决了问题,但是还是想搞明白为什么0.07这样的浮点数会有精度损失,经过一段时间的研究,发现产生误差的原因:就在于浮点数的小数位在转换成二进制的时候产生的。

浮点数小数部分转换成二进制规则:乘2取整法,即每一步将十进制小数部分乘以2,所得积的小数点左边的数字(0或1)作为二进制表示法中的数字,直到满足精确度为止。

0.07这个小数的转换成二进制过程如下:

浮点数小数部分转二进制

浮点数小数部分转二进制

经过计算发现0.07即使计算到60位后,依然还没有结束。在计算机中,32位的计算机中浮点数尾数部分是23位,64位的是52位。所以后面多出来的部分就会被舍弃掉。

附:

测试都是在64位机器上进行的。

转换小数部分为二进制的代码:

$bin  = "";
$int  = 7;
$base = 100;
echo "<table border='1'>";
echo "<td width='50'>位数</td>";
echo "<td width='50'>x2</td>";
echo "<td width='50'>位值</td>";
for ($i = 0; $i <= 60; $i++) {
    echo "<tr>";
    echo "<td>$i</td>";
    $int = $int * 2;
    echo "<td>$int</td>";
    if ($int == 100) {
        $bin.="1";
        echo "<td>1</td>";
        break;
    }
    if ($int > 100) {
        $bin.="1";
        $int = $int - $base;
        echo "<td>1</td>";
    } else {
        $bin .= "0";
        echo "<td>0</td>";
    }
    echo "</td>";
    echo "</tr>";
}
echo "</table>";
echo $bin;

对上例转换的二进制进行反推:

/*
  输出内容
  0.070000000000000006661338147751
  0.070000000000000006661338147751
 */
$f   = 0.0;
$bin = "0001000111101011100001010001111010111000010100011110101110000";
$l   = strlen($bin);
for ($i = 0; $i < $l; $i++) {
    if ($bin[$i] > 0) {
        $f = $f + pow(2, -($i + 1));
    }
}
echo number_format($f, 30);


$f = 0.07;
echo "<br />";
echo number_format($f, 30);

参考:

https://en.wikipedia.org/wiki/IEEE_floating_point

转载请注明:快乐编程 » php关于金额比较引发的问题

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址