2

Can anyone explain why this snippet does not produce an underflow exception (on MSVC 2013 and on gcc @ coliru)? The value returned from the average function is lower than DBL_MIN.

#include <float.h>
#include <iostream>
#include <iomanip>
#include <limits>

const size_t g_testValueCount = 10;
const double g_testValues[g_testValueCount] = { DBL_MIN, 0 };

double unsafeAverage(const double* testValues, size_t testValueCount)
{
    double result = 0;
    for (size_t testValueIndex = 0; testValueIndex < testValueCount; ++testValueIndex)
    {
        result += testValues[testValueIndex];
    }
    return result / testValueCount;
}

int main(int argc, char** argv)
{
    std::cout << "DBL_MIN = " << std::setprecision(std::numeric_limits<double>::digits10) << DBL_MIN << std::endl;
    try
    {
        std::cout << "    AVG = " << std::setprecision(std::numeric_limits<double>::digits10) << unsafeAverage(g_testValues, g_testValueCount) << std::endl;
    }
    catch (...)
    {
        std::cout << "unsafeAverage caught an exception!" << std::endl;
    }
    return 0;
}
Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71

2 Answers2

5

Two main reasons you don't catch an underflow exception:

  • Floating point exceptions are not C++ exceptions, so ¹in general you can't catch them with a catch(...).

  • The default floating point underflow behavior with MSVC (and presumably with g++ on Coliru) is to produce a denormal value, or zero. A denormal is a value below the ordinary minimum, with fewer significant bits. As the number of significant bits goes to zero, you get an actual zero.


With C++11 and later you can check for floating point errors via the C99 fetestexcept function.

Here's your code rewritten to use such checking:

#include <float.h>
#include <iostream>
#include <iomanip>
#include <limits>
#include <limits.h>
using namespace std;

#include <fenv.h>

const size_t g_testValueCount = 10;
const double g_testValues[g_testValueCount] = { DBL_MIN, 0 };

auto unsafeAverage( const double* const testValues, int const testValueCount )
    -> double
{
    double result = 0;
    for( int i = 0; i < testValueCount; ++i )
    {
        result += testValues[i];
    }
    return result / testValueCount;
}

auto main() -> int
{
    cout  << setprecision( numeric_limits<double>::digits10 );
    cout << "DBL_MIN = " << DBL_MIN << endl;
    try
    {
        feclearexcept( FE_ALL_EXCEPT );
        auto const result = unsafeAverage(g_testValues, g_testValueCount);
        if( fetestexcept(FE_ALL_EXCEPT ) )
        {
            throw std::runtime_error( "Oopsie daisy!" );
        }
        cout << "    AVG = " << result << endl;
    }
    catch( ... )
    {
        cerr << "!unsafeAverage caught an exception" << endl;
        return EXIT_FAILURE;
    }
}

Notes:
¹ although Visual C++ had that as a language extension in the 1990s

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Regarding the first point - how can I catch and handle the underflow exception then? As for the second, well - I expected to at least give me a zero. The reason for asking this is that I wanted to make a "safe" average function but now I can't even trigger the exception to have a bad example:D – Rudolfs Bundulis May 05 '16 at 09:05
  • Just out of curiosity, is there any real reason why you would want to use the trailing return type format for your functions returning primitives? or are you just trying to complicate the example more than needed? :) – Tommy Andersen May 05 '16 at 19:23
  • @TommyA: Your question doesn't make sense to me, I'm sorry. Using two different syntaxes is manifestly more complicated than using one, contrary to your assumption/assertion/whatever. – Cheers and hth. - Alf May 05 '16 at 20:01
3

Assuming you are using C++11 or newer, you can test for floating point exceptions using the fetestexcept function. Testing for underflow exceptions by passing the FE_UNDERFLOW constant to the function.

Tommy Andersen
  • 7,165
  • 1
  • 31
  • 50