17

正确测试两个浮点数是否相等是很多人,包括我在内,并不完全理解的事情。然而,今天,我想到了一些标准容器如何根据operator<. 我总是看到人们在平等方面存在问题,但从来没有在其他关系比较方面遇到过问题。它们甚至还有无声版本可供使用,其中包括除平等和不平等之外的所有内容。

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

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

事实上,我确实运行了一个额外的双打重载测试,如此处所示它似乎与比较它们有相同的陷阱operator==

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

输出:

浮动->双与双:0 0

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

4

5 回答 5

16

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

您似乎有这样一个前提,即应该(double)0.7f 与 0.7进行比较。不是这种情况。如果你投到a ,你会得到。然而,等于。这些在数值上并不相等;事实上,远远小于--- 如果他们比较相等,那将是荒谬的。< 0.7fdouble0x1.666666p-10.70x1.6666666666666p-1(double)0.7f0.7

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

于 2013-07-15T21:16:55.403 回答
2

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

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

  • x > y并且x >= y两者都暗示x应该表示的数字量可能大于y,最坏的情况可能不会小于y

  • x < y并且x <= y两者都暗示x应该表示的数字量可能小于y,并且在最坏的情况下可能不会大于y

  • x == y意味着xy代表的数字量彼此无法区分

注意 if xis 的类型float,并且yis 的类型,如果参数被强制转换为double,就会实现上述含义。但是,在没有特定转换的情况下,C 和 C++(以及许多其他语言)将在执行比较之前将操作数转换为。这种转换将大大降低操作数被报告为“无法区分”的可能性,但将大大增加比较产生与预期数字实际表示的结果相反的结果的可能性。例如,考虑doublefloatfloatdouble

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包含 1E40 的最佳float表示。Doubled2包含 1E400 的最佳double表示。由d2 is hundreds of orders of magnitude greater than that represented byf2 , but(double)f2 > d2 . By contrast, converting both operands to float would yieldf2 == (float)d2` 表示的数值量,正确报告值是无法区分的。

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

于 2013-07-16T16:03:28.817 回答
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 位时,两个值可能看起来相同,但实际上完全不同并且比较不相等。

于 2013-07-15T20:52:01.873 回答
1

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

这有两个大陷阱。

  1. 许多简单的短小数扩展,例如 0.1,不能精确地用 float 或 double 表示。
  2. 在实数运算中相等的两个结果在浮点运算中可能不同。例如,浮点运算不是关联的 -(a + b) + c不一定与a + (b + c)

您需要为比较选择一个大于预期舍入误差的容差,但要足够小,以便在您的程序中可以接受将容差内的数字视为相等。

如果没有这样的公差,则意味着您使用了错误的浮点类型,或者根本不应该使用浮点。32 位 IEEE 754 的精度非常有限,因此很难找到合适的容差。通常,64 位是更好的选择。

于 2013-07-16T03:01:43.820 回答
-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
于 2013-07-15T21:01:51.450 回答