减法时PHP浮点计算错误

减法时PHP浮点计算错误,php,math,floating-point,formatting,Php,Math,Floating Point,Formatting,我有一个很奇怪的问题。如果我减去2个浮点变量,其中一个是数学运算的结果,我得到了一个错误的值 例如: var_dump($remaining); var_dump($this->hours_sub['personal']); echo $remaining-$this->hours_sub['personal']; 这将显示输出: float 5.4 float 1.4 5.3290705182008E-15 5.4-1.4应该是4 如果我加上这两个值,结果是正确的 我的错在哪里

我有一个很奇怪的问题。如果我减去2个浮点变量,其中一个是数学运算的结果,我得到了一个错误的值

例如:

var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];
这将显示输出:

float 5.4
float 1.4
5.3290705182008E-15
5.4-1.4应该是4 如果我加上这两个值,结果是正确的

我的错在哪里?
这不可能是一个舍入问题。

如果仍然有人用类似的问题点击此页面,其中浮点数减法会导致错误或奇怪的值。 下面我将更详细地解释这个问题

它与PHP没有直接关系,也不是一个bug。 然而,每个程序员都应该意识到这个问题

20年前,这个问题甚至夺去了许多人的生命

1991年2月25日,MIM-104爱国者导弹连中的一个不正确浮点运算(称为舍入误差)阻止它在沙特阿拉伯的达兰拦截来袭的飞毛腿导弹,造成美国陆军第14军需分队28名士兵死亡,近100名军人受伤

但为什么会这样

原因是浮点值表示有限的精度。因此,可能需要一个值 在任何处理后都不具有相同的字符串表示形式(截断)。它也 包括在脚本中写入浮点值并直接 无需任何数学运算即可打印

举个简单的例子:

$a = '36';
$b = '-35.99';
echo ($a + $b);
您希望它打印
0.01
,对吗? 但它会打印一个非常奇怪的答案,如
0.00999999998

与其他数字一样,浮点数字double或float作为0和1的字符串存储在内存中。浮点与整数的不同之处在于,当我们想要查看0和1时,我们如何解释它们。如何存储它们有许多标准

浮点数通常作为符号位、指数字段和有效位或尾数从左到右打包到计算机数据中

由于缺少足够的空间,十进制数不能很好地用二进制表示。所以,你不能把
1/3
精确地表示为
0.3333333…
,对吗?为什么我们不能将
0.01
表示为二进制浮点数也是出于同样的原因<代码>1/100是
0.000000 1010001110101110000…..
带有重复的
1010001110101110000

如果
0.01
以简化和系统截断的形式保存在二进制
01000111101011100001010
中,当它被翻译回十进制时,根据系统的不同,它的读取方式将类似于
0.0099999….
(64位计算机将比32位计算机提供更好的精度)。在这种情况下,操作系统决定是按它所看到的方式打印,还是以更便于人阅读的方式打印。因此,他们希望如何表示它取决于机器。但它可以通过不同的方法在语言级别得到保护

如果使用

echo number_format(0.009999999999998, 2);
它将打印
0.01

这是因为在这种情况下,您指示应该如何读取它以及您需要的精度

注意number_format()不是唯一的函数,可以使用其他一些函数和方法告诉编程语言精度要求

参考文献:


除了使用number_format(),还有三种其他方法可以获得正确的结果。一个是做一点数学,如下所示:

<?php

$a = '36';
$b = '-35.99';

$a *= 100;
$b *= 100;

echo (($a + $b)/100),"\n";
<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);
请参见

这对我很有用:

<?php
    $a = 96.35;
    $b = 96.01;

    $c = ( ( floor($a * 100) - floor($b * 100) ) / 100 );

    echo $c; // should see 0.34 exactly instead of 0.33999999999999
?>

由于浮点减法运算出现问题,我决定将其转换为整数运算,然后将结果再次备份为浮点运算,从而消除该问题


我更喜欢这种解决方案,因为它基本上防止了计算错误,而不是用其他函数来修饰结果。

我编写了一个简单的函数来处理这个问题。 它的工作原理与php的bcmath扩展中的bcadd函数类似。 您以字符串形式将两个十进制数传递给它,$a和$b,并指定应使用多少个小数,这些小数必须与$a和$b中的小数数匹配

正如您所看到的,它将使用整数进行计算,然后将其转换回字符串,而不在任何点使用浮点运算

function decimalAdd($a,$b,$numDecimals=2) {
    $intSum=(int)str_replace(".","",$a)+(int)str_replace(".","",$b);
    $paddedIntSum=str_pad(abs($intSum),$numDecimals,0,STR_PAD_LEFT);
    $result=($intSum<0?"-":"").($intSum<100&&$intSum>-100?"0":"").substr_replace($paddedIntSum,".",-$numDecimals,0);
    return $result;
}
0.01


是的,也许你应该试试为什么“这不可能是一个舍入问题。”?@PatriciaShanahan,因为舍入问题不会产生一个接近0的值,因为两个近似值的差值分别为5.4和1.4。“取整问题”将产生
3.999…9xyz
4.000…0xyz
。我很抱歉这个问题。这很愚蠢。对于每个有同样问题的人。必须使用round()或number_format()将floatvals强制转换为相同的格式。请参阅高度相关:如何修复此问题?几十年后,我将始终说这是一个明显的错误。我们不在乎它是用
0
1
组合保存的,还是无论什么时候——发明家本应为此发明解决方案,而不必强迫我们学习自定义编程来计算
36-35.99
(即,首先将其保存为字符串并转换回数字-我们不在乎。我们需要计算机来正确计算简单的事情,而不需要知道
float
的内部结构).我已经花了几个小时才得到答案
945252744562139136-1
,在谷歌上搜索了好几次,找到了一个可以在
php
中正确得到简单答案的代码,但还没有找到。@t.Todua要获得大数字/更高精度,请尝试使用例如:使用您的printf解决方案
$result=sprintf(%.2f),$result);
我认为没有人能轻松阅读您的代码。它在每行上都做得太多,运算符或表达式之间没有间距。我想它可以更容易阅读。但在我写了一年后看它,我仍然没有任何问题。我允许自己阅读的原因是什么
function decimalAdd($a,$b,$numDecimals=2) {
    $intSum=(int)str_replace(".","",$a)+(int)str_replace(".","",$b);
    $paddedIntSum=str_pad(abs($intSum),$numDecimals,0,STR_PAD_LEFT);
    $result=($intSum<0?"-":"").($intSum<100&&$intSum>-100?"0":"").substr_replace($paddedIntSum,".",-$numDecimals,0);
    return $result;
}
echo decimalAdd("36.00","-35.99");