The other day, I played around with the Fibonacci sequence and overflowed my
int
variables in surprisingly low iterations. Did you know you’ll overflow an integer in **48
** iterations? Don’t believe me? Let’s take a look at the result of the code you’ll see later in this post.
You’ll notice that the last value has looped back to a negative value. Oh no! This is a tell-tale sign that an overflow has occurred.
This post will explore how to keep your applications safe from these overflow issues.
The code causing the overflow
First, let’s look at my implementation of the Fibonacci sequence generator. I’m using
Spectre.Console
to make the output more appealing, but it’s optional. I’m also using the
System.Numerics
namespace, which will allow you to experiment with different number types in .NET.
Neat! Now, why does the issue occur in the first place?
Unchecked Arithmetic
By default, .NET performs
unchecked
arithmetic operations. That means the runtime assumes you know what you’re doing when adding one variable to another. This is true in most day-to-day cases, as the scale of numbers most developers deal with is on the lower side of the maximum
int
and long
values.
For integers, the max value is 2147483647
, and it is doubled to 4294967295
if you drop the sign
—/+
from the value. For a long, the max value is
9223372036854775807
, which is quite a lot bigger but not infinite. If your application increments values indefinitely, you will run into overflow issues, and no known type will save you. It’s just math!
As you’ve seen previously, the issue is that the numbers will loop around from positive to negative values and back. Your application will continue to “work” but likely will be nonsensical and wrong.
How do we fix this issue? Well, there are two ways.
The checked keyword
The first and most precise way to fix this issue is to use the
checked
keyword. Using this keyword creates a scope where arithmetic overflows will trigger an OverflowException
.
Rerunning the same code results in the following exception.
Nice. This technique is very selective and changes the intermediate language to call the
op_CheckedAddition
method instead.
// [40 13 - 40 46]
IL_003f: ldloc.0 // fib
IL_0040: ldloc.1 // i
IL_0041: ldloc.0 // fib
IL_0042: ldloc.1 // i
IL_0043: ldc.i4.1
IL_0044: sub.ovf
IL_0045: ldelem !!0/*T*/
IL_004a: ldloc.0 // fib
IL_004b: ldloc.1 // i
IL_004c: ldc.i4.2
IL_004d: sub.ovf
IL_004e: ldelem !!0/*T*/
IL_0053: constrained. !!0/*T*/
IL_0059: call !2/*T*/ class [System.Runtime]System.Numerics.IAdditionOperators`3<!!0/*T*/, !!0/*T*/, !!0/*T*/>::op_CheckedAddition(!0/*T*/, !1/*T*/)
IL_005e: stelem !!0/*T*/
Cool! Problem solved, but what if we want all our arithmetic checked?
Project Setting for Checked Arithmetic
You can add the
CheckForOverflowUnderflow
property in your project settings, and it will change all supported arithmetic operations to use checked operations.
Easy peasy. Running the original code results in the same System.OverflowException
.
To opt out of checking, you can use the converse unchecked
keyword.
Conclusion
We can sometimes take for granted arithmetic operations and assume they will work. In most cases, they will, but if you’re building mission-critical applications that could run into underflow and overflow situations, it’s better to check these values so as not to cause potential headaches down the line.
I hope you enjoyed this post, and as always, thanks for reading.