diffnest / myblog

0 stars 0 forks source link

诡异的浮点数之精度问题 #21

Open diffnest opened 6 years ago

diffnest commented 6 years ago

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


<?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);


输出结果如下:

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.使用高精度运算方法;

<?php var_dump(round(($totalRate - $rate), 1) == round($serviceFee, 1));

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

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


输出结果如下:

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


php的官方原文地址:http://php.net/manual/zh/language.types.float.php
<a href="https://diffnest.github.io/myblog/#/posts/6">PHP 高精度函数</a>