Open diffnest opened 6 years ago
最近在计算还款计划利率差,涉及两个浮点数的比较,结果令人百思不得其解,最终得出一个真理:“永远不要相信浮点数已精确到最后一位,也永远不要用等号比较两个浮点数是否相等”,详情如下: <?php $totalRate = 10; $rate = 8.4; $dVal = $totalRate - $rate;//利率差 $serviceFee = 1.6;//服务费利率
最近在计算还款计划利率差,涉及两个浮点数的比较,结果令人百思不得其解,最终得出一个真理:“永远不要相信浮点数已精确到最后一位,也永远不要用等号比较两个浮点数是否相等”,详情如下:
<?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>
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
<?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表示两个浮点数值相等