Comparing Floating Point Nos - Google Test Framework
Asked Answered
D

3

5

While going through this post at SO by the user @skrebbel who stated that the google testing framework does a good and fast job for comparing floats and doubles. So I wrote the following code to check the validity of the code and apparently it seems like I am missing something here , since I was expecting to enter the almost equal to section here this is my code

float left = 0.1234567;
float right= 0.1234566;

const FloatingPoint<float> lhs(left), rhs(right);


if (lhs.AlmostEquals(rhs)) 
{
    std::cout << "EQUAL"; //Shouldnt it have entered here ?
}

Any suggetsions would be appreciated.

Dudek answered 6/9, 2013 at 21:14 Comment(0)
D
10

You can use

ASSERT_NEAR(val1, val2, abs_error); 

where you can give the acceptable - your chosen one, like, say 0.0000001 - difference as abs_error, if the default one is too small, see here https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#floating-point-comparison

Danell answered 6/9, 2013 at 21:19 Comment(2)
URL changed to github.com/google/googletest/blob/master/googletest/docs/…Burnet
URL changed again to github.com/google/googletest/blob/master/docs/…Kendre
C
9

Your left and right are not “almost equal” because they are too far apart, farther than the default tolerance of AlmostEquals. The code in one of the answers in the question you linked to shows a tolerance of 4 ULP, but your numbers are 14 ULP apart (using IEEE 754 32-bit binary and correctly rounding software). (An ULP is the minimum increment of the floating-point value. It is small for floating-point numbers of small magnitude and large for large numbers, so it is approximately relative to the magnitude of the numbers.)

You should never perform any floating-point comparison without understanding what errors may be in the values you are comparing and what comparison you are performing.

People often misstate that you cannot test floating-point values for equality. This is false; executing a == b is a perfect operation. It returns true if and only if a is equal to b (that is, a and b are numbers with exactly the same value). The actual problem is that they are trying to calculate a correct function given incorrect input. == is a function: It takes two inputs and returns a value. Obviously, if you give any function incorrect inputs, it may return an incorrect result. So the problem here is not floating-point comparison; it is incorrect inputs. You cannot generally calculate a sum, a product, a square root, a logarithm, or any other function correctly given incorrect input. Therefore, when using floating-point, you must design an algorithm to work with approximate values (or, in special cases, use great care to ensure no errors are introduced).

Often people try to work around errors in their floating-point values by accepting as equal numbers that are slightly different. This decreases false negatives (indications of inequality due to prior computing errors) at the expense of increasing false positives (indications of equality caused by lax acceptance). Whether this exchange of one kind of error for another is acceptable depends on the application. There is no general solution, which is why functions like AlmostEquals are generally bad.

The errors in floating-point values are the results of preceding operations and values. These errors can range from zero to infinity, depending on circumstances. Because of this, one should never simply accept the default tolerance of a function such as AlmostEquals. Instead, one should calculate the tolerance, which is specific to their applications, needs, and computations, and use that calculated tolerance (or not use a comparison at all).

Another problem is that functions such as AlmostEquals are often written using tolerances that are specified relative to the values being compared. However, the errors in the values may have been affected by intermediate values of vastly different magnitude, so the final error might be a function of data that is not present in the values being compared.

“Approximate” floating-point comparisons may be acceptable in code that is testing other code because most bugs are likely cause large errors, so a lax acceptance of equality will allow good code to continue but will report bugs in most bad code. However, even in this situation, you must set the expected result and the permitted error tolerance appropriately. The AlmostEquals code appears to hard-code the error tolerance.

Catinacation answered 6/9, 2013 at 21:37 Comment(7)
Agreed, except about functions like AlmostEquals being bad. They are bad only if you define them for generic FloatingPoint; instead they should be defined for program-specific numeric types. If you have classes such as Dose, Coord, Amplitude, etc., each being essentially a wrapper for float, each with its own applicable dynamic range and tolerance, then define approximate comparison for each of these classes with appropriate type-specific tolerance. This would prevent inconsistent comparisons of, say, Dose variables in different parts of your code.Sero
@Michael: Tolerance is not determined by the type. For example, see section 5, J-M. Muller’s Recurrence, in Kahan’s How Futile are Mindless Assessments of Roundoff in Floating-Point Computation?. Essentially regardless of type, computations of this recurrence converge to 100. The mathematically correct limit is 5. The error is 95 regardless of precision. To calculate tolerance, you must have complete context, including knowledge of prior calculations and data. (William Kahan is the reason we have IEEE 754. The paper is worth reading.)Catinacation
I guess that depends on what are you trying to do. For me expected tolerance for each numeric type is quite clear, but perhaps that is so because the kind of calculation done with each type if fairly uniform within the type. :) For Coord that would be computational geometry algorithms; for Dose that would be convolutions with kernels with truncated support, etc. We know the ballpark precision of what we do with each type, and therefore can define tolerance per type. The alternative - ill-defined assumptions due to inconsistent comparisons - is much worse: they may cause horrible bugs.Sero
I should be more clear about the assumptions causing bugs. Suppose you have something like if(Compare(x,y)<0){...} in each of the functions a(), b(), and c(). Moreover, functions a() and b() would call c() only if the above comparison is true, and c() is known to misbehave if it's called when the above is not true. This calls for uniform comparison, otherwise the code may break.Sero
@Michael: Are you allowed to add, subtract, multiply, or divide Coords with each other or Doses with each other? Your setup makes it sound like that must be a type error.Bethesde
@tmyklebu: No, Doses and Coords (and a few other numeric types) live in different universes, and it's good for design to keep them that way, even if both are floats. You shine the amount of light (Amplitude) through an aperture (Coord) and mask (Coord), get illumination of photoresist (Amplitude), derive chemically significant exposure (Dose), apply chemical reaction and such to figure out how much of the resist would quench (Dose) at every point (Coord) of the film. You never add Coord to Dose. Notice one can easily change Coord from float to int or Amplitude from float to complex if needed.Sero
@Michael: When you shine your light through your stuff, though, you wind up doing operations on floating-point numbers. You can very probably have greater error at the end of your chain of calculations than at the beginning; having a per-type epsilon makes little sense.Bethesde
N
2

(Not sure if this 100% applies to the original question but this is what I came for when I stumbled upon it)

There also exist ASSERT_FLOAT_EQ and EXPECT_FLOAT_EQ (or the corresponding versions for double) which you can use if you don't want to worry about tolerable errors yourself.

Docs: https://github.com/google/googletest/blob/master/docs/reference/assertions.md#floating-point-comparison-floating-point

Neuburger answered 29/6, 2021 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.