8

I have two variables (test1 and test2), both unsigned. I need to check which of them is bigger.

I'm trying to understand what happens if overflow occurs.

My first test was done with uint8_t (char) data type:

#include <stdio.h>
#include <stdint.h>
#include <math.h>

int main()
{
    uint8_t test1 = 0;
    printf("test1 = %d\n", test1);

    uint8_t test2 = pow(2, 8 * sizeof(test1)) - 1; //max holdable value of uint8_t
    printf("test2 = %d\n", test2);

    uint8_t test3 = test1 - test2;
    printf("test1 - test2 = %d\n", test3);

    if ((test1 - test2) == 0)
        printf("test1 == test2\n");
    if ((test1 - test2) > 0)
        printf("test1 > test2\n");
    if ((test1 - test2) < 0)
        printf("test1 < test2\n");

    if (test3 == 0)
        printf("test1 == test2\n");
    if (test3 > 0)
        printf("test1 > test2\n");
    if (test3 < 0)
        printf("test1 < test2\n");

    return 0;
}

output:

test1 = 0                                                                                                                                                       
test2 = 255                                                                                                                                                     
test1 - test2 = 1                                                                                                                                               
test1 < test2                                                                                                                                                   
test1 > test2

What? Making the substraction and save it in a variable, then checking it, is different from checking the substraction on the fly?

My second test was done with uint32_t (long) data type:

#include <stdio.h>
#include <stdint.h>
#include <math.h>

int main()
{
    uint32_t test1 = 0;
    printf("test1 = %d\n", test1);

    uint32_t test2 = pow(2, 8 * sizeof(test1)) - 1; //max holdable value of uint32_t
    printf("test2 = %lu\n", test2);

    uint32_t test3 = test1 - test2;
    printf("test1 - test2 = %d\n", test3);

    if ((test1 - test2) == 0)
        printf("test1 == test2\n");
    if ((test1 - test2) > 0)
        printf("test1 > test2\n");
    if ((test1 - test2) < 0)
        printf("test1 < test2\n");

    if (test3 == 0)
        printf("test1 == test2\n");
    if (test3 > 0)
        printf("test1 > test2\n");
    if (test3 < 0)
        printf("test1 < test2\n");

    return 0;
}

output:

test1 = 0                                                                                                                                                       
test2 = 4294967295                                                                                                                                              
test1 - test2 = 1                                                                                                                                               
test1 > test2                                                                                                                                                   
test1 > test2

What??? Now making the substraction and saving it in a variable, then check it, is the same as checking the substraction on the fly?

SO I was expecting that substraction between unsigned values (without an explicit cast) ALWAYS returns a value >= 0. But doing the substraction inside the IF leads to unexpected results.

Now I'm confused. Can someone explain this behaviour to me?

stj
  • 9,037
  • 19
  • 33
Suxsem
  • 292
  • 2
  • 15
  • 2
    At 3rd line in body of main: `uint8_t test2 = pow(2, 8 * sizeof(test1)) - 1;` this is not really sensible, if you take 2 to the power `8 * sizeof(test1)`, that is going to overflow a `uint8_t` and obtain the value zero. Since it is unsigned, overflow always has well-defined values. So instead of writing this, you should instead write `-1`, for clarity. – Chris Beck Dec 21 '15 at 08:29
  • 1
    You are making it very hard work - do you think this question could be simplified to just assignment of test 1 and test 2 then examples of expressions that yield the result you expect of don't expect? There seems to be a large amount of superfluous code to wade through here. – Clifford Dec 21 '15 at 08:31
  • 1
    @ChrisBeck : or `uint8_t test1 = ~0;` rather then assigning a negative value to an unsigned type, or better still use `UCHAR_MAX` or `std::numeric_limits::max()` – Clifford Dec 21 '15 at 08:36
  • @user2104739: You've got a lot of other problems going on here. Especially, using `%d` with an `unsigned int` is not okay. And you are also not compiling with warnings on apparently. If you had done this, the compiler would explain a lot of these problems for you. When you mix signed and unsigned integers, the compiler can do surprising things, and in this case, and if you end up with `signed`... Comparing signed with unsigned... is bad, you should be taking steps to ensure the `0` literal has the right type, `static_cast` results of `test1-test2` and such, to really explore this. – Chris Beck Dec 21 '15 at 08:41

2 Answers2

9

The somewhat arcane type promotion rules apply. In the first example where the operands are uint8_t, for the expression:

test1 - test2

both operands are implicitly promoted to int before subtraction, and the expression itself has type int.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Unfortunately, it is also incorrect. The "arcane type promotion rules" don't actually apply to `unsigned char` or to `unsigned` 8-bit types. This actually a case where the caveat at the bottom of the supplied link "Unfortunately, this is not a precise statement of these rules" is relevant. Math operations on unsigned types use modulo arithmetic. – Peter Dec 21 '15 at 09:26
  • @Peter : As I said, they are arcane, and I would advise to expect the unexpected, and never to use char or short types as arithmetic objects, and to avoid mixed signed and unsigned expressions. That said, I am not convinced that you are correct; from C99 6.3.1.1 : *"If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int."*. So while it is true that modulo arithmetic occurs on unsigned types, by the time the `-` operator is evaluated, the operands are no longer unsigned. – Clifford Dec 21 '15 at 09:43
6

Because of type promotion, your uint8 will be promoted to platform-based int, which is likely to be greater than 8-bit (usually 32-bit) in most of nowadays' Computer.

this line:

if ((test1 - test2) < 0)

if equivalent to

if ((int)((int)test1 - (int)test2) < 0) = -255 < 0

which is true.

However, for this line,

uint8_t test3 = test1 - test2;

it is equivalent to

uint8_t test3 = (uint8_t)((int)test1 - (int)test2); //(uint8_t)(-255) = 1!!;

So both test1 - test2 < 0 and test3 > 0 are (welcome to computer's world!) correct!

That explains your result.

But for uint32, because it is of "higher" rank than native int, so it is not "promoted" and stays as uint32 all the time, or in other words

this line:

if ((test1 - test2) > 0)

if equivalent to

if ((uint32_t)((uint32_t)test1 - (uint32_t)test2) > 0) = large positive number > 0

and this line

uint32_t test3 = test1 - test2;

is equivalent to

uint32_t test3 = (uint32_t)test1 - (uint32_t)test2; //also large positive number

thus you have both test1 - test2 > 0 and test3 > 0.

So, in your two cases, both can only be correct if running on 8-bit machine. Or, suppose in the future, native int is 64-bit, then both cases will be correct too...

Ian
  • 30,182
  • 19
  • 69
  • 107
  • In the first example after promotion the expression is `-255 < 0` – Clifford Dec 21 '15 at 08:56
  • I like the @Clifford answer but I choose that one because it has a good example based on my test code. Thanks to all of you guys! – Suxsem Dec 21 '15 at 14:32
  • Even on an 8 bit machine you won't get the same result as the minimum size of an `int` is 16 bit. – Clifford Dec 21 '15 at 14:44
  • 1
    @user2104749 : No need to explain your preference - your question, whatever works for you. I deliberately went for brief. – Clifford Dec 21 '15 at 14:45