减去时的 PHP 浮点数计算错误

2022-08-30 14:21:36

我有一个非常奇怪的问题。如果我减去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 如果我将两个值相加,则结果是正确的。

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


答案 1

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

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

这个问题甚至在二十年前夺走了许多生命。

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

但为什么会发生这种情况呢?

原因是浮点值表示的精度有限。因此,值在进行任何处理(切掉)后可能不具有相同的字符串表示形式。它还包括在脚本中编写浮点值,并在没有任何数学运算的情况下直接打印它。

举个简单的例子:

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

你会期望它打印出来,对吧?但它会打印一个非常奇怪的答案,比如0.010.009999999999998

与其他数字一样,浮点数双精度或浮点数作为 0 和 1 的字符串存储在内存中。浮点数与整数的区别在于当我们想要查看0和1时,我们如何解释它们。它们的存储方式有很多标准。

浮点数通常作为符号位、指数场以及有效数或尾数(从左到右)打包到计算机数据中。

由于缺乏足够的空间,十进制数在二进制中不能很好地表示。所以,你不能完全按照它本来的样子表达,对吧?为什么我们不能表示为二进制浮点数也是出于同样的原因。 带有重复。1/30.3333333...0.011/1000.00000010100011110101110000.....10100011110101110000

如果以二进制的简化和系统截断形式保存,当它被转换回十进制时,它将根据系统被读取(64位计算机将为您提供比32位更好的精度)。在这种情况下,操作系统决定是按它所看到的方式打印它,还是如何以更人性化的方式制作它。因此,这取决于他们想要如何表示它取决于机器。但它可以用不同的方法在语言层面上得到保护。0.01010001111010111000010100.0099999....

如果使用

echo number_format(0.009999999999998, 2);

它将打印.0.01

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

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

参考资料:
https://sdqweb.ipd.kit.edu/publications/pdfs/saglam2016a.pdf
https://en.wikipedia.org/wiki/Round-off_error


答案 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
?>

由于问题发生在浮点减法运算中,我决定通过将其转换为整数运算来消除它,然后再次将结果备份为浮点运算。

我更喜欢这个解决方案,因为基本上它确实可以防止计算错误,而不是用其他函数来增加结果。


推荐