Thread: [Cppunit-devel] CPPUNIT_ASSERT_DOUBLES_EQUAL() with non-finite numbers
Brought to you by:
blep
From: CppUnit d. m. l. <cpp...@li...> - 2006-11-11 04:22:19
|
Howdy, I recently received a Debian bug report for CppUnit = (http://bugs.debian.org/396865) that CPPUNIT_ASSERT_DOUBLES_EQUAL() does = not work as expected if one value is NaN. This bug has previously been reported to CppUnit at least twice: = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D1179131&grou= p_id=3D11795&atid=3D111795 = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D754638&group= _id=3D11795&atid=3D111795 In addition, there have been at least two patches for the problem: = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D1591244&grou= p_id=3D11795&atid=3D311795 = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D756340&group= _id=3D11795&atid=3D311795 Contrary to the claim of Baptiste in the latter patch, isnan() is = standard: it is part of C99. Hopefully we can assume it is portable by = now. My patch (below) uses isfinite() instead, which is also from C99. First, here are additional test cases to nail down the semantics of = CPPUNIT_ASSERT_DOUBLES: - NaN is not equal to anything, not even to another NaN - infinity is equal to infinity This patch is against CVS. Index: examples/cppunittest/TestAssertTest.cpp =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: = /cvsroot/cppunit/cppunit/examples/cppunittest/TestAssertTest.cpp,v retrieving revision 1.9 diff -u -b -B -r1.9 TestAssertTest.cpp --- examples/cppunittest/TestAssertTest.cpp 5 Nov 2004 22:47:21 = -0000 1.9 +++ examples/cppunittest/TestAssertTest.cpp 11 Nov 2006 03:57:43 = -0000 @@ -167,6 +167,17 @@ =20 CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.1, = 1.2, 0.09 ) ); CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.2, = 1.1, 0.09 ) ); + + double inf =3D std::numeric_limits<double>::infinity(); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( inf, = 0.0, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( 0.0, = inf, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_PASS( CPPUNIT_ASSERT_DOUBLES_EQUAL( inf, = inf, 1.0 ) ); + + double nan =3D std::numeric_limits<double>::quiet_NaN(); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( nan, = 0.0, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( nan, = nan, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( nan, = inf, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( inf, = nan, 1.0 ) ); } =20 =20 Now, here is my proposed fix. I'm applying this to Debian's CppUnit = package. Index: src/cppunit/TestAssert.cpp =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /cvsroot/cppunit/cppunit/src/cppunit/TestAssert.cpp,v retrieving revision 1.12 diff -u -b -B -r1.12 TestAssert.cpp --- src/cppunit/TestAssert.cpp 5 Nov 2004 22:47:20 -0000 1.12 +++ src/cppunit/TestAssert.cpp 11 Nov 2006 03:57:56 -0000 @@ -21,7 +21,13 @@ assertion_traits<double>::toString(delta) ); msg.addDetail( AdditionalMessage(message) ); =20 - Asserter::failNotEqualIf( fabs( expected - actual ) > delta, + bool equal; + if ( isfinite(expected) && isfinite(actual) ) + equal =3D fabs( expected - actual ) <=3D delta; + else + equal =3D expected =3D=3D actual; + + Asserter::failNotEqualIf( !equal, = assertion_traits<double>::toString(expected), assertion_traits<double>::toString(actual), sourceLine,=20 Cheers, -Steve |
From: CppUnit d. m. l. <cpp...@li...> - 2006-11-16 06:18:39
|
Howdy, I recently received a Debian bug report for CppUnit = (http://bugs.debian.org/396865) that CPPUNIT_ASSERT_DOUBLES_EQUAL() does = not work as expected if one value is NaN. This bug has previously been reported to CppUnit at least twice: = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D1179131&grou= p_id=3D11795&atid=3D111795 = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D754638&group= _id=3D11795&atid=3D111795 In addition, there have been at least two patches for the problem: = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D1591244&grou= p_id=3D11795&atid=3D311795 = http://sourceforge.net/tracker/index.php?func=3Ddetail&aid=3D756340&group= _id=3D11795&atid=3D311795 Contrary to the claim of Baptiste in the latter patch, isnan() is = standard: it is part of C99. Hopefully we can assume it is portable by = now. My patch (below) uses isfinite() instead, which is also from C99. First, here are additional test cases to nail down the semantics of = CPPUNIT_ASSERT_DOUBLES: - NaN is not equal to anything, not even to another NaN - infinity is equal to infinity This patch is against CVS. Index: examples/cppunittest/TestAssertTest.cpp =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: = /cvsroot/cppunit/cppunit/examples/cppunittest/TestAssertTest.cpp,v retrieving revision 1.9 diff -u -b -B -r1.9 TestAssertTest.cpp --- examples/cppunittest/TestAssertTest.cpp 5 Nov 2004 22:47:21 = -0000 1.9 +++ examples/cppunittest/TestAssertTest.cpp 11 Nov 2006 03:57:43 = -0000 @@ -167,6 +167,17 @@ =20 CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.1, = 1.2, 0.09 ) ); CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.2, = 1.1, 0.09 ) ); + + double inf =3D std::numeric_limits<double>::infinity(); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( inf, = 0.0, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( 0.0, = inf, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_PASS( CPPUNIT_ASSERT_DOUBLES_EQUAL( inf, = inf, 1.0 ) ); + + double nan =3D std::numeric_limits<double>::quiet_NaN(); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( nan, = 0.0, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( nan, = nan, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( nan, = inf, 1.0 ) ); + CPPUNIT_ASSERT_ASSERTION_FAIL( CPPUNIT_ASSERT_DOUBLES_EQUAL( inf, = nan, 1.0 ) ); } =20 =20 Now, here is my proposed fix. I'm applying this to Debian's CppUnit = package. Index: src/cppunit/TestAssert.cpp =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /cvsroot/cppunit/cppunit/src/cppunit/TestAssert.cpp,v retrieving revision 1.12 diff -u -b -B -r1.12 TestAssert.cpp --- src/cppunit/TestAssert.cpp 5 Nov 2004 22:47:20 -0000 1.12 +++ src/cppunit/TestAssert.cpp 11 Nov 2006 03:57:56 -0000 @@ -21,7 +21,13 @@ assertion_traits<double>::toString(delta) ); msg.addDetail( AdditionalMessage(message) ); =20 - Asserter::failNotEqualIf( fabs( expected - actual ) > delta, + bool equal; + if ( isfinite(expected) && isfinite(actual) ) + equal =3D fabs( expected - actual ) <=3D delta; + else + equal =3D expected =3D=3D actual; + + Asserter::failNotEqualIf( !equal, = assertion_traits<double>::toString(expected), assertion_traits<double>::toString(actual), sourceLine,=20 Cheers, -Steve |
From: CppUnit d. m. l. <cpp...@li...> - 2006-11-16 10:05:02
|
CppUnit development mailing list wrote: > Contrary to the claim of Baptiste in the latter patch, isnan() > is standard: it is part of C99. Hopefully we can assume it is > portable by now. > My patch (below) uses isfinite() instead, which is also from C99. C99 is non-standard in the context of C++, which is a superset of C90. So, all C++ compilers might not support this. I've understood that there is a new C++ standard in the works (or is it out already), which is supposed to be a superset of C99. Of course using that is not exactly portable, especially with older compilers. > First, here are additional test cases to nail down the semantics of CPPUNIT_ASSERT_DOUBLES: > - NaN is not equal to anything, not even to another NaN > - infinity is equal to infinity Remember the sign in infinity. Why not then compare NaNs with (maybe with volatile x): if(x == x) { /* not NaN */ } Don't know about portable infinity test, though? -- Tuomo |
From: CppUnit d. m. l. <cpp...@li...> - 2006-11-16 16:55:02
|
Quoting CppUnit development mailing list =20 <cpp...@li...>: > CppUnit development mailing list wrote: >> Contrary to the claim of Baptiste in the latter patch, isnan() >> is standard: it is part of C99. Hopefully we can assume it is >> portable by now. >> My patch (below) uses isfinite() instead, which is also from C99. > > C99 is non-standard in the context of C++, which is a superset of C90. > So, all C++ compilers might not support this. True, they might not; do you know of a current compiler that has =20 neither isfinite() nor the older, BSD finite()? I can revise the patch to include testing for isfinite() and finite() at configuration time. Then inside TestAssert.cpp, add code like this: #if !defined(HAS_ISFINITE) # if defined(HAS_FINITE) # define isfinite(x) finite(x) # else # define isfinite(x) 1 # endif #endif Recall that the rest of the patch is: Index: src/cppunit/TestAssert.cpp =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /cvsroot/cppunit/cppunit/src/cppunit/TestAssert.cpp,v retrieving revision 1.12 diff -u -b -B -r1.12 TestAssert.cpp --- src/cppunit/TestAssert.cpp 5 Nov 2004 22:47:20 -0000 1.12 +++ src/cppunit/TestAssert.cpp 11 Nov 2006 03:57:56 -0000 @@ -21,7 +21,13 @@ assertion_traits<double>::toString(delta) ); msg.addDetail( AdditionalMessage(message) ); - Asserter::failNotEqualIf( fabs( expected - actual ) > delta, + bool equal; + if ( isfinite(expected) && isfinite(actual) ) + equal =3D fabs( expected - actual ) <=3D delta; + else + equal =3D expected =3D=3D actual; + + Asserter::failNotEqualIf( !equal, assertion_traits<double>::toString(expected), assertion_traits<double>::toString(actual), sourceLine, This revision keeps systems with neither isfinite() nor finite() able to compile CppUnit, but they retain the old buggy behaviour with respect to NaN and Inf. Would that be more acceptable? Hmm. Now that I think of it, just flipping the expression from testing "is not equal" to "is equal" fixes the problem with NaNs anyway: all boolean expressions involving a NaN are false. Maybe we don't need the isfinite() test after all ... or maybe we do; I can't remember whether "Inf - Inf" evaluates to 0. -Steve |
From: CppUnit d. m. l. <cpp...@li...> - 2006-11-20 10:31:58
|
CppUnit development mailing list wrote: >> C99 is non-standard in the context of C++, which is a superset of C90. >> So, all C++ compilers might not support this. > > True, they might not; do you know of a current compiler that has > neither isfinite() nor the older, BSD finite()? You're right, I don't. Then again, I'm sure I don't know all compilers currently in use. I prefer to erring to side of caution as it isn't standard. > Hmm. Now that I think of it, just flipping the expression from > testing "is not equal" to "is equal" fixes the problem with NaNs > anyway: all boolean expressions involving a NaN are false. > Maybe we don't need the isfinite() test after all ... or maybe > we do; I can't remember whether "Inf - Inf" evaluates to 0. Inf - Inf evaluates to NaN. But that assumes the right signs, I think. How about (0.0 * <x>)? That should evaluate to NaN only if <x> is Inf, -Inf or NaN. So something like ((0.0 * <x>) == 0.0), if not optimized away, should be equal to finite(<x>). http://steve.hollasch.net/cgindex/coding/ieeefloat.html -- Tuomo |
From: CppUnit d. m. l. <cpp...@li...> - 2006-11-11 17:20:26
|
> Contrary to the claim of Baptiste in the latter patch, isnan() is=20 > standard: it is part of C99. Hopefully we can assume it is portable by= =20 > now. My patch (below) uses isfinite() instead, which is also from C99. Warning, CppUnit supports Microsoft Visual C++, but the function=20 isfinite() is not available on this compiler. However, a similar=20 function called _finite() is provided. I have checked this on Visual C++=20 6, Visual Studio 2003 and Visual Studio 2005. It may be a good idea to put the following macro in the file=20 cppunit/config/config-msvc6.h : #define isfinite(x) _finite(x) Additionnally, it may be good to create a CPPUNIT_HAVE_ISFINITE macro=20 and use it where it is needed. Vincent Rivi=E8re vri...@us... |