If operator< works properly for floating-point types, why can't we use it for equality testing?

前端 未结 5 1338
借酒劲吻你
借酒劲吻你 2021-01-01 16:55

Properly testing two floating-point numbers for equality is something that a lot of people, including me, don\'t fully understand. Today, however, I thought about how some s

相关标签:
5条回答
  • 2021-01-01 17:06

    Float and double are both in the binary equivalent of scientific notation, with a fixed number of significant bits. If the infinite precision result of a calculation is not exactly representable, the actual result is the closest one that is exactly representable.

    There are two big pitfalls with this.

    1. Many simple, short decimal expansions, such as 0.1, are not exactly representable in float or double.
    2. Two results that would be equal in real number arithmetic can be different in floating point arithmetic. For example, floating point arithmetic is not associative - (a + b) + c is not necessarily the same as a + (b + c)

    You need to pick a tolerance for comparisons that is larger than the expected rounding error, but small enough that it is acceptable in your program to treat numbers that are within the tolerance as being equal.

    If there is no such tolerance, it means you are using the wrong floating point type, or should not be using floating point at all. 32-bit IEEE 754 has such limited precision that it can be really challenging to find an appropriate tolerance. Usually, 64-bit is a much better choice.

    0 讨论(0)
  • 2021-01-01 17:08

    The following code (which I changed so it compiles: Specifically the call to floateq was changed to floatcmp) prints out float->double vs double: 1 0 , not 0 0 (as one would expect when comparing those two values as floats).

    #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";
    }
    

    However what matters for the standard library is that operator< defines a strict weak ordering, which it in fact does for floating point types.

    The problem with equality is that two values may look the same when rounded to say 4 or 6 places but are in fact totally different and compare as not equal.

    0 讨论(0)
  • 2021-01-01 17:12

    When using floating-point numbers, the relational operators have meanings, but their meanings don't necessarily align with how actual numbers behave.

    If floating-point values are used to represent actual numbers (their normal purpose), the operators tend to behave as follows:

    • x > y and x >= y both imply that the numeric quantity which x is supposed to represent is likely greater than y, and at worst probably not much less than y.

    • x < y and x <= y both imply that the numeric quantity which x is supposed to represent is likely less than than y, and is at worst probably not much greater than y.

    • x == y implies that the numeric quantities which x and y represent are indistinguishable from each other

    Note that if x is of type float, and y is of type double, the above meanings will be achieved if the double argument is cast to float. In the absence of a specific cast, however, C and C++ (and also many other languages) will convert a float operand to double before performing a comparison. Such conversion will greatly reduce the likelihood that the operands will be reported "indistinguishable", but will greatly increase the likelihood that the comparison will yield a result contrary to what the intended numbers actually indicate. Consider, for example,

    float f = 16777217;
    double d = 16777216.5;
    

    If both operands are cast to float, the comparison will indicate that the values are indistinguishable. If they are cast to double, the comparison will indicate that d is larger even though the value f is supposed to represent is slightly bigger. As a more extreme example:

    float f = 1E20f;
    float f2 = f*f;
    double d = 1E150;
    double d2 = d*d;
    

    Float f2 contains the best float representation of 1E40. Double d2 contains the best double representation of 1E400. The numerical quantity represented by 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`, correctly reporting that the values are indistinguishable.

    PS--I am well aware that IEEE standards require that calculations be performed as though floating-point values represent precise power-of-two fractions, but few people seeing the code float f2 = f1 / 10.0; as being "Set f2 to the representable power-of-two fraction which is closest to being one tenth of the one in f1". The purpose of the code is to make f2 be a tenth of f1. Because of imprecision, the code cannot fulfill that purpose perfectly, but in most cases it's more helpful to regard floating-point numbers as representing actual numerical quantities than to regard them as power-of-two fractions.

    0 讨论(0)
  • 2021-01-01 17:24

    The ==, <, >, <=, >=, and != operators work just fine with floating-point numbers.

    You seem to have the premise that some reasonable implementation of < ought to compare (double)0.7f equal to 0.7. This is not the case. If you cast 0.7f to a double, you get 0x1.666666p-1. However, 0.7 is equal to 0x1.6666666666666p-1. These are not numerically equal; in fact, (double)0.7f is considerably smaller than 0.7 --- it would be ridiculous for them to compare equal.

    When working with floating-point numbers, it is important to remember that they are floating-point numbers, rather than real numbers or rational numbers or any other such thing. You have to take into account their properties and not the properties everyone wants them to have. Do this and you automatically avoid most of the commonly-cited "pitfalls" of working with floating-point numbers.

    0 讨论(0)
  • 2021-01-01 17:28

    Generally all comparison operations on floating point numbers should be done within a specified precision limit. Otherwise, you may be bitten by accumulated rounding error which is not seen at low precision, but will be taken into account by comparison operators. It just often doesn't matter much for sorting.

    Another code sample which does show that your comparison doesn't work (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;
    }
    

    Output:

    B after increase = 1.1
    B after decrease = 0.1
    A is not equal toB
    
    0 讨论(0)
提交回复
热议问题