Recipe 1.13 Safely Performing a Narrowing Numeric Cast
Problem
You need to cast a value from a larger
value to a smaller one, while gracefully handling conditions that
result in a loss of information. For example, casting a
long to an int results only in
a loss of information if the long data type is
greater than int.MaxSize.
Solution
The simplest
way to do this check is to use the checked
keyword. The following method accepts two long
data types and attempts to add them together. The result is stuffed
into an int data type. If an overflow condition
exists, the OverflowException
is thrown:
using System;
public void UseChecked(long lhs, long rhs)
{
int result = 0;
try
{
result = checked((int)(lhs + rhs));
}
catch (OverflowException e)
{
// Handle overflow exception here.
}
}
This is the simplest method. However, if you do not want the overhead
of throwing an exception and having to wrap a lot of code in
try/catch blocks to handle the
overflow condition, you could use the MaxValue and
MinValue fields of each type. A check using these
fields can be done prior to the conversion to insure that no loss of
information occurs. If this does occur, the code can inform the
application that this cast will cause a loss of information. You can
use the following conditional statement to determine whether
sourceValue can be cast to a
short without losing any information:
// Our two variables are declared and initialized
int sourceValue = 34000;
short destinationValue = 0;
// Determine if sourceValue will lose information in a cast to a short
if (sourceValue <= short.MaxValue && sourceValue >= short.MinValue)
{
destinationValue = (short)sourceValue;
}
else
{
// Inform the application that a loss of information will occur
}
Instead of placing this conditional throughout your code, you can use
the following overloaded methods to determine whether an integral
type will lose data in a cast:
// Overloaded methods to check conversions from unsigned integral types
// to any other type
public static bool IsSafeToConvert(byte valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((ulong)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(ushort valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((ulong)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(uint valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((ulong)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(ulong valueToConvert,
string typeToConvertTo)
{
bool isSafe = false;
switch(typeToConvertTo)
{
case "byte":
if(valueToConvert <= byte.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
case "sbyte":
if(valueToConvert <= (ulong)sbyte.MaxValue &&
valueToConvert >= 0)
isSafe = true;
break;
case "short":
if(valueToConvert <= (ulong)short.MaxValue &&
valueToConvert >= 0)
isSafe = true;
break;
case "ushort":
if(valueToConvert <= ushort.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
case "int":
if(valueToConvert <= int.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
case "uint":
if(valueToConvert <= uint.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
case "long":
if(valueToConvert <= long.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
case "ulong":
if(valueToConvert <= ulong.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
case "char":
if(valueToConvert <= char.MaxValue && valueToConvert >= 0)
isSafe = true;
break;
default:
isSafe = true;
break;
}
return (isSafe);
}
// Overloaded methods to check conversions from signed integral types
// to any other type
public static bool IsSafeToConvert(sbyte valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((long)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(short valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((long)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(int valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((long)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(char valueToConvert,
string typeToConvertTo)
{
return (IsSafeToConvert((long)valueToConvert, typeToConvertTo));
}
public static bool IsSafeToConvert(long valueToConvert,
string typeToConvertTo)
{
bool isSafe = false;
switch(typeToConvertTo)
{
case "byte":
if(valueToConvert <= byte.MaxValue &&
valueToConvert >= byte.MinValue)
isSafe = true;
break;
case "sbyte":
if(valueToConvert <= sbyte.MaxValue &&
valueToConvert >= sbyte.MinValue)
isSafe = true;
break;
case "short":
if(valueToConvert <= short.MaxValue &&
valueToConvert >= short.MinValue)
isSafe = true;
break;
case "ushort":
if(valueToConvert <= ushort.MaxValue &&
valueToConvert >= ushort.MinValue)
isSafe = true;
break;
case "int":
if(valueToConvert <= int.MaxValue &&
valueToConvert >= int.MinValue)
isSafe = true;
break;
case "uint":
if(valueToConvert <= uint.MaxValue &&
valueToConvert >= uint.MinValue)
isSafe = true;
break;
case "long":
if(valueToConvert <= long.MaxValue &&
valueToConvert >= long.MinValue)
isSafe = true;
break;
case "ulong":
if(valueToConvert >= 0)
isSafe = true;
break;
case "char":
if(valueToConvert <= char.MaxValue &&
valueToConvert >= char.MinValue)
isSafe = true;
break;
default:
isSafe = true;
break;
}
return (isSafe);
}
// Overloaded methods to check conversions from a float type
// to any other type
public bool IsSafeToConvert(float valueToConvert, string typeToConvertTo)
{
bool isSafe = false;
switch(typeToConvertTo)
{
case "byte":
if(valueToConvert <= byte.MaxValue &&
valueToConvert >= byte.MinValue)
isSafe = true;
break;
case "sbyte":
if(valueToConvert <= sbyte.MaxValue &&
valueToConvert >= sbyte.MinValue)
isSafe = true;
break;
case "short":
if(valueToConvert <= short.MaxValue &&
valueToConvert >= short.MinValue)
isSafe = true;
break;
case "ushort":
if(valueToConvert <= ushort.MaxValue &&
valueToConvert >= ushort.MinValue)
isSafe = true;
break;
case "int":
if(valueToConvert <= int.MaxValue &&
valueToConvert >= int.MinValue)
isSafe = true;
break;
case "uint":
if(valueToConvert <= uint.MaxValue &&
valueToConvert >= uint.MinValue)
isSafe = true;
break;
case "long":
if(valueToConvert <= long.MaxValue &&
valueToConvert >= long.MinValue)
isSafe = true;
break;
case "ulong":
if(valueToConvert <= ulong.MaxValue &&
valueToConvert >= ulong.MinValue)
isSafe = true;
break;
case "char":
if(valueToConvert <= char.MaxValue &&
valueToConvert >= char.MinValue)
isSafe = true;
break;
case "double":
if(valueToConvert <= double.MaxValue &&
valueToConvert >= double.MinValue)
isSafe = true;
break;
case "decimal":
if(valueToConvert <= (float)decimal.MaxValue &&
valueToConvert >= (float)decimal.MinValue)
isSafe = true;
break;
default:
isSafe = true;
break;
}
return (isSafe);
}
// Overloaded methods to check conversions from a double type
// to any other type
public bool IsSafeToConvert(double valueToConvert, string typeToConvertTo)
{
bool isSafe = false;
switch(typeToConvertTo)
{
case "byte":
if(valueToConvert <= byte.MaxValue &&
valueToConvert >= byte.MinValue)
isSafe = true;
break;
case "sbyte":
if(valueToConvert <= sbyte.MaxValue &&
valueToConvert >= sbyte.MinValue)
isSafe = true;
break;
case "short":
if(valueToConvert <= short.MaxValue &&
valueToConvert >= short.MinValue)
isSafe = true;
break;
case "ushort":
if(valueToConvert <= ushort.MaxValue &&
valueToConvert >= ushort.MinValue)
isSafe = true;
break;
case "int":
if(valueToConvert <= int.MaxValue &&
valueToConvert >= int.MinValue)
isSafe = true;
break;
case "uint":
if(valueToConvert <= uint.MaxValue &&
valueToConvert >= uint.MinValue)
isSafe = true;
break;
case "long":
if(valueToConvert <= long.MaxValue &&
valueToConvert >= long.MinValue)
isSafe = true;
break;
case "ulong":
if(valueToConvert <= ulong.MaxValue &&
valueToConvert >= ulong.MinValue)
isSafe = true;
break;
case "char":
if(valueToConvert <= char.MaxValue &&
valueToConvert >= char.MinValue)
isSafe = true;
break;
case "float":
if(valueToConvert <= float.MaxValue &&
valueToConvert >= float.MinValue)
isSafe = true;
break;
case "decimal":
if(valueToConvert <= (double)decimal.MaxValue &&
valueToConvert >= (double)decimal.MinValue)
isSafe = true;
break;
default:
isSafe = true;
break;
}
return (isSafe);
}
// Overloaded methods to check conversions from a decimal type
// to any other type
public bool IsSafeToConvert(decimal valueToConvert,
string typeToConvertTo)
{
bool isSafe = false;
switch(typeToConvertTo)
{
case "byte":
if(valueToConvert <= byte.MaxValue &&
valueToConvert >= byte.MinValue)
isSafe = true;
break;
case "sbyte":
if(valueToConvert <= sbyte.MaxValue &&
valueToConvert >= sbyte.MinValue)
isSafe = true;
break;
case "short":
if(valueToConvert <= short.MaxValue &&
valueToConvert >= short.MinValue)
isSafe = true;
break;
case "ushort":
if(valueToConvert <= ushort.MaxValue &&
valueToConvert >= ushort.MinValue)
isSafe = true;
break;
case "int":
if(valueToConvert <= int.MaxValue &&
valueToConvert >= int.MinValue)
isSafe = true;
break;
case "uint":
if(valueToConvert <= uint.MaxValue &&
valueToConvert >= uint.MinValue)
isSafe = true;
break;
case "long":
if(valueToConvert <= long.MaxValue &&
valueToConvert >= long.MinValue)
isSafe = true;
break;
case "ulong":
if(valueToConvert <= ulong.MaxValue &&
valueToConvert >= ulong.MinValue)
isSafe = true;
break;
case "char":
if(valueToConvert <= char.MaxValue &&
valueToConvert >= char.MinValue)
isSafe = true;
break;
default:
isSafe = true;
break;
}
return (isSafe);
}
Discussion
A
narrowing conversion occurs when a larger type
is cast down to a smaller type. For instance, consider casting a
value of type Int32 to a value of type
Int16. If the Int32 value is
smaller or equal to the Int16.MaxValue field and
the Int32 value is higher or equal to the
Int16.MinValue field, the cast will occur without
error or loss of information. Loss of information occurs when the
Int32 value is larger than the
Int16.MaxValue field or the
Int32 value is lower than the
Int16.MinValue field. In either of these cases,
the most-significant bits of the Int32 value would
be truncated and discarded, changing the value after the cast.
If a loss of information occurs in an unchecked context, it will
occur silently without the application noticing. This problem can
cause some very insidious bugs that are hard to track down. To
prevent this, check the value to be converted to determine whether it
is within the lower and upper bounds of the type that it will be cast
to. If the value is outside these bounds, then code can be written to
handle this situation. This code could force the cast not to occur
and/or possibly to inform the application of the casting problem.
This solution can aid in the prevention of hard-to-find arithmetic
bugs from appearing in your applications.
You should understand that both techniques shown in the Solution
section are valid. However, the technique you use will depend on
whether you expect to hit the overflow case on a regular basis or
only occasionally. If you expect to hit the overflow case quite
often, you might want to choose the second technique of manually
testing the numeric value. Otherwise, it might be easier to use
the
checked keyword, as in the first technique.
 |
In C#,
code can run in either a checked or
unchecked context; by default, the code runs in
an unchecked context. In a checked context, any arithmetic and
conversions involving integral types are examined to determine
whether an overflow condition exists. If so, an
OverflowException
is thrown. In an unchecked context, no
OverflowException will be thrown when an overflow
condition exists.
A checked context can be set up by using the
/checked{+} compiler
switch, by setting the Check for
Arithmetic Overflow/Underflow project property to
true, or by using the checked
keyword. An unchecked context can be set up using the
/checked- compiler switch, by setting the Check
for Arithmetic Overflow/Underflow project property to
false, or by using the
unchecked keyword.
|
|
Notice that floating-point and decimal types are not included in the
code that handles the conversions to integral types in this recipe.
The reason is that a conversion from any integral type to a
float, double, or
decimal will not lose any information; therefore,
it is redundant to check these conversions.
In addition, you should be aware of the following when performing a
conversion:
Casting from a float, double,
or decimal type to an integral type results in the
truncation of the fractional portion of this number. Casting from a float or double
to a decimal results in the
float or double being rounded
to 28 decimal places. Casting from a double to a
float results in the double
being rounded to the nearest float value. Casting from a decimal to a
float or double results in the
decimal being rounded to the resulting type (float
or double). Casting from int, uint, or
long to a float could result in
the loss of precision, but never magnitude. Casting from long to a double
could result in the loss of precision, but never magnitude.
See Also
See the "checked" keyword and
"Checked and Unchecked" topics in
the MSDN documentation.
|