GCC and clang offer a number of special intrinsics for avoiding signed overflow, which can be conveniently used like so:
bool CanAddWithoutOverflow(int32_t a, int32_t b) {
int32_t dest;
return !__builtin_add_overflow(a, b, &dest);
}
Since this does not cause overflow if the operation would overflow, this operation is safe to perform with any values of a
and b
. On x86 this also results in very simple code generation:
CanAddWithoutOverflow(int, int):
add edi, esi
setno al
ret
MSVC offers many intrinsics, but none of them can be used to determine if an operation overflowed. This means that all of the cases where overflow could occur have to be explicitly checked. The cleanest way I found to do this was to do the operation using unsigned integers and then check if the result makes sense when reinterpreted as signed:
int32_t WrappingAdd(int32_t a, int32_t b) {
const uint32_t a_u = std::bit_cast<uint32_t>(a);
const uint32_t b_u = std::bit_cast<uint32_t>(b);
return std::bit_cast<int32_t>(a_u + b_u);
}
bool CanAddWithoutOverflow(int32_t a, int32_t b) {
if (a >= 0 && b >= 0) {
return WrappingAdd(a, b) >= std::max(a, b);
} else if (a < 0 && b < 0) {
return WrappingAdd(a, b) <= std::min(a, b);
} else {
return true;
}
}
Predictably, MSVC cannot optimize this, so you end up with a rather large pile of code to handle this scenario correctly.
a$ = 8
b$ = 16
bool CanAddWithoutOverflow(int,int) PROC ; CanAddWithoutOverflow, COMDAT
mov eax, ecx
test ecx, ecx
js SHORT $LN17@CanAddWith
test edx, edx
js SHORT $LN4@CanAddWith
cmp ecx, edx
mov r8d, ecx
cmovl r8d, edx
add eax, edx
cmp eax, r8d
setge al
ret 0
$LN17@CanAddWith:
test edx, edx
jns SHORT $LN4@CanAddWith
cmp edx, eax
cmovl ecx, edx
add eax, edx
cmp eax, ecx
setle al
ret 0
$LN4@CanAddWith:
mov al, 1
ret 0