减法时PHP浮点计算错误
我有一个很奇怪的问题。如果我减去2个浮点变量,其中一个是数学运算的结果,我得到了一个错误的值 例如:减法时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 如果我加上这两个值,结果是正确的 我的错在哪里
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");