I am a beginner in C . I have recently learned about 2's Complement
and other ways to represent negative number and why 2's complement
was the most appropriate one.
What i want to ask is for example,
int a = -3;
unsigned int b = -3; //This is the interesting Part.
Now , for the conversion of int type
The standard says:
6.3.1.3 Signed and unsigned integers
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
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.
The first paragraph can't be used as -3
can't be represented by unsigned int
.
Therefore paragraph 2 comes to play and we need to know the maximum value for unsigned int. It can be found as UINT_MAX in limits.h. The maximum value in this case is 4294967295
so the calculation is:
-3 + UINT_MAX + 1 = -3 + 4294967295 + 1 = 4294967293
Now 4294967293
in binary is 11111111 11111111 11111111 11111101
and -3
in 2's Complement form is 11111111 11111111 11111111 11111101
so they are essentially same bit representation , it would be always same no matter what negative integer i am trying to assign to unsigned int.So isn't unsigned type redundant.
Now i know that printf("%d" , b)
is an undefined behavior according to standard, but isn't that what is a reasonable and more intuitive way to do things. As representation will be same if negative are represented as 2's Complement
and that is what we have now , and other ways used are rare and most probably will not be in future developments.
So if we could have only one type say int , now if int x = -1
then %d
checks for the sign bit and print negative number if sign bit is 1
and %u
always interpret the plain binary digit (bits) as it is . Addition and subtraction are already dealt with because of using 2's complement
. So isn't this more intuitive and less complex way to do things.
I think a major reason is operators and operations depends on the signed-ness.
You've observed add/subtract behaves the same for signed and unsigned types, if signed types uses 2's compliment (and you've been ignoring the fact that this "if" sometimes is not the case.)
There are numerous cases where the compiler needs the signed-ness information to understand the purpose of the program.
1. Integer promotion.
When a narrower type is converted to a wider type, the compiler will generate the code depending the operands' types.
E.g. if you convert
signed short
tosigned int
andint
is wider thanshort
, the compiler would generate code that does the conversion, and that conversion is different from "unsigned short" to "signed int" (sign extension or not).2. Arithmetic right shift
-1>>1
can be still-1
if the implementation choose to, but0xffffffffu>>1
must be0x7fffffffu
3. Integer division
Similarly,
-1/2
is0
,0xffffffffu/2
is0x7fffffffu
4. 32bit multiply by 32bit, with 64 bit result:
This is a little hard to explain, so let me use code instead.
Demo: http://ideone.com/k30nZ9
5. And of course, comparison.
One can design a signed-ness-less language, but then a lot of operators needs to split into two or more versions so that the programmer can express the purpose of the program, e.g. operator
/
needs to be split intoudiv
andsdiv
, operator*
need to be split intoumul
andsmul
, integer promotion needs to be explicit, operator>
needs to bescmpgt
/ucmpgt
.........That would be a horrible language to use, isn't it?
Bonus: All pointers usually have the same bit representation but have different operator
[]
,->
,*
,++
,--
,+
,-
.