28

I am trying to convert 65529 from an unsigned int to a signed int. I tried doing a cast like this:

unsigned int x = 65529;
int y = (int) x;

But y is still returning 65529 when it should return -7. Why is that?

Sam
  • 7,252
  • 16
  • 46
  • 65
darksky
  • 20,411
  • 61
  • 165
  • 254
  • You could try this: `y=(int)x<<(sizeof(int)*CHAR_BIT-16)>>(sizeof(int)*CHAR_BIT-16);`. Should work on most platforms. – Alexey Frunze Nov 29 '11 at 20:39
  • 10
    32-bit era has come decades ago. If you're still using ancient compiler for DOS or embedded systems where int has 16 bits then the result may be as you expected – phuclv Oct 23 '13 at 02:45
  • Why would it be -7? 65529 - 65535 = -6... – MarcusJ Dec 05 '15 at 09:45
  • related http://stackoverflow.com/questions/50605/signed-to-unsigned-conversion-in-c-is-it-always-safe – Ciro Santilli OurBigBook.com Apr 18 '16 at 17:37
  • 3
    @MarcusJ Because 2^16 is 65536, not 65535 (hint: powers of 2 are always even). So the correct calculation for 16-bit integers is 65529 - 65536 which, as expected, is -7. You can count down from negative one: -1 is 65535, -2 is 65534, ..., -7 is 65529. But OP apparently isn't using 16-bit integers. – Tom Karzes Oct 27 '20 at 00:41
  • @TomKarzes Thanks for the response, but I asked this 5 years ago when I was just starting to teach myself how to program. – MarcusJ Oct 27 '20 at 14:29
  • 2
    @MarcusJ Hah, yeah I saw that, but I happened upon this page and since no one had responded to your comment (and you hadn't removed it), I figured better late than never. – Tom Karzes Oct 27 '20 at 16:45

9 Answers9

39

It seems like you are expecting int and unsigned int to be a 16-bit integer. That's apparently not the case. Most likely, it's a 32-bit integer - which is large enough to avoid the wrap-around that you're expecting.

Note that there is no fully C-compliant way to do this because casting between signed/unsigned for values out of range is implementation-defined. But this will still work in most cases:

unsigned int x = 65529;
int y = (short) x;      //  If short is a 16-bit integer.

or alternatively:

unsigned int x = 65529;
int y = (int16_t) x;    //  This is defined in <stdint.h>
Mysticial
  • 464,885
  • 45
  • 335
  • 332
  • 2
    If you can guarantee that `short` is a 16-bit integer, then yes, int `y = (short)x;` will work. Or you can use `int16_t` if your compiler has the `` header. – Mysticial Nov 29 '11 at 20:33
  • @Nayefc As I said, you can check the size of your types by printing the value of `sizeof(int)` `sizeof(short)` or whichever you need – Szabolcs Nov 29 '11 at 20:34
  • 3
    There is a mostly C-compliant way to do it: `y = x < 32767 ? (int)x : (x > 32768 ? -(int)-x : -32768);` (the only wrinkle is that `-32768` is not necessarily representable as a signed int, so you have to decide how to treat that). – caf Nov 29 '11 at 21:50
  • Yeah, that works too - albeit a dirtier approach. I didn't think about going all the way to branching to do it... – Mysticial Nov 29 '11 at 21:56
  • @Mysticial: It won't branch on a decent compiler. – R.. GitHub STOP HELPING ICE Nov 29 '11 at 23:36
  • Erk can't edit comment now but that first `32767` should be a `32768`. – caf Nov 29 '11 at 23:42
  • Just out of curiosity, I just tried it in VS2010 with full optimizations on x64. You're right, it doesn't branch. As I was half-expecting, it compiled to a bunch of `cmp` and `cmov` instructions. So it's smart enough to get rid of the branches, but not smart enough to turn it into a single `movsx` sign-extension. EDIT: Even with the first `32767` changed to `32768`. – Mysticial Nov 29 '11 at 23:44
6

I know it's an old question, but it's a good one, so how about this?

unsigned short int x = 65529U;
short int y = *(short int*)&x;

printf("%d\n", y);

This works because we are casting the address of x to the signed version of it's type, that's permitted by the C standard. Not all type punning like this (most in fact) is legal. The standard says this.

An object shall have its stored value accessed only by an lvalue that has one of the following types:

  • the declared type of the object,
  • a qualified version of the declared type of the object,
  • a type that is the signed or unsigned type corresponding to the declared type of the object,
  • a type that is the signed or unsigned type corresponding to a qualified version of the declared type of the object,
  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
  • a character type.

So, alas, since we are accessing the bits of x as if they were a signed (via the pointer), the actual conversion operation is replaced by reading what appears to be just a negative signed short, and conversion takes place without issue. However, it's possible for this to screw up on a one's complement machine, but those are so, so rare, and so, so obsolete, I wouldn't even bother with looking out for them.

wovano
  • 4,543
  • 5
  • 22
  • 49
Subsentient
  • 554
  • 3
  • 12
  • @pm89 I don't know about your definition of "works", but at least a compiler implementation could target a system with 1-s complement instead of 2-complement integers. For such a system, the solution would **work**, but it would result in **different values**. On the other hand, a compiler could define `short` to be 32bit size. In this case, the value would stay positive after the cast. Of course none of this is relevant if you take one of todays matured compilers and a standard x86/x64 hardware. Just don't be ignorant to the standard when talking about *regardless of compiler implementation* – grek40 Sep 22 '18 at 16:16
  • 1
    @pm89 the accepted answer is explicit about the implementation defined behavior, so it has the same theoretical problems but it doesn't claim to produce the same results for any implementation oft the standard. – grek40 Oct 01 '18 at 18:38
5

@Mysticial got it. A short is usually 16-bit and will illustrate the answer:

int main()  
{
    unsigned int x = 65529;
    int y = (int) x;
    printf("%d\n", y);

    unsigned short z = 65529;
    short zz = (short)z;
    printf("%d\n", zz);
}

65529
-7
Press any key to continue . . .


A little more detail. It's all about how signed numbers are stored in memory. Do a search for twos-complement notation for more detail, but here are the basics.

So let's look at 65529 decimal. It can be represented as FFF9h in hexadecimal. We can also represent that in binary as:

11111111 11111001

When we declare short zz = 65529;, the compiler interprets 65529 as a signed value. In twos-complement notation, the top bit signifies whether a signed value is positive or negative. In this case, you can see the top bit is a 1, so it is treated as a negative number. That's why it prints out -7.

For an unsigned short, we don't care about sign since it's unsigned. So when we print it out using %d, we use all 16 bits, so it's interpreted as 65529.

JoeFish
  • 3,009
  • 1
  • 18
  • 23
2

To understand why, you need to know that the CPU represents signed numbers using the two's complement (maybe not all, but many).

    byte n = 1; //0000 0001 =  1
    n = ~n + 1; //1111 1110 + 0000 0001 = 1111 1111 = -1

And also, that the type int and unsigned int can be of different sized depending on your CPU. When doing specific stuff like this:

   #include <stdint.h>
   int8_t ibyte;
   uint8_t ubyte;
   int16_t iword;
   //......
c-a
  • 94
  • 1
  • 12
1

The representation of the values 65529u and -7 are identical for 16-bit ints. Only the interpretation of the bits is different.

For larger ints and these values, you need to sign extend; one way is with logical operations

int y = (int )(x | 0xffff0000u); // assumes 16 to 32 extension, x is > 32767

If speed is not an issue, or divide is fast on your processor,

int y = ((int ) (x * 65536u)) / 65536;

The multiply shifts left 16 bits (again, assuming 16 to 32 extension), and the divide shifts right maintaining the sign.

Doug Currie
  • 40,708
  • 1
  • 95
  • 119
  • This only works on little endian two's complement systems. – yyny Dec 17 '16 at 11:09
  • 1
    The questioner's machine is two's complement as evidenced by the values he reported. Endianess has nothing to do with it; I am only using int valves here, not individual bytes from memory. – Doug Currie Dec 17 '16 at 15:40
0

Since converting unsigned values use to represent positive numbers converting it can be done by setting the most significant bit to 0. Therefore a program will not interpret that as a Two`s complement value. One caveat is that this will lose information for numbers that near max of the unsigned type.

template <typename TUnsigned, typename TSinged>
TSinged UnsignedToSigned(TUnsigned val)
{
    return val & ~(1 << ((sizeof(TUnsigned) * 8) - 1));
}
amilamad
  • 470
  • 6
  • 9
0

I know this is an old question, but I think the responders may have misinterpreted it. I think what was intended was to convert a 16-digit bit sequence received as an unsigned integer (technically, an unsigned short) into a signed integer. This might happen (it recently did to me) when you need to convert something received from a network from network byte order to host byte order. In that case, use a union:

unsigned short value_from_network;
unsigned short host_val = ntohs(value_from_network);
// Now suppose host_val is 65529.
union SignedUnsigned {
  short          s_int;
  unsigned short us_int;
};
SignedUnsigned su;
su.us_int = host_val;
short minus_seven = su.s_int;

And now minus_seven has the value -7.

Steve W
  • 9
  • 2
  • 1
    It isn't clear that OP required 16 bits, only that the conversion from `unsigned` to `signed` did not go as expected. Aside from that storing an out of range unsigned value in a signed type is always implementation-defined behavior, `short` and `unsigned short` are not required to be 16 bits wide; they are required to be a _minimum_ of 16 bits wide. If you require exact widths, use an exact width type such as `int16_t` or `uint16_t`. Note that the exact width types are optional types that are nonetheless supported on common platforms. – ad absurdum Mar 19 '20 at 21:20
  • A `short` is not guaranteed to be 16 bits (although it probably will be on most platforms). If you want 16 bits, why not use the standard types `uint16_t` and `int16_t`, which ARE guaranteed to be 16 bits? Even the return type of `ntohs()` is `uint16_t`, for good reason. The union is a nice trick, but seems overly complicated. A cast/conversion from `uint16_t` to `int16_t` will have the same effect and is much more descriptive (and probably more efficient, depending on compiler and compiler options). – wovano Nov 19 '22 at 08:35
0

You are expecting that your int type is 16 bit wide, in which case you'd indeed get a negative value. But most likely it's 32 bits wide, so a signed int can represent 65529 just fine. You can check this by printing sizeof(int).

Szabolcs
  • 24,728
  • 9
  • 85
  • 174
0

To answer the question posted in the comment above - try something like this:

unsigned short int x = 65529U;
short int y = (short int)x;

printf("%d\n", y);

or

unsigned short int x = 65529U;
short int y = 0;

memcpy(&y, &x, sizeof(short int);
printf("%d\n", y);
Throwback1986
  • 5,887
  • 1
  • 30
  • 22