查找浮点错误的真实示例

Find a real life example of floating point error

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

您能否分享一个自包含的 PHP 代码片段,其中包含精心挑选的浮点数字和正确的算法,该算法会呈现由浮点限制明确导致的错误结果?

免责声明:我无意争论、反驳或揭穿任何东西,老实说,我需要一个工具带示例。

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

误差很小。它们以遥远的小数形式出现,并且只有在您需要非常高的精度水平时才会引起注意。毕竟,IEEE 754 为绝大多数计算机系统提供支持,并且它提供了出色的精度。换句话说,用浮点数表示的 0.1 公里是 0.100000001490116119384765625,精确到 1/10 µm 如果我没算错的话。

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

浮点数学的问题是它迫使你在每一步都非常小心,而且很容易出现错误。

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


原回答:

这是迄今为止我得到的最好的(感谢 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 个错误的发生,并且需要超过 4 分钟才能 运行 在我的电脑上),所以它是就像我希望的那样真实,但它确实说明了潜在的问题。

只需不到 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),代码也遵循 'wrong' 执行分支,破坏了我们的应用程序。

如果我们在 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

echo floor((0.1 + 0.7) * 10);

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

结果:7

测试于 PHP 7.2.12