4

I have a fixed-point implementation for some financial application. It's basically an integer wrapped in a class that is based on the number of decimals given Ntreated as a decimal number. The class is paranoid and checks for overflows, but when I ran my tests in release mode, and they failed, and finally I created this minimal example that demonstrates the problem:

#include <iostream>
#include <sstream>

template <typename T, typename U>
typename std::enable_if<std::is_convertible<U, std::string>::value, T>::type 
FromString(U&& str)
{
    std::stringstream ss;
    ss << str;
    T ret;
    ss >> ret;
    return ret;
}

int main()
{
    int NewAccu=32;
    int N=10;

    using T = int64_t;

    T l = 10;
    T r = FromString<T>("1" + std::string(NewAccu - N, '0'));
    if (l == 0 || r == 0) {
        return 0;
    }
    T res = l * r;
    std::cout << l << std::endl;
    std::cout << r << std::endl;
    std::cout << res << std::endl;
    std::cout << (res / l) << std::endl;
    std::cout << std::endl;
    if ((res / l) != r) {
        throw std::runtime_error(
                   "FixedPoint Multiplication Overflow while upscaling [:" + std::to_string(l) + ", " + std::to_string(r) + "]");
    }

    return 0;
}

This happens with Clang 6, my version is:

$ clang++ --version
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

It's funny because it's an impressive optimization, but this ruins my application and prevents me from detecting overflows. I was able to reproduce this problem in g++ here. It doesn't throw an exception there.

Notice that the exception is thrown in debug mode, but it's not in release mode.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • What about using `volatile`? – curiousguy Sep 01 '18 at 06:39
  • 1
    @curiousguy That may be a possible solution. I'll try it now. But isn't that an overkill? It'll destroy all optimizations in my program! – The Quantum Physicist Sep 01 '18 at 06:40
  • `volatile` applies strictly to one variable, it doesn't inhibit the optimisations before or after. It does break value propagation through the volatile variable, though. Like outputting to a local socket and reading back the same value (such that the compiler wouldn't possibly know is the same). So it depends on whether you want value propagation or a simple fix. – curiousguy Sep 01 '18 at 06:44
  • Also see [Detecting signed overflow in C/C++](https://stackoverflow.com/a/32317442/1708801) – Shafik Yaghmour Sep 01 '18 at 07:15
  • For overflow-trapping fixed-point arithmetic, consider [CNL](https://github.com/johnmcfarlane/cnl). – John McFarlane Sep 01 '18 at 22:58

2 Answers2

10

As @Basile already stated, signed integer overflow is an undefined behavior, so the compiler can handle it in any way - even optimizing it away to gain a performance advantage. So detecting integer overflow after its occurence is way too late. Instead, you should predict integer overflow just before it occurs.

Here is my implementation of overflow prediction of integer multiplication:

#include <limits>

template <typename T>
bool predict_mul_overflow(T x, T y)
{
    static_assert(std::numeric_limits<T>::is_integer, "predict_mul_overflow expects integral types");

    if constexpr (std::numeric_limits<T>::is_bounded)
    {
        return ((x != T{0}) && ((std::numeric_limits<T>::max() / x) < y));
    }
    else
    {
        return false;
    }
}

The function returns true if the integer multiplication x * y is predicted to overflow.

Note that while unsigned overflow is well-defined in terms of modular arithmetic, signed overflow is an undefined behavior. Nevertheless, the presented function works for signed and unsigned T types as well.

plasmacel
  • 8,183
  • 7
  • 53
  • 101
4

If you want to detect (signed) integer overflows (on scalar types like int64_t or long), you should use appropriate builtins, often compiler specific.

For GCC, see integer overflow builtins.

Integer overflow (on plain int or long or other signed integral type) is an instance of undefined behavior, so the compiler can optimize as it please against it. Be scared. If you depend on UB you are no more coding in standard C++ and your program is tied to a particular compiler and system, so is not portable at all (even to other compilers, other compiler versions, other compilation flags, other computers and OSes). So Clang (or GCC) is allowed to optimize against integer overflow, and sometimes does.

Or consider using some bignum package (then of course you don't deal with just predefined C++ integral scalar types). Perhaps GMPlib.

You could consider using GCC's __int128 if your numbers fit into 128 bits.

I believe you cannot reliably detect integer overflows when they happen (unless you use the integer overflow builtins). You should avoid them (or use some bignum library, or some library using these builtins, etc.).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • I was gonna respond to your comment. This is not always valid for me because I'm using `boost::multiprecision`. The class is a template class. – The Quantum Physicist Sep 01 '18 at 06:34
  • 3
    It is still possible to do reliable overflow predicates, predicting, instead of catching the overflow. As you already said, when overflow occurs, that is undefined behavior, and compilers can do whatever they want. – plasmacel Sep 01 '18 at 06:37
  • @plasmacel I was trying to detect overflows in my program... doesn't seem to work! – The Quantum Physicist Sep 01 '18 at 06:38
  • 2
    @TheQuantumPhysicist As I see you are trying to detect overflow **after** it happened. You should predict it before it happens. – plasmacel Sep 01 '18 at 06:40
  • @plasmacel I'll research that a little bit and try. If you have a solution, please post it. – The Quantum Physicist Sep 01 '18 at 06:42
  • @TheQuantumPhysicist: Why don't you use [integer overflow builtins](https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html)? They answer your needs! – Basile Starynkevitch Sep 01 '18 at 06:44
  • @BasileStarynkevitch Because I'm using a template class that mostly takes `boost::multiprecision` and I need a precision of up to 512 bits. This int64_t is a special case. It would be very painful to specialize things manually. – The Quantum Physicist Sep 01 '18 at 06:47
  • 2
    Integer overflow gives undefined behaviour on any *signed* integral type (other than class types that emulate behaviour of integral types). It is not undefined for unsigned types. In any event, the undefined behaviour means it is impossible *in standard C++* to detect overflow after the fact. It is necessary to check if an operation would overflow, and throw the exception BEFORE actually doing the operation. – Peter Sep 01 '18 at 06:47
  • 2
    Does `boost::multiprecision` define its signed integer overflow behavior? Signed integer overflow is undefined behavior by the standard, so you are on boost::multiprecision's hands now. – sandthorn Sep 01 '18 at 06:47
  • @sandthorn That's one more thing to research. I think they have a "checked" type, but I was hoping I wouldn't need to distinguish between types. I think predicting overflows is the safest solution. – The Quantum Physicist Sep 01 '18 at 06:52
  • 1
    @BasileStarynkevitch Btw, thank you for the help. Appreciate it! – The Quantum Physicist Sep 01 '18 at 07:19