减去时PHP浮点计算错误

Rub*_*atc 19 php math floating-point formatting

我有一个非常奇怪的问题.如果我减去2个浮点数,其中一个是数学运算的结果,我得到一个错误的值.

例:

var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];
Run Code Online (Sandbox Code Playgroud)

这是它的输出:

float 5.4
float 1.4
5.3290705182008E-15
Run Code Online (Sandbox Code Playgroud)

5.4-1.4应为4 如果我添加两个值,结果是正确的.

我的错误在哪里?它不能成为一个四舍五入的问题.

Sel*_*lay 64

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

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

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

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

但为什么会发生呢?

原因是浮点值表示有限的精度.因此,在任何处理之后,值可能不具有相同的字符串表示.它还包括在脚本中编写浮点值并直接打印它而无需任何数学运算.

只是一个简单的例子:

$a = '36';
$b = '-35.99';
echo ($a + $b);
Run Code Online (Sandbox Code Playgroud)

你会期望它打印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);
Run Code Online (Sandbox Code Playgroud)

它会打印出来0.01.

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

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

  • 数十年后,我将永远说**这是显而易见的错误**。我们不在乎它是否以“ 0”和“ 1”的组合保存,或者不管怎么说-发明者应该为此发明一种解决方案,而不必强迫我们学习定制编程来计算“ 36-35.99”(即将其保存为首先将字符串转换为数字-**我们不在乎**。我们需要计算机来正确计算简单的事物,而无需了解`float`的内部。 (6认同)
  • 如何解决这个问题? (3认同)
  • 我已经花了一个小时才得到答案`945252744562139136 - 1`,在谷歌上搜索了几次代码,它可以在`php`中正确地得到那个简单的答案,但还没有找到。 (2认同)

小智 7

这对我有用:

<?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
?>
Run Code Online (Sandbox Code Playgroud)

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

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


sle*_*vy1 5

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

<?php

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

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

echo (($a + $b)/100),"\n";
Run Code Online (Sandbox Code Playgroud)

查看演示

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

<?php

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

printf("\n%.2f",($a+$b));
Run Code Online (Sandbox Code Playgroud)

查看演示

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

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

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

查看演示

  • 使用您的 printf 解决方案 `$result = sprintf("%.2f", $result);` (2认同)