Dot Net Perls

Numeric Types and Casts - C#

by Sam Allen

Problem. Your program uses numeric casts. When are these are necessary, useless, or possibly harmful? You want to know how C# casts longs to ints, for example. Solution. This article has examples of C# casts and looks into the instructions they generate.

Information: code quality and performance

Our problem here involves both correctness and efficiency. Sometimes, casting numbers is not important, but if you are working on a critical aeronautics program, you need to know what casts are done.

Example: casting different number types

Here we see the basic syntax to cast numbers to different types. This gives us some context for the rest of the article. The view of the program in the debugger is shown after the code.

using System;

class Program
{
    static void Main()
    {
        // 32-bit integer.
        int num1 = 1000;

        // Cast to long.
        long num2 = (long)num1;

        // Cast to double.
        double num3 = (double)num1;

        // Cast to float.
        float num4 = (float)num1;

        // Cast to uint.
        uint num5 = (uint)num1;

        // Cast to short.
        short num6 = (short)num1;

        // Cast to ushort.
        ushort num7 = (ushort)num1;

        // Cast to decimal.
        decimal num8 = (decimal)num1;

        // Cast to ulong.
        ulong num9 = (ulong)num8;
    }
}

We see above that each new type is converted from the first int and they are strongly typed. These are explicit casts.

Information: explicit casts vs. implicit casts

Implicit casts are those that are not declared in your code using the parenthesis syntax above. Explicit casts are those you write out in the code.

It is important to know that C# sometimes compiles implicit casts the same way as if you used the cast operators.

Numeric comparisons, such as comparing an int to a long, also cause implicit casts. In these cases, lots of computations are taking place without you seeing it in the editor.

Question: how are numeric casts performed in the compiler?

When you have a numeric cast in your C# code, a special intermediate language, IL, instruction is used: conv. Emilio Guijarro states they "convert the value at the top of the stack to the type designed in the opcode". [Type Casting Impact by Emilio Guijarro, codeproject.com]

Example: how long and int are converted in IL

Here we see that when you compare a long to an int in an expression, the C# compiler generates extra conv instructions. These add complexity to your code, regardless of performance concerns.

C# code before compilationIL generated from C# code
using System; class Program { static void Main() { long l = 1000L; int i = 1000; if (l == i) { Console.WriteLine("True"); } } }.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ( [0] int64 l, [1] int32 i) L_0000: ldc.i4 0x3e8 L_0005: conv.i8 L_0006: stloc.0 L_0007: ldc.i4 0x3e8 L_000c: stloc.1 L_000d: ldloc.0 L_000e: ldloc.1 L_000f: conv.i8 L_0010: bne.un.s L_001c L_0012: ldstr "True" L_0017: call void [mscorlib] System.Console::WriteLine(string) L_001c: ret }
• One long and one int
• Numeric suffix used
• Two conv instructions generated
• First conv instruction used to load long value
• Second conv instruction used to compare long to int
12 lines of instructions

Interestingly, we see that the long's value is loaded with a conv instruction and then an implicit conversion is done when the two variables are compared.

Numeric types such as long and int always must be converted before they are compared. Note that as Emilio states, sometimes with uint and int, for example, no conv instructions are emitted. [Link above]

Example: avoiding numeric casts

Here I show the same basic code as above, but with no numeric casts in the IL. You can see that the C# code is about the same, but the IL has two fewer instructions.

C# code before compilationIL generated after compilation
using System; class Program { static void Main() { int l = 1000; int i = 1000; if (l == i) { Console.WriteLine("True"); } } }.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ( [0] int32 l, [1] int32 i) L_0000: ldc.i4 0x3e8 L_0005: stloc.0 L_0006: ldc.i4 0x3e8 L_000b: stloc.1 L_000c: ldloc.0 L_000d: ldloc.1 L_000e: bne.un.s L_001a L_0010: ldstr "True" L_0015: call void [mscorlib] System.Console::WriteLine(string) L_001a: ret }
• Two ints• No conv instructions
10 lines of instructions: 2 fewer than before

