诡异的浮点数之精度问题

最近在计算还款计划利率差,涉及两个浮点数的比较,结果令人百思不得其解,最终得出一个真理:“永远不要相信浮点数已精确到最后一位,也永远不要用等号比较两个浮点数是否相等”,详情如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$totalRate = 10;
$rate = 8.4;
$dVal = $totalRate - $rate;//利率差
$serviceFee = 1.6;//服务费利率

var_dump($dVal === $serviceFee);
var_dump(floatval($dVal) == floatval($serviceFee));

printf("%.020f", $dVal);
echo PHP_EOL;
printf("%.020f", $serviceFee);

输出结果如下:

1
2
3
4
bool(false)
bool(false)
1.59999999999999964473
1.60000000000000008882

出现这个是因为计算机底层二进制无法精确表示浮点数;
如下所示:
小数转二进制方法:
整数部分采用除以2取余方法
小数部分采用乘以2取整方法

如:8.4转二进制
整数部分是8:
8/2=4 8%2=0
4/2=2 4%2=0
2/2=1 2%2=0
1比2小,因此不需要计算下去,整数8的二进制为 1000

小数部分0.4:
0.4 * 2 = 0.8 => 0
0.8 * 2 = 1.6 => 1
0.6 * 2 = 1.2 => 1
0.2 * 2 = 0.4 => 0
0.4 * 2 = 0.8 => 0
0.8 * 2 = 1.6 => 1
0.6 * 2 = 1.2 => 1
0.2 * 2 = 0.4 => 0
0.4 * 2 = 0.8 => 0
0.8 * 2 = 1.6 => 1
0.6 * 2 = 1.2 => 1
0.2 * 2 = 0.4 => 0

1000.0110011001100110…….
这样一直循环下去,当截取精度为N时,N后的数会被舍去,导致精度丢失,
所以在上例中,8.4在计算机转为二进制时精度就已经丢失了,导致相减出的差值不等于1.6;

正确比较浮点数的方法:
1.使用round方法处理后再比较;
2.使用高精度运算方法;

1
2
3
4
5
6
<?php
var_dump(round(($totalRate - $rate), 1) == round($serviceFee, 1));

var_dump(bcsub($totalRate, $rate, 1) == $serviceFee);

var_dump(bccomp($dVal, $serviceFee, 1));

输出结果如下:

1
2
3
bool(true)
bool(true)
int(0) // 0表示两个浮点数值相等

php的官方原文地址:http://php.net/manual/zh/language.types.float.php
PHP 高精度函数

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!