在这个例子中,使用“==”操作符是否安全来处理FP(浮点数)?

8

我偶然发现了这段代码(链接)

Generators doubleSquares(int value)
{
    Generators result;
    for (int i = 0; i <= std::sqrt(value / 2); i++) // 1
    {
        double j = std::sqrt(value - std::pow(i, 2)); // 2
        if (std::floor(j) == j) // 3
            result.push_back( { i, static_cast<int>(j) } ); // 4
    }
    return result;
}

我想知道//3是否危险?

2
我认为这是安全的 -- std::floor 返回一个整数值(尽管在 floatdouble 中),所有整数都可以被准确地表示,不会出现会影响小数值的轻微误差。 - Joe
1
@Joe:在某些时候(非常接近0的值),浮点数表示可能不会准确地表示整数。这在实践中不太可能出现,即使如此,我也不确定(有人吗?有人吗?)是否仍然正确,因为平方根将表现出相同的差异? - Ben Zotto
@Joe 如果“j”本身具有分数值呢?例如,如果sqrt(16)返回4.000000000000001,并且比较结果为false呢? - ApproachingDarknessFish
任何绝对值小于2^53的整数都可以在双精度格式中被准确地表示。 - Ben Zotto
3个回答

10

这段代码不能保证按照C++标准所期望的方式工作。

一些低质量的数学库在输入值为整数且数学结果可以被精确表示时,对于pow函数无法返回正确舍入的值。虽然sqrt函数更容易实现,因此缺陷较少,但其返回的值也可能不准确。

因此,不能保证当您期望j是整数时,它确切地是一个整数。

在优质的数学库中,当数学结果可以被精确表示时,powsqrt将始终返回正确的结果(零误差)。如果您有一个优质的C++实现,则该代码应按预期工作,直到使用的整数和浮点类型的限制。


改进代码

这段代码没有理由使用powstd::pow(i, 2)应该替换为i*i。这会产生精确的算术(直到整数溢出的程度),并完全避免了是否正确使用pow的问题。

消除pow后,只剩下sqrt。如果我们知道该实现返回正确的值,我们可以接受使用sqrt。否则,我们可以改用以下代码:

for (int i = 0; i*i <= value/2; ++i)
{
    int j = std::round(std::sqrt(value - i*i));
    if (j*j + i*i == value)
        result.push_back( { i, j } );
}

这段代码仅依赖于sqrt函数,能够返回一个精确到0.5的结果,即使是低质量的sqrt实现也应该对合理的输入值提供这样的准确性。


1
@Mehrdad:你为什么这样认为呢? - Eric Postpischil
4
如果一个平方根实现符合IEEE 754标准,那么它会返回正确舍入的结果。但这不是问题所在。C++的std::sqrt函数并非绑定到IEEE 754。C++的实现可能选择符合IEEE 754标准,也可能选择不符合。那么,你为什么认为C++的std::sqrt函数返回一个正确舍入的值? - Eric Postpischil
我从未说过sqrt返回正确舍入的值。我说它返回IEEE浮点数的正确舍入值。你问我为什么,我给你提供了原因链接。现在你错误地将我的陈述概括化了。 - user541686
3
C++实现可以使用IEEE-754浮点值和格式,但不使用IEEE-754操作和要求。因此,仅仅说“对于IEEE浮点数”,是不足以说明std::sqrt受限于IEEE 754的。事实上,许多C++实现确实使用IEEE-754的值和格式,但不符合IEEE-754的正确性要求。 - Eric Postpischil
1
我的评论的目的是传达一个观点(即sqrtpow在所需精度方面根本不同),而不是在词语选择上吹毛求疵。看起来你理解了这个观点,所以我就结束了。 - user541686
显示剩余2条评论

1

有两个不同但相关的问题:

  1. j 是整数吗?
  2. j 可能是双精度计算的结果,其精确结果为整数吗?

引用的代码询问第一个问题。它并不正确地询问第二个问题。需要更多上下文才能确定应该询问哪个问题。

如果应该询问第二个问题,则不能仅依赖于 floor。考虑一个大于 2.99999999999 但小于 3 的双精度数。它可能是计算的结果,其精确值为 3。它的 floor 值为 2,并且它比 floor 值大接近1。您需要将其与 std:round 的结果进行比较以接近精确结果。


0

我认为这是危险的。我们应该始终通过将两个浮点数之间的差与可接受的小数字进行比较来测试浮点数的“相等性”,例如:

#include <math.h>
...
if (fabs(std::floor(j) - j) < eps) {
...

...其中eps是一个对于某个目的而言可接受的小数。除非能够保证操作返回精确结果,否则这种方法是必不可少的。对于某些情况(例如IEEE-754兼容系统),这可能是正确的,但C++标准并不要求如此。请参见,例如C++中浮点算术的跨平台问题


在这种情况下,最好用其他东西替换std::floor - Markus Mayr
@MarkusMayr:好发现。不精确的结果可能会略小于整数,这将导致floor()给出下一个较低的整数。最好使用round()函数,可以从C++11中获取,也可以从round() for float in C++的答案选项中选择一个。 - Simon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接