如果运算符<适用于浮点类型,为什么我们不能用它来进行相等测试?

时间:2013-07-15 20:35:38

标签: c++ floating-point equality

正确地测试两个浮点数是否相等是包括我在内的很多人都不完全理解的。然而,今天,我想到了一些标准容器如何用operator<来定义相等性。我总是看到人们在平等方面存在问题,但从未与其他关系比较相提并论。甚至可以使用silent versions个,其中包括除了平等和不平等之外的所有内容。

假设operator<“正常”工作,与operator==不同,我们为什么不能这样做:

bool floateq(float a, float b) {
    //check NaN
    return !(a < b) && !(b < a);
}

事实上,我确实运行了一个带有双重额外重载的测试,如here所示,它似乎与将它们与operator==进行比较有相同的缺陷:

std::cout << "float->double vs double: " 
          << floateq(static_cast<double>(0.7f), 0.7) << " " 
          << (static_cast<double>(0.7f) == 0.7) << "\n";

输出:

  

float-&gt; double vs double:0 0

我是否担心使用所有比较运算符,或者还有其他方面比较我不能正确理解的浮点数?

5 个答案:

答案 0 :(得分:16)

==<><=>=!=运算符可以很好地处理浮点数。

您似乎有一个前提,即某些 < 的合理实现应该比较(double)0.7f等于0.7。不是这种情况。如果您将0.7f投射到double,则会获得0x1.666666p-1。但是,0.7等于0x1.6666666666666p-1。这些在数值上并不相同;事实上,(double)0.7f0.7小得多 - 他们比较平等是荒谬的。

使用浮点数时,重要的是要记住它们是浮点数,而不是实数或有理数或任何其他类似的东西。您必须考虑他们的属性,而不是每个人都希望他们拥有的属性。这样做可以自动避免使用浮点数的大多数常见“陷阱”。

答案 1 :(得分:2)

使用浮点数时,关系运算符具有含义,但它们的含义不一定与实际数字的行为一致。

如果浮点值用于表示实际数字(它们的正常目的),则运算符的行为倾向如下:

  • x > yx >= y都暗示x应该表示的数字量可能大于y,最差可能不会少于yx < y

  • x <= yx都暗示y应该表示的数字量可能小于y,最差的可能不是远远大于x == y

  • x表示yx所代表的数字量彼此无法区分

请注意,如果float的类型为y,而double的类型为double,则float参数为float时,将实现上述含义施放到double。但是,在没有特定强制转换的情况下,C和C ++(以及许多其他语言)会在执行比较之前将float f = 16777217; double d = 16777216.5; 操作数转换为float。这种转换将极大地降低报告操作数“无法区分”的可能性,但是将极大地增加比较将产生与预期数字实际指示的结果相反的结果的可能性。例如,考虑一下

double

如果两个操作数都转换为d,则比较将指示值无法区分。如果它们被转换为f,则比较将指示float f = 1E20f; float f2 = f*f; double d = 1E150; double d2 = d*d; 更大,即使值f2应该表示稍大。作为一个更极端的例子:

float

Float d2包含1E40的最佳double表示。 Double d2 is hundreds of orders of magnitude greater than that represented by包含1E400的最佳, but表示。由. By contrast, converting both operands to float would yield f2 float f2 = f1 / 10.0;(双)f2&gt;表示的数值量。 d2 {{1}} f2 ==(float)d2`,正确报告值无法区分

PS - 我很清楚IEEE标准要求执行计算,好像浮点值表示两个分数的精确幂,但很少有人将代码{{1}}视为“将f2设置为可表示的二次幂分数,最​​接近f1“中的十分之一。代码的目的是使f2成为f1的十分之一。由于不精确,代码无法完美地实现该目的,但在大多数情况下,将浮点数表示为实际数值比将它们视为两个幂的分数更有帮助。

答案 2 :(得分:1)

以下代码(我更改后编译:特别是对floateq的调用已更改为floatcmp)打印出float->double vs double: 1 0,而不是0 0(如同期望将这两个值作为浮点数进行比较时。)

#include <iostream>

bool floatcmp(float a, float b) {
    //check NaN
    return !(a < b) && !(b < a);
}

int main()
{
    std::cout << "float->double vs double: "
              << floatcmp(static_cast<double>(0.7f), 0.7) << " "
              << (static_cast<double>(0.7f) == 0.7) << "\n";
}

然而,对于标准库而言,重要的是operator<定义了一个严格的弱排序,它实际上对浮点类型有效。

相等的问题是,当四舍五入到4或6个位置时,两个值可能看起来相同但实际上完全不同并且比较为不相等。

答案 3 :(得分:1)

Float和double都是科学记数法的二进制等值,具有固定数量的有效位。如果计算的无限精度结果不能精确表示,则实际结果是最接近可精确表示的结果。

这有两个很大的陷阱。

  1. 许多简单的短十进制扩展(例如0.1)在float或double中不能完全表示。
  2. 实数运算中相同的两个结果在浮点运算中可能不同。例如,浮点运算不是关联的 - (a + b) + c不一定与a + (b + c)相同
  3. 您需要选择一个大于预期舍入误差的比较容差,但要小到足以使程序可以将公差范围内的数字视为相等。

    如果没有这样的容差,则表示您使用了错误的浮点类型,或者根本不应该使用浮点数。 32位IEEE 754具有如此有限的精度,因此找到合适的容差非常具有挑战性。通常,64位是更好的选择。

答案 4 :(得分:-2)

通常,浮点数的所有比较操作都应在指定的精度限制内完成。否则,您可能会被累积的舍入误差所困扰,这种误差在低精度时看不到,但是比较运算符会将其考虑在内。 排序通常无关紧要。

另一个代码示例确实显示您的比较不起作用(http://ideone.com/mI4S76)。

#include <iostream>

bool floatcmp(float a, float b) {
    //check NaN
    return !(a < b) && !(b < a);
}

int main() {
    using namespace std;

    float a = 0.1;
    float b = 0.1;

    // Introducing rounding error:
    b += 1;
    // Just to be sure change is not inlined
    cout << "B after increase = " << b << endl;
    b -= 1;
    cout << "B after decrease = " << b << endl;

    cout << "A " << (floatcmp(a, b) ? "equals" : "is not equal to") << "B" << endl;
}

输出:

B after increase = 1.1
B after decrease = 0.1
A is not equal toB