Introduction
This article will take you through a small journey related to basics of arithmetic overflow and underflow related to integer and long data types of .NET Framework. Knowledge of these few key details related to arithmetic overflow and underflow can save you a great deal of frustration and silent failures. Let's get started.
Prerequisites
A basic knowledge of C# language and .NET Framework from Microsoft is desired while reading this article.
A Note for Attached Code
You will need the following software to run the attached code on your computer:
- Visual Studio 2012 or above
Background
The idea for this article struck my mind when I was solving a very trivial problem given by a very good programmer friend of mine at my office. The problem was to print the successor of highest number which can be contained by integer (alias Int32
) data type in .NET Framework. What followed through were a series of surprises.
Basic Fundamentals
We will be dealing with only Integer
data type in this article, but it is equally applicable to long
data type as well without any deviation.
Note: Throughout my article, I've used int
, Integer
and Int32
interchangeably. int
is a keyword facility provided by C# compiler to integer data type as it is a primitive data type. So writing int
, Int32
and System.Int32
in code is equivalent. For System.Int64
data type, the keyword is long
.
Let's quickly answer few basic questions which might be popping up in your mind right now:
What is an integer data type? Int32
or int
or System.Int32
is a data type supported by .NET Framework to deal with computation of numbers. It can only contain integral values like 0
, 1
, 2
, -1
,-2
,-3
. No fractional numbers can be stored in a variable of integer data type. Even if you attempt to do so, the fractional part will get automatically truncated. Internally, it has memory storage size of 32 bits as is evident from its name Int32
. The highest possible value which a variable of type Int32
can contain is 2147483647
and the lowest possible value is -2147483648
.
What is arithmetic overflow? If you try assigning an integral value to a variable of type integer
which is outside the range which spans between -2147483648
and 2147483647
, it results in an arithmetic overflow error. Whenever the value being assigned to an integer variable is greater than 2147483647
or is less than -2147483648
, then the resulting value along with its sign (+/-) can not be accomodated in the 32 bit memory space assigned to the variable which results in memory overflow.
.NET Framework class library has a built-in exception class named System.ArithmeticException
to catch this overflow condition arising in code.
Here is a code snippet from the attached source code which generates ArithemticException
. Here, the exception gets raised because of arithmetic overflow happening towards negative limit of Int32 data type.
Code Snippet # 1
private static void CreateArithmeticOverflowForInteger()
{
try
{
var myNumber = int.MinValue;
var predecessorNumber = myNumber - 1;
}
catch (ArithmeticException ex)
{
Console.WriteLine(ex.ToString());
}
}
Using the Code
Getting back to my original problem statement of printing the successor of highest number which can be contained by integer data type, here is the program I wrote:
Code Snippet # 2
static void Main(string[] args)
{
PrintSuccessor(int.MaxValue);
}
private static void PrintSuccessor(int number)
{
Console.WriteLine(number + 1);
}
Here is what got printed on console: -2147483648
Highest value which can be stored by Int32
data type is 2147483647
. The number which got printed here is -2147483648
. What's going on in here? There is something wrong for sure as mathematically successor of 2147483647
(the highest value which can be stored in Int32
data type) is 2147483648
. Also, if you noticed this value which got printed on console is actually the lowest possible integer which can be stored in Int32
data type. Not to worry, all this is happening because of a C# project setting which is not enabled by default and results in this aberration. Let's check that.
Project Setting to Escape Arithmetic Exceptions
Open solution explorer. Right click on the project file "ArithmeticCalculationsNumericDataTypes
". Click properties in context menu. This will open the project properties window. You can also open this window by simply pressing alt + Enter after selecting project file in solution explorer. Now go to build tab in project properties window. Click Advanced button. Verify the value of "Check for arithmetic overflow/underflow" check box. Currently, it will be in unchecked state as shown below:
Now this particular setting being unchecked is the root cause of the aberrant behavior you are observing currently. This project setting instructs the compiler to generate MSIL which will ignore the arithmetic overflow exception. The moment you increase the value of an integer variable which goes past the positive limit of Int32
data type, the value does a roll-over and gets set to negative limit of Int32
data type which is -2147483648
. No run time exceptions will be raised at all. Just a silent failure which no one can notice. :) I sent this source code to my friend only to face embarrassment as it was producing wrong result.
An irritating limitation: Once set in project properties, I've no way to change this behavior of arithmetic overflow at run time. If this setting is checked in project properties, it will always throw exception when arithmetic overflow occurs. If this setting is unchecked in project properties, it will always eat the exception when arithmetic overflow occurs and silently roll over to lower limit or upper limit of the data type as the case may be. What is the way out? C# compiler designers are smart people. :) Let's see what they have in store for us.
Checked and Unchecked Programming Constructs
To modify/control the behavior of arithmetic overflow at run time, C# provides two programming constructs namely checked
and unchecked
keywords. Let's see them in action. Let's say at run time, you decide to enable arithmetic overflow exception. Here is a quick snippet which shows how it works:
private static void CheckedProgrammingConstruct()
{
checked
{
var myNumber = int.MaxValue;
var successorNumber = myNumber + 1;
}
}
So you can see clearly that in the last line, arithmetic overflow will happen. It will result in arithmetic exception as it is crossing the upper limit of Int32
data type irrespective of the state of the arithmetic overflow setting in your project properties since this code block is enclosed inside checked
block.
Let us now see unchecked
programming construct:
private static void UncheckedProgrammingConstruct()
{
unchecked
{
var myNumber = int.MaxValue;
var successorNumber = myNumber + 1;
}
}
So as you can see again in the last line of code, there is a likelihood of arithmetic overflow, but it will NOT result in any kind of arithmetic exception even if it is crossing the upper limit of Int32
data type. It will silently jump to the negative limit of Int32
data type irrespective of the state of the arithmetic overflow setting in your project properties since this code block is enclosed inside unchecked
block.
Who Solves My Successor Problem?
The only way to get the right result is to use the Int64
(long
) data type instead of Int32
(int
) data type. Here is the code which solves my friend's original problem:
static void Main(string[] args)
{
PrintSuccessorWithoutError(int.MaxValue);
}
private static void PrintSuccessorWithoutError(long number)
{
Console.WriteLine(number + 1);
}
Do FCL Classes Respect Your Project Settings?
This is the MOST important take away for you which is why I wrote this article as this finding had left me in a bit of amazement and gave me food for thought. Let's say you have kept the "Check for arithmetic overflow/underflow" setting unchecked in your project properties. Now how the following code snippet will behave. List<int>
is a framework class library (FCL) class which comes with .NET Framework. Here, we store two integers in a list and then ask the Sum
API of List<int>
class to sum them up. I've chosen two numbers in such a way that the summation process will result in an arithmetic overflow.
private static void BehaviorOfFclClasses()
{
var listOfNumbers = new List<int>();
listOfNumbers.Add(Int32.MaxValue);
listOfNumbers.Add(1);
var totalSumOfListItems = listOfNumbers.Sum();
}
This code results in arithmetic overflow exception irrespective of arithmetic overflow settings in project properties. This really goes in programmer's favor. The thing is it can get really chaotic if framework class libraries start eating arithmetic overflow exception under the carpet based on your project settings. If it starts happening, then they will return results like sum
in this case which will be incorrect without throwing exception. So FCL classes always use checked
keyword in their implementation wherever an arithmetic operation in involved. So don't feel dumbfounded next time if you see FCL classes not respecting your project settings for arithmetic overflow. :) They are doing it in your best interest.
So you are all set to dive into the world of arithmetic operations.
Recommendations
- Always follow the default project setting for arithmetic flow (Unchecked state). Enabling arithmetic overflow checks across the application in one shot using the project setting will have negative impact on performance. If you have specific cases where you feel you have to enable it, then do it through
checked
keyword in specific piece of code only at run time.
Points of Interest
- I've an
Int32
variable. Let's say I assign it the lowest possible value of Int32
data type. Now you have to think what arithmetic operation on the variable will cause the lower limit of Int32
data type to jump to upper limit of Int32
data type if the project setting I mentioned in this article is unchecked. - How about putting an FCL class API which does arithmetic calculation enclosed inside
unchecked
keyword. Observe its behavior. Will it throw an arithmetic exception or eat it silently?
unchecked
{
var listOfNumbers = new List<int>();
listOfNumbers.Add(Int32.MaxValue);
listOfNumbers.Add(1);
var totalSumOfListItems = listOfNumbers.Sum();
}
History
- May 5, 2016 - First release
- May 9, 2016 - Updated the article to remove the concept of underflow. The concept of underflow is not related to integer data type. Integers always overflow both on their positive and negative limits.