Suxsem
Suxsem

Reputation: 292

subtraction between unsigned values - unexpected result

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?

Upvotes: 8

Views: 6402

Answers (2)

Ian
Ian

Reputation: 30813

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...

Upvotes: 6

Clifford
Clifford

Reputation: 93566

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.

Upvotes: 9

Related Questions