C++ unsigned and signed conversion
Asked Answered
B

3

7

I've seen this kind of question asked before, but supplied answers don't clear everything for me. When this question is posted it is usually accompanied by next example:

#include <iostream>

int main()
{
    unsigned int u = 10;
             int i = -42;

    std::cout << i + i << std::endl;
    std::cout << i + u << std::endl;

    return 0;
}

Output:

-84
4294967264

All works as expected int is converted to unsigned. But if absolute value of i is less than u it seems like no such conversion is hapenning.

#include <iostream>

int main()
{
    unsigned int u = 10;
             int i = -3;

    std::cout << i + i << std::endl;
    std::cout << i + u << std::endl;

    return 0;
}

Output:

-6
7

I haven't seen any answer mentioning it and can't find any explanation. Even though it seems like a logical thing to happen I havent thing any explanation accounting for this.

Befog answered 22/6, 2021 at 20:39 Comment(6)
how can you tell the difference between a signed 7 and an unsigned 7 ?Ultimo
You can verify the type of an expression using decltype and std::is_same_v. (Or print it with a number of tricks in articles out there for printing types.)Stipitate
@463035818_is_not_a_number ususally answers to this kind of question mention that signed variable (int i) is converted to unsigned as it happens in first example. If it would happen in second example result wouldn't be equal 7.Befog
oh I think now I get it ;)Ultimo
@463035818_is_not_a_number: An unsigned 7 has a U suffix, otherwise it's an integer, a.k.a. signed int.Backboard
@ThomasMatthews its a 7 printed via std::cout on the screen not the integer literal 7. Nevermind, I completely misunderstood the question at first and that was the reason for asking stupid questionsUltimo
G
9

After:

unsigned int u = 10;
         int i = -3;

the evaluation of i + u proceeds by first converting i to unsigned int. For a 32-bit unsigned int, this conversion wraps modulo 232, which is 4,294,967,296. The result of this wrapping is −3 + 4,294,967,296 = 4,294,967,293.

After the conversion, we are adding 4,294,967,293 (converted i) and 10 (u). This would be 4,294,967,303. Since this exceeds the range of a 32-bit unsigned int, it is wrapped modulo 4,294,967,296. The result of this is 4,294,967,303 − 4,294,967,296 = 7.

Thus “7” is printed.

Gilmer answered 22/6, 2021 at 20:51 Comment(0)
B
5

But if absolute value of i is less than u it seems like no such conversion is hapenning.

Your assumption is wrong: such conversion is happening. And by "such conversion", I mean that when -3 was converted to the unsigned type, the result was 4'294'967'293.

I haven't seen any answer mentioning it and can't find any explanation.

Unsigned arithmetic is modular. This is simply how modular arithmetic works.

To understand modular arithmetic, think for example how a 12 hour clock works. It is also modular. You'll notice that the clock face doesn't have any negative numbers:

  • Time is 10 (am, today). 3 hours ago, what was the time? It was 7 (am, today).
  • Time is 10 (am, today). 42 hours ago, what was the time? It was 4 (pm, day before yesterday)

Unsigned arithmetic works exactly like that. Except, instead of 12 values, there are 4'294'967'296 values in case of 32 bit type.


In order to convert an unrepresentable value into the representable range, simply add or subtract the modulo (12 in case of clock, 4'294'967'296 in case of 32 bit unsigned integer) until the value is in the representable range.

Here is the math for the clock examples:

R ≡ 10 + (-3)          (mod 12)
// -3 = 9  + (12 * -1)
R ≡ 10 + 9             (mod 12) 
R ≡ 19                 (mod 12)
// 19 = 7  + (12 *  1)
R ≡ 7                  (mod 12)

R ≡ 10 + (-42)         (mod 12)
// -42 = 6 + (12 * -4)
R ≡ 10 + 6             (mod 12)
R ≡ 16                 (mod 12)
// 16 = 4 +  (12 *  1)
R ≡ 4                  (mod 12)

Here is the math for your examples:

R ≡ 10 + (-42)         (mod 4'294'967'296)
// -42           = 4'294'967'254 + (4'294'967'296 * -1)
R ≡ 10 + 4'294'967'254 (mod 4'294'967'296)
R ≡ 4'294'967'264      (mod 4'294'967'296)

R ≡ 10 + (-3)          (mod 4'294'967'296)
// -3            = 4'294'967'293 + (4'294'967'296 * -1)
R ≡ 10 + 4'294'967'293 (mod 4'294'967'296)
R ≡ 4'294'967'303      (mod 4'294'967'296)
// 4'294'967'303 = 7             + (4'294'967'296 * -1)
R ≡ 7                  (mod 4'294'967'296)
Bone answered 22/6, 2021 at 20:50 Comment(0)
K
0

There are two concepts very important to understanding how negative numbers and unsigned are related. The first is Ones compliment. This a very old standard and no longer used, but it simple to understand. Negative number are simply the not of each bit. -1 = ~1 = 1111...1110. The problem with ones complement is there are two zeros (+0/-0). This gets complicated very quickly. Consider

X = 2-2  
Y = -2+2  
X != Y

That's why modern computers and C/C++ adopted twos compliment. Twos compliment drops -0 (no such thing) and so

-1 = 1111...1111  
-2 = 1111...1110  
so -v = ~1+1

You see there is no "operation" to convert between signed and unsigned. In hardware each is just a vector of bits. Whether the value is signed or unsigned, is simply a question of how the bits are interpreted. To prove this you simply try

printf("%x", -1); // print a signed value as if it was unsigned

So to go back to your question, any small (+v) number is the same, whether it is signed or not. (signed) 1 is the same as unsigned (1). Only when the Most Significant Bit is one, does the interpretation change.

As an aside: in C/C++ "unsigned int" can be shortened to "unsigned" this is a remnant of the rule that any type that is not specified is assumed to be int.

Kiernan answered 22/6, 2021 at 21:8 Comment(2)
One’s complement (not “compliment”) and two’s complement have no effect in this question. The C standard specifies the conversion to unsigned integer types and the wrapping of unsigned arithmetic in terms of arithmetic modulo M+1, where M is the maximum value representable in the unsigned type. Whether a conforming C implementation uses one’s complement, two’s complement, or sign-and-magnitude will not affect the results observed in this case.Gilmer
It's always useful for less experienced programmers to understand that the C/C++ standard is not a bunch of arbitrary rules, but is chosen to map to the hardware it runs on. Where hardware adopts a standard, C/C++ generally follow. Your answer is more popular, so I will concede, but I stand by the main teaching point, that integer casting involves no operation on the value (and thus has no overhead) it simply changes the interpretation of the original value. This contrasts with, int-float, which has significant overhead.Kiernan

© 2022 - 2024 — McMap. All rights reserved.