In the example, we removed some casting between long and int and eliminated 2 conv instructions from being emitted in the IL.

Opinion: what's really important about numeric casts

Many numeric casts are safe. In one program, I casted between long and int safely. However, I found that there were several conv instructions emitted I wasn't aware of.

Implicit conversions between long and int were being performed in one of my comparison, if statement, expressions.

Using the knowledge I gained here, I was able to avoid an unnecessary cast, which ended up saving several implicit casts, reducing the IL size by almost 10%.

Question: do safe implicit casts hurt anything?

Yes, I think so. Your code is doing things you don't need or want it to do. In a critical application, you don't want uncertainty. For this reason, it is far better to carefully maintain numeric types.

Opinion: always avoid unnecessary numeric casts

Fewer instructions means fewer possible points of failure. In other words, small programs are easier to maintain than large ones.

Tip: function signatures and file sizes

File sizes in Windows are reported in longs. I have found code that casts these values to 32-bit ints, which is safe normally.

These casts, however, are always translated into conv instructions. Exceptions due to invalid casts are also possible.

Another problem I have found is methods that specify one integer type, but callers that have another integer type. Often, it is possible to simply change one method.

Information: how to use numeric suffixes in C#

Numeric suffixes, also called literal number suffixes, are hints to the compiler that a literal number is of a certain type. Recall that "literal" means a value hard-coded into your program.

My investigation revealed that literal suffixes on constants actually generate conv instructions. This means they work the same as runtime casts.

using System;

class Program
{
    static void Main()
    {
        // Use long suffix.
        long l1 = 10000L;

        // Use double suffix.
        double d1 = 123.764D;

        // Use float suffix.
        float f1 = 100.50F;

        // Use unsigned suffix.
        uint u1 = 1000U;

        // Use decimal suffix.
        decimal m2 = 4000.1234M;

        // Use unsigned suffix and long suffix.
        ulong u2 = 10002000300040005000UL;
    }
}
If you need...Specify this suffixExample
unsigned intUuint x = 100U;
longLlong x = 100L;
unsigned longULulong x = 100UL;
floatFfloat x = 100F;
doubleDdouble x = 100D;
decimalMdecimal x = 100M;

Question: are numeric suffixes uppercase or lowercase?

Both. However, Visual Studio recommends that you use uppercase casts for "l", for example. IntelliSense says: "The 'l' suffix is easily confused with the digit '1'--use 'L' for clarity."

Lowercase suffixUppercase suffix
long x = 10000l; // Is that 100001 or 10000l?long x = 10000L; // It's 10000L.

Question: what are the MaxValue and MinValues of types?

Please see my article on this subject. If you need to see the value of these, use the Locals debugging window in Visual Studio. [C# - int Max and Min Constants - dotnetperls.com]

Question: how can I convert a string into an int?

You need to use int.Parse or a similar method. These methods are not considered numeric casts or conversions, but data type conversions. See my article on the subject. [C# - Use int.Parse for Integer Conversion - dotnetperls.com]

Question: what about char?

Char in C# is defined as a Unicode character, not specifically a numeric value type. As a result, it follows different rules. You cannot compare a char to an int directly. You can cast the char to an int explicitly.

Information: conversion operators in C#

The keywords implicit and explicit are used in C# to specify the kinds of casts that are not required, or required, in the compilation process. They define a contract, but don't change runtime behavior. [Using Conversion Operators - msdn.microsoft.com]

Dot Net Perls
About
Sitemap
Integers
int Max and Min Constants
Loop Over Two Variables
Random Number Generator
Unsafe Optimization for Parsing...
Use int.Parse for Integer Conversion
New
Occurrence Count of String
StartsWith String Examples
© 2008 Sam Allen. All rights reserved.