Trying to study how C# reacts to overflows, I wrote this simple code :
static uint diff(int a, int b)
{
return (uint)(b - a);
}
static void Main(string[] args)
{
Console.Out.WriteLine(int.MaxValue);
uint l = diff(int.MinValue, int.MaxValue);
Console.Out.WriteLine(l);
Console.In.ReadLine();
}
I get this output :
2147483647
4294967295
I find surprising that it ran so well, as the int subtraction in diff
should give a result greater than int.MaxValue
.
However, if I write this, which seems to be equivalent to the above code :
uint l = (uint)(int.MaxValue - int.MinValue);
C# won't even compile, because the code could overflow.
What makes the first code run without overflow, while the compiler won't even compile the second line ?
When you use constant values:
uint l = (uint)(int.MaxValue - int.MinValue);
The compiler knows exactly what you're trying to do because it knows the values, and it sees that the result of the subtraction cannot fit in an int
, so it gives you the error.
When you use variables:
return (uint)(b - a);
The compiler has no idea at compile time what the values of the variables will be, so it doesn't complain.
Note that the overflow is in the int
, not uint
as you stated in your now-deleted answer. You may think that you are subtracting big value from small value, but you're not. The int.MinValue
is actually negative (-2147483648) and subtracting it means you're actually adding it (2147483647 - (-2147483648)), so the result (4294967295) cannot fit in int
, but it can fit in uint
. So for example, this will compile and give the correct result 4294967295:
uint x = (uint)((long)int.MaxValue - int.MinValue);
Because now you're telling the compiler to store the result of the subtraction in long
instead of int
and that will work. Now print x
to the console and notice that the result of the subtraction is 4294967295, not -1 as you're stating in your answer. If it was -1 like you said, then the code below should compile, but it doesn't because 4294967295 overflows the int
:
int x = int.MaxValue - int.MinValue;
Edit There are more people who are struggling to understand the result, so here is even more explanation that I hope will help:
First of all, we all know that int.MaxValue
is 2147483647 and int.MinValue
is -2147483648. I hope that we also all agree on this simple math without having to prove it in a program:
2147483647 - (-2147483648) = 4294967295
So we should all agree that the math result is 4294967295, not -1. If somebody does not agree with that, please go back to school.
So why the result in the program is sometimes -1 which is confusing so many people?
OK, we all agree that an overflow occurs, so that's not the issue. Some people don't understand where the overflow is happening. It is not happening when casting to uint
. Surely -1 will overflow uint
, but the program is overflowing the int
in the step before casting to uint
. When an overflow occurs, the program behavior varies depending the on the execution context (checked or unchecked). In a checked context, an OverflowException
is thrown, and nothing is executed afterwards, so the casting to uint
is not executed. In an unchecked context, the most significant bits of the result are discarded and execution continues, so the casting to uint
is then executed and another overflow occurs. Here is an MSDN article about how the integer overflows behave.
So let's see how we're getting -1:
First, in C#, when you subtract two integers, the result is an integer. Now if the result cannot fit in an integer, an overflow occurs. The tricky part is that in an unchecked context, the most significant bits of the result are discarded, like I mentioned above. In the question's scenario, that results in -1. Here are some examples that I hope will make this clear:
Console.WriteLine(unchecked(int.MaxValue)); //Result 2147483647
Console.WriteLine(unchecked(int.MinValue)); //Result -2147483648
Console.WriteLine(unchecked(int.MaxValue-int.MinValue)); //Result -1 overflow
Console.WriteLine(unchecked(2147483647-(-2147483648))); //Same as above
Console.WriteLine(unchecked(int.MaxValue+int.MinValue)); //Result -1 no overflow
Console.WriteLine(unchecked(2147483647+(-2147483648))); //Same as above
Console.WriteLine(unchecked(int.MaxValue+1)); //Result -2147483648 overflow
Console.WriteLine(unchecked(2147483647+1)); //Same as above
Console.WriteLine(unchecked(int.MaxValue-int.MaxValue)); //Result 0
Console.WriteLine(unchecked(2147483647-2147483647)); //Same as above
Console.WriteLine(unchecked(int.MaxValue+int.MaxValue)); //Result -2 overflow
Console.WriteLine(unchecked(2147483647+2147483647)); //Same as above
The results of these examples should be clear. I'm not doing any casting here to avoid the argument about where the overflow is happening, so it is clear that it is happening in the int
. Every time an overflow happens, it is as if the first int
is assigned the value of int.MinValue
which is -2147483648 and then the second int
is added/subtracted.
If you cast the first number to long
, then the result will be long
. Now no overflow will occur and you will get the same results as in math:
Console.WriteLine((long)int.MaxValue); //Result 2147483647
Console.WriteLine((long)int.MinValue); //Result -2147483648
Console.WriteLine((long)int.MaxValue-int.MinValue); //Result 4294967295
Console.WriteLine((long)2147483647-(-2147483648)); //Same as above
Console.WriteLine((long)int.MaxValue+int.MinValue); //Result -1
Console.WriteLine((long)2147483647+(-2147483648)); //Same as above
Console.WriteLine((long)int.MaxValue+1); //Result 2147483648
Console.WriteLine((long)2147483647+1); //Same as above
Console.WriteLine((long)int.MaxValue-int.MaxValue); //Result 0
Console.WriteLine((long)2147483647-2147483647); //Same as above
Console.WriteLine((long)int.MaxValue+int.MaxValue); //Result 4294967294
Console.WriteLine((long)2147483647+2147483647); //Same as above
And here is the evidence without using any addition/subtraction. Simply casting a value above the int.MaxValue
causes an overflow which unchecked
converts to int.MinValue
. Any value greater than int.MaxValue + 1
will be added to int.MinValue
:
Console.WriteLine(unchecked((int)2147483647)); //Result 2147483647
Console.WriteLine(unchecked((int)2147483648)); //Result -2147483648 overflow
Console.WriteLine(unchecked((int)2147483649)); //Result -2147483647 overflow
Console.WriteLine(unchecked((int)2147483649)); //Result -2147483647 overflow
Console.WriteLine(unchecked((int)2147483650)); //Result -2147483646 overflow
Console.WriteLine(unchecked((int)2147483651)); //Result -2147483645 overflow
The exact opposite happens when you overflow the int
with values below int.MinValue
:
Console.WriteLine(unchecked((int)-2147483648)); //Result -2147483648
Console.WriteLine(unchecked((int)-2147483649)); //Result 2147483647 overflow
Console.WriteLine(unchecked((int)-2147483650)); //Result 2147483646 overflow
Console.WriteLine(unchecked((int)-2147483651)); //Result 2147483645 overflow
This makes int
work like an infinite rotating spinner. The two end are glued next to each other, so when you reach one end, you flip to the other one and continue 1 2 3 1 2 3 1 2 3.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With