16

I have a situation in which some numerical results (involving floating point arithmetic with double and float) become incorrect for large input sizes, but not for small ones.

In general, I would like to know which tools are available to diagnose conditions such as numerical overflows and problematic loss of precision.

In other words: Is there a tool which complains about overflows etc. the same way valgrind complains about memory errors?

clstaudt
  • 21,436
  • 45
  • 156
  • 239
  • 1
    @LihO If could do this then I could also answer my question myself. I'll be happy to describe what worked for me once I have collected some suggestions. Right now, I do not know where to start looking, or if such a tool can even exist. – clstaudt Feb 15 '13 at 14:25
  • Integer or floating point arithmetic? You are not likely to be reaching an overflow condition in double precision floating point, because that happens when you exceed about 308 decimal digits (when your number is too big to represent in the exponent). Most likely you are dealing with a loss of precision. – amdn Feb 15 '13 at 14:30
  • 1
    To answer your question we need to know what you mean by "numerical results become incorrect for large input sizes" – amdn Feb 15 '13 at 14:32
  • @amdn Floating point arithmetic. More specifically, I calculate [Modularity](https://en.wikipedia.org/wiki/Modularity_%28networks%29) for large networks. I can also show you the specific [code](https://gist.github.com/anonymous/4960737), but I'm also interested in general suggestions. – clstaudt Feb 15 '13 at 14:32
  • Look at the C99 feraiseexcept and other fenv.h functions. – Marc Glisse Feb 15 '13 at 14:37
  • @LihO the questions were about integers and also that was radically changing the question - they should be answers if they were relevant – mmmmmm Feb 15 '13 at 14:37
  • 1
    In case it's about floating-point overflow, then this could be interesting reading for you: http://stackoverflow.com/questions/3260022/how-to-handle-a-float-overflow – LihO Feb 15 '13 at 14:38
  • @Mark: Yes, I also added floating-point tag and put "floating-point" to the title as well. – LihO Feb 15 '13 at 14:41
  • From the `double` tag I assume you are talking about floating point calculation? In that case overflow is an issue separate from loss of precision. Overflow can be detected through IEEE 754 exception flags or through occurrence of Infinity or NaN values. To track loss of precision, you could look for a library to do your calculation using interval arithmetic. You might also want to try to analyze numerical stability of your algorithms. But your links indicate that you are worried about detecting integer overflow?! That is yet another issue. – JoergB Feb 15 '13 at 14:42
  • @JoergB My issue is about floating point arithmetic, possibly both overflows and loss of precision. Can you post a link explaining IEEE754 exception flags? – clstaudt Feb 15 '13 at 14:43
  • If you look at the valgrind documentation, they explicitly ask you to report a bug to them if you would like it to report this information. – Marc Glisse Feb 15 '13 at 14:46
  • +1 for bringing out the interesting topic. I've also found this work: [Static Analysis for Detecting and Avoiding Floating-Point Run-Time Errors in Logic Programs](http://etheses.whiterose.ac.uk/1347/1/fontarnau.pdf) – LihO Feb 15 '13 at 14:48
  • Results that become “incorrect” for large values but not for small ones are more likely an indicator of casual use of floating-point that incurs rounding errors (which are typically larger for large values than for small values) than it is of floating-point overflow. Hence, this question may be assuming a conclusion (floating-point overflow has occurred) that is not justified by the observed symptoms. – Eric Postpischil Feb 15 '13 at 20:17
  • @EricPostpischil I would also like to tackle rounding errors, but before that, I would like to exclude overflows as an error source. Overflows are also probably easier to detect. – clstaudt Feb 15 '13 at 21:29

4 Answers4

8

If you enable floating point exceptions, then the FPU can throw an exception on overflow. How exactly this works is operating system dependent. For example:

  • On Windows, you can use _control87 to unmask _EM_OVERFLOW so that you'll get a C++ exception on overflow.
  • On Linux, you can use feenableexcept to enable exceptions on FE_OVERFLOW so that you'll get a SIGFPE on overflow. For example, to enable all exceptions, call feenableexcept(FE_ALL_EXCEPT) in your main. To enable overflow and divide by zero, call feenableexcept(FE_OVERFLOW | FE_DIVBYZERO).

Note that, in all cases, third-party code may disable exceptions that you've enabled; this is probably rare in practice.

This is probably not quite as nice as Valgrind, since it's more of a drop-to-debugger-and-manually-inspect than it is a get-a-nice-summary-at-the-end, but it works.

Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • Linux is relevant for me. I would like to enable all of these exceptions: `divide-by-zero`, `overflow`, `underflow`, `inexact`, `invalid`. I'm unfamiliar with this kind of C functions. Do I simpy call `feenableexcept(?)` in my `main`? What is the correct argument? – clstaudt Feb 15 '13 at 14:59
  • @cls - I added some examples. Thanks. – Josh Kelley Feb 15 '13 at 15:11
  • I `#include ` and then `std::feenableexcept(FE_ALL_EXCEPT);`, but I get `error: 'feenableexcept' is not a member of 'std'`. Note that I do not get an error for `std::feclearexcept(FE_ALL_EXCEPT);`. What's wrong here? – clstaudt Feb 15 '13 at 15:26
  • 1
    @cls - As explained in the [feenableexcept](http://linux.die.net/man/3/feenableexcept) manpage, `feenableexcept` is a GNU extension, so you'll need to define _GNU_SOURCE to get it. An easy way to do that is to have your build system add -D_GNU_SOURCE to your compiler options when it invokes your C++ compiler. – Josh Kelley Feb 15 '13 at 15:28
  • Even if I compile with `-D_GNU_SOURCE` this does not work. Compiler flags and error messsage: https://gist.github.com/anonymous/4961296 – clstaudt Feb 15 '13 at 15:59
  • @cls - It's just a C function. You don't need `std::`. (And this is getting a bit awkward for comments and a bit far afield of the initial question - you might want to post as a separate question.) – Josh Kelley Feb 15 '13 at 16:01
  • Now it compiles. I'll try to figure out how to use this productively, for now it just kills the program at some point and prints "floating point exception". – clstaudt Feb 15 '13 at 16:20
  • 4
    You might not want to enable traps for inexact operations. In most floating-point code, inexact operations are normal and expected, and enabling exceptions will cause a trap on nearly every floating-point operation. Unless your code is carefully designed never to use inexact operations in normal conditions or to use traps to alter floating-point behavior in special ways, then enabling traps for inexact operations will be a grueling exercise. – Eric Postpischil Feb 15 '13 at 20:20
3

To diagnose overflow, you can use floating point exceptions. See for example cppreference. Note that you may need to use implementation-specific functions to configure the behavior of floating point errors.

Note that even though they are often called 'exceptions', floating point errors don't cause C++ exceptions.

The cppreference code shows what should be the default behavior for implementations based on IEEE 754: you can check the floating point exception flags whenever you find it appropriate. You should clear the flags befor entering your calculation. You may want to wait until your calculation is through to see, if it has set any flags or you may want to check every single operation that you suspect of being susceptible to error.

There may be implementation-specific extensions to have such 'exceptions' trigger something you can't ignore. On Windows/MSVC++ that could be a 'structured exception' (not a real C++ one) , on Linux that could be SIGFPE (so you need a signal handler to handle the errors). You'll need implementation-specific library functions or even compiler/linker flags to enable such behavior.

I'd still assume that overflow is unlikely to be your problem. If some of your input become large while other values remain small, you are likely to lose precision when combining them. One way to control that is to use interval arithmetic. There are various libraries for that, including boost interval.

Disclaimer: I have no experience with this library (nor other interval arithmetic libraries), but maybe this can get you started.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
JoergB
  • 4,383
  • 21
  • 19
  • What is the code I need to add to enable all of these floating point exceptions throughout my program? The don't find the code example on cppreference very helpful. – clstaudt Feb 15 '13 at 15:03
  • See the answer by @Josh Kelley. It depends on platform, compiler, library. Tell us what platform/implementation you use - or better yet RTFM of your implementation. On Linux, you typically have a man-page. – JoergB Feb 15 '13 at 15:33
1

Maybe you need to debug an implementation of an algorithm where you may have made a coding mistake and want to trace the floating point computations being carried out. Maybe you need a hook to inspect all values being operated on, looking for values that appear to be out of the range you expect. In C++ you can define your own floating point class and use operator overloading to write your calculations in a natural way, while retaining the ability to inspect all calculations.

For example, here's a program that defines an FP class, and prints out all additions and multiplications.

#include <iostream>
struct FP {
    double value;
    FP( double value ) : value(value) {}
};
std::ostream & operator<< ( std::ostream &o, const FP &x ) { o << x.value; return o; }
FP operator+( const FP & lhs, const FP & rhs ) {
    FP sum( lhs.value + rhs.value );
    std::cout << "lhs=" << lhs.value << " rhs=" << rhs.value << " sum=" << sum << std::endl;
    return sum;
}
FP operator*( const FP & lhs, const FP & rhs ) {
    FP product( lhs.value * rhs.value );
    std::cout << "lhs=" << lhs.value << " rhs=" << rhs.value << " product=" << product << std::endl;
    return product;
}

int main() {
    FP x = 2.0;
    FP y = 3.0;
    std::cout << "answer=" << x + 2 * y << std::endl;
    return 0;
}

Which prints

lhs=2 rhs=3 product=6
lhs=2 rhs=6 sum=8
answer=8

Update: I've enhanced the program (on x86) to show the floating point status flags after each floating point operation (only implemented addition and multiplication, others could be easily added).

#include <iostream>

struct MXCSR {
    unsigned value;
    enum Flags {
        IE  = 0, // Invalid Operation Flag
        DE  = 1, // Denormal Flag
        ZE  = 2, // Divide By Zero Flag
        OE  = 3, // Overflow Flag
        UE  = 4, // Underflow Flag
        PE  = 5, // Precision Flag
    };
};
std::ostream & operator<< ( std::ostream &o, const MXCSR &x ) {
    if (x.value & (1<<MXCSR::IE)) o << " Invalid";
    if (x.value & (1<<MXCSR::DE)) o << " Denormal";
    if (x.value & (1<<MXCSR::ZE)) o << " Divide-by-Zero";
    if (x.value & (1<<MXCSR::OE)) o << " Overflow";
    if (x.value & (1<<MXCSR::UE)) o << " Underflow";
    if (x.value & (1<<MXCSR::PE)) o << " Precision";
    return o;
}

struct FP {
    double value;
    FP( double value ) : value(value) {}
};
std::ostream & operator<< ( std::ostream &o, const FP &x ) { o << x.value; return o; }
FP operator+( const FP & lhs, const FP & rhs ) {
    FP sum( lhs.value );
    MXCSR mxcsr, new_mxcsr;
    asm ( "movsd %0, %%xmm0 \n\t"
          "addsd %3, %%xmm0 \n\t"
          "movsd %%xmm0, %0 \n\t"
          "stmxcsr %1 \n\t"
          "stmxcsr %2 \n\t"
          "andl  $0xffffffc0,%2 \n\t"
          "ldmxcsr %2 \n\t"
          : "=m" (sum.value), "=m" (mxcsr.value), "=m" (new_mxcsr.value)
          : "m" (rhs.value)
          : "xmm0", "cc" );

    std::cout << "lhs=" << lhs.value
              << " rhs=" << rhs.value
              << " sum=" << sum
              << mxcsr
              << std::endl;
    return sum;
}
FP operator*( const FP & lhs, const FP & rhs ) {
    FP product( lhs.value );
    MXCSR mxcsr, new_mxcsr;
    asm ( "movsd %0, %%xmm0 \n\t"
          "mulsd %3, %%xmm0 \n\t"
          "movsd %%xmm0, %0 \n\t"
          "stmxcsr %1 \n\t"
          "stmxcsr %2 \n\t"
          "andl  $0xffffffc0,%2 \n\t"
          "ldmxcsr %2 \n\t"
          : "=m" (product.value), "=m" (mxcsr.value), "=m" (new_mxcsr.value)
          : "m" (rhs.value)
          : "xmm0", "cc" );

    std::cout << "lhs=" << lhs.value
              << " rhs=" << rhs.value
              << " product=" << product
              << mxcsr
              << std::endl;
    return product;
}

int main() {
    FP x = 2.0;
    FP y = 3.9;
    std::cout << "answer=" << x + 2.1 * y << std::endl;
    std::cout << "answer=" << x + 2 * x << std::endl;
    FP z = 1;
    for( int i=0; i<310; ++i) {
        std::cout << "i=" << i << " z=" << z << std::endl;
        z = 10 * z;
    }

    return 0;
}

The last loop multiplies a number by 10 enough times to show overflow happen. You'll notice precision errors happen as well. It ends with the value being infinity once it overflows.

Here's the tail of the output

lhs=10 rhs=1e+305 product=1e+306 Precision
i=306 z=1e+306
lhs=10 rhs=1e+306 product=1e+307
i=307 z=1e+307
lhs=10 rhs=1e+307 product=1e+308 Precision
i=308 z=1e+308
lhs=10 rhs=1e+308 product=inf Overflow Precision
i=309 z=inf
lhs=10 rhs=inf product=inf
amdn
  • 11,314
  • 33
  • 45
  • Tracing like this when you deal with million of numbers is not going to be practical. Also a class with operators doesn't quite do the same as basic types in an expression. So I don't think that's the best solution. – Alexis Wilke Jan 22 '22 at 05:09
1

In addition to the excellent suggestions already posted, here is another approach. Write a function that examines your floating point data structures, doing range and consistency checks. Insert calls to it in your main loop. In order to examine other variables you can set a breakpoint in the checker after it has found a problem.

This is more set-up work than enabling exceptions, but can pick up subtler problems such as inconsistencies and numbers that are larger than expected without having gone infinite, leading to detection close to the original problem.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75