Integral arithmetic in C and C++¶
Unsigned comparison, and
There are clear rules in C, which are not always intuitive, about what type comes out of arithmetic operations between different types. Some are listed in this table:
|op1||op2||op1 + op2|
- all binary arithmetic and comparison operators act like
*, etc. (that means
5U > -6is also false!)
- the order of the operands does not matter
- these rules do not depend on the value of the variables, but only on their type (e.g.
5U - 6Uis the same type as
5U - 4U, that is
I want to know if two wires are neighbouring. I rely on the fact that their ID is in sequence, and the IDs happen to be of type
unsigned int. Then my code:
if (std::abs(w1 - w2) == 1) // do something if the wires are contiguous -- WRONG!!
w1 - w2is an unsigned value (and therefore always positive: Clang will point out that there is no
unsigned int, as it should). If
w1 - w2evaluates to
1and everything is good, but if
6U, that difference is now something like
4294967295, whose absolute value does not compare equal to
1. Similarly for
if (std::abs(w1 - w2) <= 2) // do something if the wires are close -- WRONG!!
LArSoft provides a small function to perform this difference for values of the same type:
It will refuse to compile if the types are different though, in which case the best option is to explicitly cast one of the two operands depending on the prior knowledge (that is, cast the signed argument into unsigned if it is known that the value must be non-negative, and cast the unsigned into a signed otherwise).
#include "larcorealg/CoreUtils/NumericUtils.h" // ... if (util::absDiff(w1, w2) <= 2) // do something if the wires are close
Now I have a base index in an array, and an offset we subtract. The base index is an unsigned integral type (usually
std::size_t), and the offset may be a signed or unsigned integral type (commonly just an
int, but it might be more appropriately a
std::ptrdiff_t). We don't want to point to before the start of the array, so we write:
if (base - offset >= 0) value = data[base - offset]; // WRONG!!
base - offsetis an unsigned value (see table above), so the comparison is always true! The compiler will warn in this specific case, but not for example if you want also to skip the first element:
The solution may be to reshape the comparison, if
if (base - offset > 0) previousValue = data[base - offset - 1]; // may be WRONG!!
Note that if you have a case with signed
if (base > offset) previousValue = data[base - offset - 1]; // may still be wrong if offset < 0
offsetis negative the comparison
base > offsetwill be likely
5U > -6above).
While there is no simple general solution, it is often possible to rely on the fact that the actual possible range of the indices is quite small compared to the range of the data types involved. This means that it's effectively safe to check the access to an array with an unsigned base index by:
in that in all supported architecture a signed negative number is interpreted as a large unsigned number (larger than the available memory if
if (base + offset < N) value = data[base + offset]; if (base + offset - 1 < N) previousValue = data[base + offset - 1];
std::size_t). Note that in the last example the test
base + offset - 1 < Nis not equivalent to
base + offset < N + 1, since the latter is
base + offsetequal to