tags:

views:

550

answers:

7

When you attempt to declare an unsigned variable in C#.NET with a value outside its value range it is flagged as a compiler error, but if you produce a negative value at runtime and assign it to that variable at runtime the value wraps.

uint z = -1; // Will not compile

uint a = 5;
uint b = 6;
uint c = a - b; // Will result in uint.MaxValue

Is there a good reason why unsigned variables wrap in such a situation instead of throwing an exception?

Thanks.

A: 

I'll leave it to someone else to explain why this happens, but in this case you should check that b <= a and throw your own exception, or use a long instead if you can.

Jon B
If you want to throw an exception, running the code in a checked context is probably a simpler and more reliable way of doing so.
Jon Skeet
I learned something new today :)
Jon B
+24  A: 

Declaring an unassigned variable in C# isn't flagged with an error - trying to assign an invalid value to a variable is. For instance, here's a variable which isn't definitely assigned (assuming it's local) after declaration:

uint z;

-1 isn't a valid value for a uint any more than 0.5 is, which is why your example wouldn't compile.

Now, as for the rest: integers types just wrap on overflow - just as adding 1 to int.MaxValue returns int.MinValue. This is a significant performance improvement over having the program check each operation for overflow - at the cost of potentially not spotting an error.

That's only if you're in an unchecked context, mind you - if you perform any of these operations in a checked context, you'll get an exception instead. For instance;

class Test
{
    static void Main()
    {
        checked
        {
            uint a = 5;
            uint b = 6;
            uint c = a - b;
        }
    }
}

Run that and you'll see an OverflowException get thrown. If that's what you want for your whole project, you can set it in the project properties (or compile with the /checked+ command line option to csc.)

EDIT: It's worth noting that the other answers have shown that you could put smaller amounts of code in the checked context - just the declaration and assignment of c or even just the calculation. It's all pretty flexible.

Jon Skeet
Darm - pipped to it... I hit "Post" to find you've already got +5... [delete...]
Marc Gravell
That's a scarily quick set of votes. It's not like it's been sitting around for a while...
Jon Skeet
Surprisingly I have never encountered the checked and unchecked contexts. This is great.
vanslly
BTW, Jon - you just sold another copy of your book
Jon B
Similarly you can use an explicit unchecked block and cast -1 to a uint if you really really want to. i.e. unchecked{ uint z = (uint)-1; }
Gerald
Are there performance hits on having large code blocks in a checked context?
vanslly
Yup, checked contexts are more expensive, certainly. Whether the performance hit is significant or not will entirely depend on your application though. I've seen advice of "debug with checked; run with unchecked".
Jon Skeet
@JonB: Cool - hope you like it :)
Jon Skeet
+7  A: 

The wrapping is because by dfault C# is unchecked. If you add a "checked" block, the overflow will be detected:

    uint a = 3, b = 4;
    checked
    {
       uint c = a - b; // throws an overflow
    }

As for the compiler: it simply demands valid data.

Marc Gravell
I'm used to a checked environment, so looked on MSDN, and this states that checked is the default? - http://msdn.microsoft.com/en-us/library/74b4xzyw(VS.85).aspx
MattH
+2  A: 

This is just the default - you can change the compilation settings to enable runtime arithetic overflow checks. If you turn that on, exceptions will be thrown as you are expecting. Alternatively, you can turn on checking for a specific operation by placing it inside a checked block.

uint c = checked(a - b);
Dave Cluderay
A: 

Probably worth clarifying here that uint is an unsigned int, not an unassigned int. Big difference!

Andrew Watt
A: 

The reason it wraps to negative is because of Two's Complement math that is used for integer arithmetic.

The short answer is that the most significant bit is used as the sign. A zero is positive; a one is negative.

Robert
uint = unsigned int
eplawless
A: 

Always use double. The performance difference is insignificant, and it is more flexible and accommodating. This practice prevents exceptions and reduces development time. Anal people hate it though.

LonnieBest