Reputation: 26286
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 N
treated 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.
Upvotes: 4
Views: 1357
Reputation: 8540
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.
Upvotes: 10
Reputation: 1
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.).
Upvotes: 4