【问题标题】:Find a real life example of floating point error查找浮点错误的真实示例
【发布时间】:2020-11-21 23:35:58
【问题描述】:

处理金钱和其他精度至关重要的十进制数字的通常建议是使用整数或字符串(加上任意精度库),如果您了解浮点数学的工作原理,这很有意义。但是,我手头没有任何具体的例子来说明这一点,因为我在野外发现的每一个错误计算都是由于其他一些错误:使用== 的天真比较,显示结果时缺乏适当的舍入,公然错误的逻辑(例如,使用不一致的算法计算税款,在纸面上也不起作用)...我做了一些研究,结果要么仅适用于 C/C++(具有不同精度的浮点数/双精度数),要么只是对为什么你不能相信两个浮点数相等。

您能否分享一个包含精心挑选的浮点数的自包含 PHP 代码 sn-p 以及一个正确的算法,该算法会显式地呈现由浮点限制引起的不正确结果?

免责声明:我不打算争论、反驳或揭穿任何事情,我真的需要一个我的工具带的例子。

【问题讨论】:

  • 只需编写一个循环,以0.01 为步长累加秒/钱/任何东西(并使用任何二进制浮点格式)。随着时间的推移,这将累积一个重大错误(“我听说”这样的事情确实发生在野外——但我不认为在这里找到参考是主题......)。
  • @chtz 谢谢你的提示。我会用它来写一个答案,但我不会接受它,因为我想出的例子并不像我预期的那样实用。

标签: php floating-point ieee-754


【解决方案1】:

这个问题没有什么意义,因为浮点错误不是这样工作的。

误差很小。它们以远程小数形式出现,并且仅在您需要非常高的精度级别时才会引人注目。毕竟,IEEE 754 为绝大多数计算机系统提供动力,并提供出色的精度。把它放在上下文中,0.1 公里表示为浮点数是 0.100000001490116119384765625,精确到 1/10 微米 如果我没有算错的话。

可能没有一组精心挑选的数字和现实生活中的计算(发票、证券交易所指数......),无论您多么小心,都会呈现不正确的结果具有精度水平。因为那不是问题。

浮点数学的问题在于,它迫使你在每一步都非常小心,而且很容易让错误溜进来。

对于准确性很重要的应用程序,您可以使用浮点数编写正确的软件,但它不会那么容易、可维护或健壮。


原答案:

这是我目前为止最好的(感谢chtz 的提示):

// Set-up and display settings (shouldn't affect internal calculations or final result)
set_time_limit(0);
ini_set('precision', -1);

// Expected accuracy: 2 decimal positions
$total = 0;
for ($i = 0; $i < 1e9; $i++) {
    $total += 0.01;
    // It's important to NOT round inside the loop, e.g.: $total = round($total + 0.01, 2);
}
var_dump($total, number_format($total, 2));
float(9999999.825158669)
string(12) "9,999,999.83" // Correct value would be "10,000,000.00"

不幸的是,它依赖于大量精度错误的积累(它需要大约 1,000,000,000 个错误发生,并且在我的 PC 上运行需要超过 4 分钟),所以它不是真实的正如我所希望的那样,但它确实说明了根本问题。

【讨论】:

  • 在 10 亿个条款后仅下降 17 ulps。可能会有很多取消,但我没有看到一个简单的方法来避免它。如果你能以某种方式避免这种情况,你可能仍然需要收银机上的一百万件物品才能减少一分钱。 (只有 CVS 有这么多论文。)
  • " 所以它不是真实的" --> 嗯。请参阅Rounding errors on its Index price 一些现实生活中的 FP 问题需要时间。
  • @chux-ReinstateMonica 记住问题的上下文。一个典型的 PHP 进程生命周期是几秒钟,最多几个小时,当然永远不会。这不是关于“浮点错误是一件事吗?”而是要找到一段显示问题的具体代码。
  • 循环示例将是显示效果的最简单方法。一个稍微更真实的例子可能涉及将浮点数存储在数据库中并根据某些请求更新它们(并且每分钟/每秒有多个请求)。然后可能需要几周/几个月的时间才能发现错误(但这些细微的错误通常比明显的错误更糟糕,因为它们可以在一切“几乎正常”时被隐藏)。
