the Compartmented Robust Posix C++ Unit Test system |
|
---|
Rounding errors makes equality comparisons for floating point values interesting. Usually a comparison with some chosen precision is preferable over a direct equality comparison.
crpcut offers three different ways of making near-equality comparisons for floating point values, each with its own pros and cons.
Verify that the difference between two floating point numbers is not greater than a defined maximum. This works well when comparing numbers near zero, but is not very useful otherwise
Verify that the quotient between two floating point numbers does not differ by more than a defined maximum. This works very well for large numbers, but is very unsuitable for numbers near zero.
Verify that two floating point numbers do not differ by more than a maximum defined number of possible floating point number representations. This works very well over the entire range of floating point value, but is unfortunately only supported on intel x86 compatibles, and only for 32-bit float and 64-bit double.
All three near-equality comparators are used in the same way,
using crpcut::match
<matcher>(...)
with either
ASSERT_PRED(pred, ...)
or
VERIFY_PRED(pred, ...)
. An example using
crpcut::ulps_diff:
#include <crpcut.hpp> #include <numeric> #include <limits> template <typename T> class moving_avg { public: moving_avg() : avg(T()), n(T()) {} moving_avg& operator+=(T t) { ++n; avg-= (avg - t)/n; return *this; } operator T() const { return avg; } private: T avg; T n; }; static const unsigned count = 300; TEST(too_narrow) { moving_avg<float> mavg; float sum = 0.0; for (unsigned n = 0; n < count; ++n) { mavg+= 1.0f/3 + float(n); sum+= 1.0f/3 + float(n); } float avg = sum/count; ASSERT_PRED(crpcut::match<crpcut::ulps_diff>(2U), float(mavg), avg); } TEST(close_enough) { moving_avg<float> mavg; float sum = 0.0; for (unsigned n = 0; n < count; ++n) { mavg+= 1.0f/3 + float(n); sum+= 1.0f/3 + float(n); } float avg = sum/count; ASSERT_PRED(crpcut::match<crpcut::ulps_diff>(10U), float(mavg), avg); } int main(int argc, char *argv[]) { return crpcut::run(argc, argv); }
Running this test program yields:
FAILED!: too_narrow phase="running" -------------------------------------------------------------- samples/ulps_diff.cpp:56 ASSERT_PRED(crpcut::match<crpcut::ulps_diff>(2U), float(mavg), avg) param1 = 149.833 param2 = 149.833 for crpcut::match<crpcut::ulps_diff>(2U): Max allowed diff = 2 ULPS Actual diff = 5 ULPS ------------------------------------------------------------------------------- =============================================================================== 2 test cases selected Sum Critical Non-critical PASSED : 1 1 0 FAILED : 1 1 0
If you are not confident with the details of floating point calculations, David Goldberg's What Every Computer Scientist Should Know About Floating-Point Arithmetic is a must read. More information about comparing equality between floating point numbers, including a detailed description of the ULPS notion is available from Cygnus in the paper on comparing floating point numbers.