Comparing floating point number to zero

后端 未结 9 930
时光说笑
时光说笑 2020-11-28 23:04

The C++ FAQ lite \"[29.17] Why doesn\'t my floating-point comparison work?\" recommends this equality test:

#include   /* for std::abs(double) *         


        
相关标签:
9条回答
  • 2020-11-28 23:08

    Like @Exceptyon pointed out, this function is 'relative' to the values you're comparing. The Epsilon * abs(x) measure will scale based on the value of x, so that you'll get a comparison result as accurately as epsilon, irrespective of the range of values in x or y.

    If you're comparing zero(y) to another really small value(x), say 1e-8, abs(x-y) = 1e-8 will still be much larger than epsilon *abs(x) = 1e-13. So unless you're dealing with extremely small number that can't be represented in a double type, this function should do the job and will match zero only against +0 and -0.

    The function seems perfectly valid for zero comparison. If you're planning to use it, I suggest you use it everywhere there're floats involved, and not have special cases for things like zero, just so that there's uniformity in the code.

    ps: This is a neat function. Thanks for pointing to it.

    0 讨论(0)
  • 2020-11-28 23:10

    2 + 2 = 5(*)

    (for some floating-precision values of 2)

    This problem frequently arises when we think of"floating point" as a way to increase precision. Then we run afoul of the "floating" part, which means there is no guarantee of which numbers can be represented.

    So while we might easily be able to represent "1.0, -1.0, 0.1, -0.1" as we get to larger numbers we start to see approximations - or we should, except we often hide them by truncating the numbers for display.

    As a result, we might think the computer is storing "0.003" but it may instead be storing "0.0033333333334".

    What happens if you perform "0.0003 - 0.0002"? We expect .0001, but the actual values being stored might be more like "0.00033" - "0.00029" which yields "0.000004", or the closest representable value, which might be 0, or it might be "0.000006".

    With current floating point math operations, it is not guaranteed that (a / b) * b == a.

    #include <stdio.h>
    
    // defeat inline optimizations of 'a / b * b' to 'a'
    extern double bodge(int base, int divisor) {
        return static_cast<double>(base) / static_cast<double>(divisor);
    }
    
    int main() {
        int errors = 0;
        for (int b = 1; b < 100; ++b) {
            for (int d = 1; d < 100; ++d) {
                // b / d * d ... should == b
                double res = bodge(b, d) * static_cast<double>(d);
                // but it doesn't always
                if (res != static_cast<double>(b))
                    ++errors;
            }
        }
        printf("errors: %d\n", errors);
    }
    

    ideone reports 599 instances where (b * d) / d != b using just the 10,000 combinations of 1 <= b <= 100 and 1 <= d <= 100 .

    The solution described in the FAQ is essentially to apply a granularity constraint - to test if (a == b +/- epsilon).

    An alternative approach is to avoid the problem entirely by using fixed point precision or by using your desired granularity as the base unit for your storage. E.g. if you want times stored with nanosecond precision, use nanoseconds as your unit of storage.

    C++11 introduced std::ratio as the basis for fixed-point conversions between different time units.

    0 讨论(0)
  • 2020-11-28 23:17

    notice, that code is:

    std::abs((x - y)/x) <= epsilon
    

    you are requiring that the "relative error" on the var is <= epsilon, not that the absolute difference is

    0 讨论(0)
  • 2020-11-28 23:20

    Simple comparison of FP numbers has it's own specific and it's key is the understanding of FP format (see https://en.wikipedia.org/wiki/IEEE_floating_point)

    When FP numbers calculated in a different ways, one through sin(), other though exp(), strict equality won't be working, even though mathematically numbers could be equal. The same way won't be working equality with the constant. Actually, in many situations FP numbers must not be compared using strict equality (==)

    In such cases should be used DBL_EPSIPON constant, which is minimal value do not change representation of 1.0 being added to the number more than 1.0. For floating point numbers that more than 2.0 DBL_EPSIPON does not exists at all. Meanwhile, DBL_EPSILON has exponent -16, which means that all numbers, let's say, with exponent -34, would be absolutely equal in compare to DBL_EPSILON.

    Also, see example, why 10.0 == 10.0000000000000001

    Comparing dwo floating point numbers depend on these number nature, we should calculate DBL_EPSILON for them that would be meaningful for the comparison. Simply, we should multiply DBL_EPSILON to one of these numbers. Which of them? Maximum of course

    bool close_enough(double a, double b){
        if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
        {
            return true;
        }
        return false;
    }
    

    All other ways would give you bugs with inequality which could be very hard to catch

    0 讨论(0)
  • 2020-11-28 23:24

    You are correct with your observation.

    If x == 0.0, then abs(x) * epsilon is zero and you're testing whether abs(y) <= 0.0.

    If y == 0.0 then you're testing abs(x) <= abs(x) * epsilon which means either epsilon >= 1 (it isn't) or x == 0.0.

    So either is_equal(val, 0.0) or is_equal(0.0, val) would be pointless, and you could just say val == 0.0. If you want to only accept exactly +0.0 and -0.0.

    The FAQ's recommendation in this case is of limited utility. There is no "one size fits all" floating-point comparison. You have to think about the semantics of your variables, the acceptable range of values, and the magnitude of error introduced by your computations. Even the FAQ mentions a caveat, saying this function is not usually a problem "when the magnitudes of x and y are significantly larger than epsilon, but your mileage may vary".

    0 讨论(0)
  • 2020-11-28 23:25

    You can use std::nextafter with a fixed factor of the epsilon of a value like the following:

    bool isNearlyEqual(double a, double b)
    {
      int factor = /* a fixed factor of epsilon */;
    
      double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
      double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;
    
      return min_a <= b && max_a >= b;
    }
    
    0 讨论(0)
提交回复
热议问题