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