Boost logo

Boost :

From: Greg Chicares (chicares_at_[hidden])
Date: 2001-05-20 17:09:20


Beman Dawes wrote:
>
> I'm run the Win32 regression tests, preliminary to the next release. The
> tests are looking quite good, but it would be nice to clear the floating
> point failures for several of the config/limits_test.cpp tests, which are
> uniformly failing on the Borland, Metrowerks, and Microsoft compilers.
>
> Since I don't know the first thing about floating point arithmetic, could
> someone who does please take a look at the errors and see if there is any
> obvious fix.
>
> See ftp://boost.sourceforge.net/pub/boost/regression-logs/win32.zip

Patches 1 and 2 below probably take care of most of the win32 problems;
I've only tested them with gcc and borland, though. Additional ideas
below should help several compilers on other platforms pass this test.

*Analysis of win32 errors*

I assume the purpose of limits_test.cpp is to test only whether
<boost/limits.hpp> is correct--not whether the compilers are correct.

1. Most (4/5) of the win32 failures arise when quiet NaNs are compared.

2. Some (2/5) of the win32 failures are abends. I've verified that the
borland-compiled program abends because it initializes the floating
point hardware to raise a fatal signal when quiet NaNs are compared.
I hypothesize that the ms + stlport system abends for the same reason,
but I do not have the tools to test that. Without support for C99
<fenv.h>, there's no standard way to fix this; and supplying a
nonstandard <fenv.h> workaround is outside the scope of this question.

*Recommendations*

1. This patch comments out the NaN equality comparisons and moves them
under the "arithmetic traps" comment that precedes the commented-out
NaN inequality comparisons.

- // NaNs shall always compare "false" when compared for equality
- // If one of these fail, your compiler may be optimizing incorrectly
- BOOST_TEST(! (qnan == 42));
- BOOST_TEST(! (qnan == qnan));
- BOOST_TEST(qnan != 42);
- BOOST_TEST(qnan != qnan);

  // The following tests may cause arithmetic traps etc. Avoid for now.
+ // NaNs shall always compare "false" when compared for equality
+ // BOOST_TEST(! (qnan == 42));
+ // BOOST_TEST(! (qnan == qnan));
+ // BOOST_TEST(qnan != 42);
+ // BOOST_TEST(qnan != qnan);
  // BOOST_TEST(! (qnan < 42));

2. This patch removes other code that raises hardware exceptions
with the borland compiler (and perhaps others). That compiler sets up
the floating point hardware to abend on many operations with quiet
NaNs, and of course it likes signaling NaNs even less.

  const T snan = lim::signaling_NaN();

  std::cout << "IEEE-compatible: " << lim::is_iec559
            << ", traps: " << lim::traps
            << ", bounded: " << lim::is_bounded
            << ", exact: " << lim::is_exact << '\n'
            << "min: " << lim::min() << ", max: " << lim::max() << '\n'
            << "infinity: " << infinity << ", QNaN: " << qnan << '\n';

  print_hex_val(snan, "snan");

It seems likely that these changes will help with compilers on other
platforms too (see below).

*Data: win32 compilers*

Borland C++ 5.5.1
  abends
GCC under cygwin
  passes
Intel C++ 5.0
  12 errors comparing qnans
Metrowerks CodeWarrior
  3 errors on has_signaling_NaN
Microsoft Visual C++
  6 errors comparing qnans
Microsoft Visual C++ with STLport
  8 errors comparing qnans, then abends when testing long double

*Data: other platforms*

Many compilers on other platforms also fail this test. With the above
patches, several should have fewer errors, but none would go from
'fail' to 'pass'. A non-rigorous perusal of the files suggests that
five might be made to pass with extra changes described below; but I
can't analyze this further because I don't have these tools.

3. It appears that these linux targets
  Comeau C++ 4.2.45 beta2 libcomo beta7
  Comeau C++ 4.2.45 beta2 (strict mode) libcomo beta7
might pass if these tests
  lim::infinity() > lim::max()
  -lim::infinity() < -lim::max()
were suppressed. But this change doesn't seem like a very good idea.
I can't guess why this is happening, but there's something similar
going on with some other compilers:

