减去时PHP浮点计算错误

时间:2013-06-20 10:02:08

标签: 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 如果我添加这两个值,结果是正确的。

我的错误在哪里? 这不是一个四舍五入的问题。

4 个答案:

答案 0 :(得分:53)

如果仍然有人到达此页面时出现类似问题,其中浮点数减法会导致错误或奇怪的值。 我想用更多细节来解释这个问题。

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

这个问题在20年前甚至夺走了许多人​​的生命。

1991年2月25日,MIM-104爱国者导弹电池的浮动数计算问题使得它无法拦截沙特阿拉伯达兰的一枚入射飞毛腿导弹,导致美国陆军第14军需支队的28名士兵死亡。

但为什么会这样呢?

原因是浮点值表示有限的精度。所以,价值可能 任何处理后都没有相同的字符串表示。它也是 包括在脚本中直接写入浮点值 在没有任何数学运算的情况下打印它。

只是一个简单的例子:

$a = '36';
$b = '-35.99';
echo ($a + $b);

您希望它能打印0.01,对吗? 但它会打印一个非常奇怪的答案,如0.009999999999998

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

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

由于空间不足,十进制数字没有很好地用二进制表示。所以,你不能完全表达1/3 0.3333333...,对吧?为什么我们不能将0.01表示为二进制浮点数是出于同样的原因。 1/1000.00000010100011110101110000.....,重复10100011110101110000

如果0.01以二进制的01000111101011100001010的简化和系统截断形式保存,当它被翻译回十进制时,它将被读取为0.0099999....,具体取决于系统(64位)计算机将提供比32位更好的精度。在这种情况下,操作系统决定是否按照它看到的方式打印它,或者如何以更人性化的方式进行打印。因此,它依赖于机器的方式来表示它。但它可以用不同的方法在语言层面进行保护。

如果使用

格式化结果
echo number_format(0.009999999999998, 2);

它会打印0.01

这是因为在这种情况下,您将指示如何阅读以及您需要的精确度。

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

答案 1 :(得分:3)

除了使用number_format()之外,还有其他三种方法可以获得正确的结果。一个涉及做一些数学,如下:

<?php

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

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

echo (($a + $b)/100),"\n";

请参阅demo

或者,你可以简单地使用printf():

<?php

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

printf("\n%.2f",($a+$b));

请参阅demo

注意,如果没有精度说明符,printf()结果将包含尾随零小数,如下所示:0.010000

您还可以使用BC Math函数bcadd(),如下所示:

<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);

请参阅demo

答案 2 :(得分:1)

这对我有用:

void add_card_to(deck_t * deck, card_t * c)
{
    deck->n_cards++;
    deck->cards = realloc(...);
    deck->cards[deck->n_cards - 1] = c;
}

由于浮点减法运算会出现问题,因此我决定通过将其转换为整数运算来消除这种情况,然后再次将结果备份到浮点数中。

我更喜欢该解决方案,因为从根本上说,它确实防止了计算错误,而不是将结果与其他函数联系起来。

答案 3 :(得分:0)

我写了一个简单的函数来处理这个问题。 它的工作方式类似于php的bcmath扩展中的bcadd函数。 您以$ a和$ b字符串形式传递2个十进制数字,并指定应使用多少个十进制数,这些十进制数必须与$ 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;
}

样品用量:

echo decimalAdd("36.00","-35.99");

0.01