【解决方案2】:

只需不到 10 亿次迭代,事情就很容易崩溃。问题是,通过使用浮点数和算术,您很容易发现自己得到了意想不到的结果,即使数字表面上看起来很好,但细微的不精确可能会导致应用程序出错。

让我们在您的答案中尝试该示例的变体:

$total = 0.0;
for ($i = 0; $i < 10; $i++) {
    $total += 0.1;
}

echo "added ten cents, ten times\n";

// since we added 0.1 € x 10 times, we now have 1€ in total, right?
if ($total == 1) {
    echo "I have 1€. All is good in the realm.";
}
else {
    echo "WTF? Where is my money? I only have {$total}€!!!!\n";
    echo "\$total holds: ";
    var_dump($total);
}

上面的输出是:

added ten cents
WTF? Where is my money? I only have 1€!!!!
$total holds: float(1)

即使$total 看起来是float(1),代码也会遵循“错误”的执行分支,从而破坏我们的应用程序。

如果我们在 PHP8(到目前为止是 beta 版)中执行相同的代码,你会得到一个更容易理解的结果:

added ten cents
WTF? Where is my money? I only have 1€!!!!
$total holds: float(0.9999999999999999)

另一个简单的例子:

$balance          = 50.03;
$debit            = 45.42;
$expected_balance = 4.61;
$real_balance     = $balance - $debit;

if ($real_balance !== $expected_balance) {
    echo "problems: ";
    var_dump($real_balance);
}

上面的输出是:

balance mismatch: float(4.61)

或者,在 PHP8 中:

balance mismatch: float(4.609999999999999)

上述任何一个例子都表明,实际上,使用浮点数来进行(特别是)货币算术可能是有问题的。由于结果不再符合您的期望,不仅会导致明显错误的结果,而且细微的不同结果也会使整个应用程序以意想不到的方式运行。

示例和结果,here

【讨论】:

  • 这些信息确实很有价值,任何处理浮点数的人都需要充分了解——但不是我要问的(因此我提到“使用 == 进行简单比较,显示结果时缺乏适当的舍入”)。无论如何,我承认我问了这个问题,因为我认为浮点错误有其他形式,也许他们没有,它们只是让你描述的问题更容易发生。
  • 第二个示例使用严格比较 (!==)。第一个示例使用严格比较 (===) 的行为完全相同。我只是使用更宽松的比较来表明即使这样也会发生错误。这正是使用浮点运算会弹出的舍入误差。而且它确实很实用,因为在现实生活中很可能会发生这样的事情。如果这不是你要找的,那我就束手无策了。
  • 通过舍入输出,您在逻辑上掩盖了不精确性,但在达到该点之前可能会发生错误,如上所示。
  • 我指的不是 PHP 松散类型。比较浮点数的正确方法是使用 epsilon,如$isEqual = abs($f1 – $f2) &lt;= $epsilon
  • 您答案中的代码执行的是“真正的任务”,而不是“想象的问题”?不清楚“现实生活中的示例”是什么意思,除非它是从现有工具或库中复制的代码,否则无论如何都会使问题偏离主题。
【解决方案3】:
echo floor((0.1 + 0.7) * 10);

预期结果:0.1 + 0.7 = 0.8; 0.8 * 10 = 8;

结果:7

在 PHP 7.2.12 上测试

【讨论】:

  • 根据文档php.net/manual/en/function.floor.php 返回下一个最小整数值。因此,由于 0.7 和 0.1 在点后不是只有一位数,因此结果低于 8,即下限为 7。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-10
  • 2012-03-27
相关资源
最近更新 更多