4. It appears that these solaris targets
  Sun WorkShop 6 update 1 C++ 5.2 2000/09/11 64 bit
  GCC 2.95.2
and this alpha target
  Compaq C++ 6.3
might pass if the tests for
  lim::has_infinity
  lim::has_quiet_NaN
  lim::has_signaling_NaN
  lim::infinity() > lim::max()
  -lim::infinity() < -lim::max()
were made conditional on lim::is_iec559. If a compiler doesn't claim
conformance to IEC559, then maybe it shouldn't need to pass these
tests.

*Other observations*

A. If I'm correct in assuming that limit_test.cpp's purpose is only to
test limit_test.hpp, then should it perform any testing at all when
a compiler's own <limits> header is used? If not, here's an idea:

  #include <boost/test/test_tools.hpp>
  #include <iostream>
+ #ifndef BOOST_NO_LIMITS // compiler has its own <limits>
+ int test_main(int, char*[])
+ {
+ return 0;
+ }
+ #else // using <boost/detail/limits.hpp>
  [remainder of file]
+ #endif // using <boost/detail/limits.hpp>

This would cause a clean run with almost every compiler, since
BOOST_NO_LIMITS is defined (so <boost/detail/limits.hpp> is used)
only for __GNUC__ in <boost/config.hpp> as of 2001-04-25.

If OTOH it is desired to test even a compiler's own <limits>, then
this idea:

  int test_main(int, char*[])
  {
+ #ifdef BOOST_NO_LIMITS
+ std::cout << "Testing <boost/detail/limits.hpp>\n";
+ #else
+ std::cout << "Testing compiler's own <limits.hpp>\n";
+ #endif

would make it easier to analyze the output by making it plain whether
the boost header was used.

B. The value of epsilon() seems as interesting as the value of max();
it might be treated similarly, thus:

  std::cout << "IEEE-compatible: " << lim::is_iec559
            << ", traps: " << lim::traps
            << ", bounded: " << lim::is_bounded
            << ", exact: " << lim::is_exact << '\n'
            << "min: " << lim::min() << ", max: " << lim::max() << '\n'
+ << "epsilon: " << lim::epsilon() << '\n'

  print_hex_val(lim::max(), "max");
+ print_hex_val(lim::epsilon(), "epsilon");

A stronger test than 'lim::epsilon() > 0' could be added:

+ T distinguishable_from_unity = T(1) + lim::epsilon();
+ T indistinguishable_from_unity = T(1) + lim::epsilon() / T(2);
  BOOST_TEST(lim::epsilon() > 0);
+ BOOST_TEST(T(1.0) != T( distinguishable_from_unity));
+ BOOST_TEST(T(1.0) == T(indistinguishable_from_unity));

This test actually breaks one compiler not in boost's validation suite
(it has an incorrect value for DBL_EPSILON; I've sent the maintainer
a correction).

*Addendum*

Comparison with quiet NaN: gcc vs. borland--technical details

For this code

  double qnan = 0.0 / 0.0;
  bool comp = qnan == 42;
  bool lt = qnan < 42;
  bool gt = qnan > 42;

gcc generates

  fld QWORD PTR [%ebp-16]
  fld %ds:0x401230
  fucompp
  fnstsw %ax

while borland generates

  fld qword ptr[ebp-0C]
  fcomp dword ptr[NAN.004011BC]
  fnstsw ax

The FCOM[P[P]] and FUCOM[P[P]] instructions can both detect an
unordered relationship; the only difference is that the first (used
by borland) signals an invalid operation, while the second (used by
gcc) raises no signal for (quiet) NaN operand(s).

        Compiler default floating-point exception flags (1 = masked)

       inexact underflow overflow zero-divide denormalized invalid-op
gcc 1 1 1 1 1 1
borland 1 1 0 0 1 0

By default, borland does not mask the invalid operation exception, so
comparing NaNs causes termination. This is what happens when the
limits_test program is run. If we mask the exception, borland does
not give the correct result for a comparison involving NaN(s) (but
then again they don't return 'true' from is_iec559()), and limits_test
reports errors. The only way to get a clean run is not to execute the
particular code that compares NaNs.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk