views:

287

answers:

6

I'm a Java programmer trying to migrate to C#, and this gotcha has me slightly stumped:

int a = 1;

a = 0x08000000 | a;
a = 0x80000000 | a;

The first line compiles just fine. The second does not. It seems to recognise that there is a constant with a sign bit, and for some reason it decides to cast the result to a long, resulting in the error:

Cannot implicitly convert type 'long' to 'int'.
An explicit conversion exists (are you missing a cast?)

The fix I have so far is:

a = (int)(0x80000000 | a);

Which deals with the cast but still leaves a warning:

Bitwise-or operator used on a sign-extended operand;
consider casting to a smaller unsigned type first

What would be the correct C# way to express this in an error/warning/long-free way?

+3  A: 

Your issue is because 0x80000000 is a minus in int form and you cannot perform bitwise operations on minus values.

It should work fine if you use a uint.

a = ((uint)0x80000000) | a;  //assuming a is a uint
Nanook
`0x80000000` is already `uint`, according to the specification: http://msdn.microsoft.com/en-us/library/aa664674%28VS.71%29.aspx
Joey
That makes no difference I'm afraid.
izb
This explanation is bogus. You can perform bitwise operations on negative integers.
Eric Lippert
A: 

I think

a = (unsigned int)(0x80000000) | a;

should work. This is failing, as Nanook states, because 0x80000000 as an int is negative, and you can't perform bitwise operations on a negative signed integer. If you explicitly cast it to uint first, it should work.

Chris
Afraid that makes no difference.
izb
`0x80000000` is already a `uint`, so that cast does nothing here
Joey
Huh. I'm stumped then. What type's a?
Chris
Probably `int`, which then exhibits the conversion to `long` for the complete expression.
Joey
@Chris: he is asking for C#... "unsigned int" would be "uint" but it would still need casted back to "int" to match the type of "a"
Matthew Whited
Fair enough. That'll teach me to assume that the low level bits of C# are much the same as C and C++...
Chris
+1  A: 

Changing that line to
(int)((uint)0x80000000 | (uint)a);
does it for me.

Aaron
That seems to do the trick :)Anyone know if those casts come with a runtime cost? Or does the compiler tidy it all up sensibly?
izb
The underlying runtime system doesn't make a distinction between int and uint; it's just a bucket of 32 bits. Casts will be jitted away to nothing.
Eric Lippert
+10  A: 

A numeric integer literal is an int by default, unless the number is too large to fit in an int and it becomes an uint instead (and so on for long and ulong).

As the value 0x80000000 is too large to fit in an int, it's an uint value. When you use the | operator on an int and an uint both are extended to long as neither can be safely converted to the other.

The value can be represented as an int, but then you have to ignore that it becomes a negative value. The compiler won't do that silently, so you have to instruct it to make the value an int without caring about the overflow:

a = unchecked((int)0x80000000) | a;

(Note: This only instructs the compiler how to convert the value, so there is no code created for doing the conversion to int.)

Guffa
No, it's `uint`: http://msdn.microsoft.com/en-us/library/aa664674%28VS.71%29.aspx
Joey
Actually, that looks like the neatest fix. Thanks.
izb
And for code-tidyness because I have a lot of masks:private const int MASK_80000000 = unchecked((int)0x80000000);
izb
@Johannes: That is true. I corrected that, and also specified where the conversion to long happens.
Guffa
A: 

The problem you have here is that

  • 0x80000000 is an unsigned integer literal. The specification says that an integer literal is of the first type in the list (int, uint, long, ulong) which can hold the literal. In this case it is uint.
  • a is probably an int

This causes the result to be a long. I don't see a nicer way other than cast the result back to int, unless you know that a can't be negative. Then you can cast a to uint or declare it that way in the first place.

Joey
+5  A: 

I find it interesting that in all these answers, only one person actually suggested doing what the warning says. The warning is telling you how to fix the problem; pay attention to it.

Bitwise-or operator used on a sign-extended operand; consider casting to a smaller unsigned type first

The bitwise or operator is being used on a sign-extended operand: the int. That's causing the result to be converted to a larger type: long. An unsigned type smaller than long is uint. So do what the warning says; cast the sign-extended operand -- the int -- to uint:

result = (int)(0x80000000 | (uint) operand);

Now there is no sign extension.

Of course this just raises the larger question: why are you treating a signed integer as a bitfield in the first place? This seems like a dangerous thing to do.

Eric Lippert