C ++ Float comparison around zero with Gtest

we have been struggling for a while with one of the unit test. During the study, we found the main reason, which, apparently, is a comparison in floats (see the following code fragment, where I simplified the calculation, but it still does not work).

TEST_F( MyFloatTest, thisOneDoesFail) { const float toCompare = 0.2f - 1.0f + 0.9f; EXPECT_FLOAT_EQ( toCompare, 0.1f ); } 

Result:

Actual: 0.1 Expected: toCompare Which: 0.099999964

With some experience in numerical mathematics, we still cannot understand why this test fails, while a custom floating point comparison using std :: numeric_limits :: epsilon has passed. Therefore, at some point, we began to think that GTest was wrong, and we are debugging it. He uses strange expressions that we are not completely capturing. What's else weird: the following test passes, although I just add 1:

 TEST_F( MyFloatTest, thisOnePasses) { const float toCompare = 1.2f - 1.0f + 0.9f; EXPECT_FLOAT_EQ( toCompare, 1.1f ); } 

We thought this might be a problem when negative float values ​​are included, but the following test also passes:

 TEST_F( MyFloatTest, thisOnePassesAlso) { const float toCompare = 0.2f - 1.0f + 1.9f; EXPECT_FLOAT_EQ( toCompare, 1.1f ); } 

So, it seems to us that the macro EXPECT_FLOAT_EQ from Gtest just has a problem around zero. Does anyone know about this behavior? Have you ever seen this in your midst? (By the way, we are using MSVC2015). Is it just by chance, due to the accuracy of the 4 ULPs mentioned in GTest? (which is also not entirely clear to us).

+6
source share
3 answers

The problem is that a floating point sum with a small value and large intermediate values ​​will tend to have a large relative error. You reduce the error by writing

 const float toCompare = 0.2f - (1.0f - 0.9f); 

In the source code, the largest intermediate value was 0.2 - 1.0 = -0.8, which is eight times the final result. With the modified code, the largest intermediate value is 0.1, which is equal to the final result. And if you check your sample tests that have passed, in each case you do not have intermediate results that are large compared to the final result.

The problem is not the EXPECT_FLOAT_EQ macro, but the calculation.

+4
source

Does this just happen by accident because of the accuracy of the 4 ULPs mentioned in GTest?

It seems to me.

Try the following (very rude, not portable!) Test code:

 float toCompare = 0.2f - 1.0f + 0.9f; int i = *reinterpret_cast<int*>(&toCompare); std::cout << i << '\n'; float expected = 0.1f; i = *reinterpret_cast<int*>(&expected); std::cout << i << '\n'; 

On my system, the output is:

 1036831944 1036831949 

Mantissas are exactly 5 ULPs apart. Comparison 4 ULP is not enough for calculation error.

0.2f - 1.0f excellent, there is no accuracy error on my system. Remaining -0.8f + 0.9f . Here an error occurs (on my system). I am not an expert enough to tell you why this calculation has 5 ULP accuracy errors.


In cases where a certain degree of error is expected, use EXPECT_NEAR .

+5
source

As I can see, the problem lies in your assumption that 0.2f - 1.0f + 0.9f is 0.1f. None of 0.2 0.9 0.1 can be represented exactly as a float (or as a doubling or any other binary floating-point representation).

0.2f and 0.9f will actually be approximations to 0.2 and 0.9, and there is no reason to believe that your sum will give the same approximation to 0.1 as the value of 0.1f. Although the relative error in 0.2f and 0.9f will be approximately the same, the relative error in the sum can be much larger due to cancellation.

If you try the same with numbers that can be exactly represented as float, for example 0.25f - 1.0f + 0.875f, you will find that this is 0.125f

0
source

All Articles