Search code examples
cunsignedinteger-arithmetic

Using unsigned in C with negative temporary results


From what I understand, in C the unsigned type behaves like arithmetic (assuming unsigned has 32-bit length) in the integer ring modulo 4294967296.

So, it seems to follow from basic ring theory, that if we have an integer (now I mean ℤ !) calculation involving * + -, then no matter what kind of overflows and underflows temporarily happen, the end result will be correct, as long as the end result is in the range [0, 2^32] ?

For example, in the following calculation:

#include <stdio.h>

int main(void){

  unsigned v = 50000;
  unsigned w = 100000;
  unsigned x = 300000;
  unsigned y = 20000;
  unsigned z = 4000000000;

  unsigned r = v*w - x*y + z;

  printf("v*w - x*y + z = %u \n", r);

  return 0;
}

we have temporary results that differ from the results which we would get if we would calculate in ℤ, but we still get the right answer.

Is this right or can anything go wrong?


Solution

  • As per C11 6.2.5 Types /9 (my emphasis):

    A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

    In other words, provided the types are unsigned, and the modulus value you want to use is UINT_MAX + 1, and the type is the right width (all these are true is in this particular case), this will work as expected.

    You should, however, be properly specifying the constants as unsigned so as to ensure there's no issue with overflow in the constant itself (C will use a wider signed type in that case so, assuming you can assign that wider type to an unsigned int, there should be no issues - there may be issues if, for example, you're not using a two's complement implementation, unlikely though that is).

    You should also use a type guaranteed to be big enough since unsigned int may, on some platforms, be less than 32 bits wide.

    In other words, your statements should be of the form:

    unsigned long z = 4000000000U;
    

    That, of course, means you may need to do your own modulus operations since an unsigned long may be more than 32 bits wide.

    Of course, if your platform provides them, it's far more preferable to use the fixed-width types, uint32_t in your case. That way, you get a Goldilocks type (neither too small nor too big, and definitely two's complement)) and you can let C itself handle the modulo operations:

    uint32_t z = 4000000000U;
    

    That is the solution I'd be going for.