Cheiron
Cheiron

Reputation: 3736

The semantics of storing the subtraction of two unsigneds to a signed

I am subtracting to unsigned numbers and storing them to a signed type, and it just works. An I do not fully understand why this just works. Take the following example:

    #include <stdio.h>

    int main(void)
    {
        uint32_t a = 1;
        uint32_t b = 2;
        int32_t c = a - b;
        printf("%"PRId32"\n", c);
        return 0;
    }

The result of this subtraction is -1, and it seems like it is only -1 because my computer is two's complement. Am I correct? I am looking at the C11 specification.

If we disect the following statement:

        int32_t c = a - b;

We start with, according to operator precedence (as stated by annotation 85 on page 76), the subtraction:

a - b

C11 6.5.6/6:

The result of the binary - operator is the difference resulting from the subtraction of the second operand from the first.

This is -1 and thus will not fit. Conversion! C11 6.3.1.3/2:

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

So the actual value of a-b is 4294967295. Next up, the assignment operator: C11 6.5.16.1/2:

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

So the unsigned value 4294967295 needs to be converted to a signed value. What are the rules? C11 6.3.1.3/3:

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

So this is fully implementation defined and thus ends up being -1, since that is how my implementation is.

Is this really the case? Would the code result in a different value if I run it on a system with one's complement system? Or am I overlooking something?

Upvotes: 2

Views: 109

Answers (1)

Lundin
Lundin

Reputation: 213960

The result of this subtraction is -1, and it seems like it is only -1 because my computer is two's complement. Am I correct?

Before lvalue conversion upon assignment, the result of the actual operation a - b is 4294967295 = 2^32 - 1 = UINT_MAX, because the subtraction is carried out on uint32_t operands and wrap-around is well-defined, according to the 6.3.1.3/2 rule that you quote. You will get 4294967295 regardless of which signedness format your system uses.

Is this really the case?

Yes, you've read and quoted the standard correctly.

Would the code result in a different value if I run it on a system with one's complement system?

Yes. Raw binary of this is number is 0xFFFFFFFF and so the impl.defined conversion to signed is very likely translating it to the signedness format's corresponding representation of this raw binary:

  • On a 2's complement system, 0xFFFFFFFF gives -1.
  • On a 1's complement system, it would give -0 which is possibly a trap representation.
  • On a signed magnitude system, it would give -2147483647.

Other forms are not allowed (C17 6.2.6.2/2).

Or am I overlooking something?

One little detail. Unlike C's integer conversion rules, the int32_t type specifically is crap-free. It is guaranteed (7.20.1.1) to always be using 2's complement and no padding bits. An exotic signedness system with 1's compl. or signed magnitude would not easily be able to support the int32_t - it is actually an optional type, only mandatory for 2's complement implementations. So your code would not compile if 2's complement wasn't supported.

Also please note that strictly speaking, the correct format specifier for int32_t is "%"PRId32 and not %d.

Upvotes: 3

Related Questions