Home > Enterprise >  Differece between uint can be negative
Differece between uint can be negative

Time:01-02

I have a question about unsigned integer in C/C . They and the results of operations on them should always be positive or equal to zero but it does not look the case with uint16_t difference. uint are defined in the C header cstdint.

The next program takes the "wrong" branch:

uint16_t beg = 7;
uint16_t end = 6;
uint16_t zero = 0;

if (end - beg >= zero) cout << "This branch is always taken.\n";
else cout << "This branch will never be taken.\n";

I tested in my computer (gcc 9.3.0) and on Compiler Explorer with same result.

To fix that, I have to cast the difference to uint16_t:

if (uint16_t(end - beg) >= zero) cout << "This branch is always taken.\n";
else cout << "This branch will never be taken.\n";

Thanks, Simone

CodePudding user response:

Because of integer promotion.

In Implicit conversions:

  1. Otherwise, both operands are integers. Both operands undergo integer promotions (see below); then, after integer promotion, one of the following cases applies:

The unsigned type has conversion rank less than the signed type: If the signed type can represent all values of the unsigned type, then the operand with the unsigned type is implicitly converted to the signed type.

Because all uint16_t values can be represented as an int (32 bits), it gets promoted to an int. Because 6 - 7 = -1, the condition is false


Worth noting that if C compilers decide to use 16 bits for an int, this doesn't hold anymore. Because not all uint16_t values can be held in a 16 bits int, it doesn't get promoted anymore. 6 - 7 now cause an wrap-around, and become true

CodePudding user response:

When the bit-width of int (commonly 32) is wider than end, beg (16), end - beg is like (int)end - (int)beg with an int result - potentially negative.

To fix that, I have to cast the difference to uint16_t:

// OP's approach
if (uint16_t(end - beg) >= zero) cout << "This branch is always taken.\n";
else cout << "This branch will never be taken.\n";

That is one way.

Another way to drive code to use unsigned math:

// Suggested alternative
// if (uint16_t(end - beg) >= zero) ...
if (0u   end - beg >= zero) ...

This will cause unsigned math throughout 0u end - beg >= zero*1.

The trouble with casts is one of code maintenance. Should code later use uint32_t beg, end, consider the now bug uint16_t(end - beg).

True that any local code change warrants a larger review, it is simply that gentle nudges to use unsigned math typically cause less issues and less maintenance than casts. The nudge never narrows. A cast may widened or narrow.


Deeper

The core issues lies with using unsigned types narrower than int/unsigned. When able, avoid this for non-array objects like beg, end, .... When obliged to use small unsigned types that may be narrower than unsigned, take care to insure the desired unsigned/signed math is used on these objects.

From time-to-time, I wanted a uint_N_and_at_least_unsigned_width_t type to avoid this very problem.


*1 Unless beg, end later become a signed integer type wider than unsigned. In which case, usually the wider signed math is preferred.

  • Related