C# Numeric Casts

A numeric cast changes a number's representation. Sometimes numeric casts are necessary in C# programs. Sometimes they are not. We further investigate numeric casts. We show their syntax and their IL.

Numeric types in debugger

Example

C# programming language

First, 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 above.

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

Int
C# program that performs explicit casting

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;
    }
}

Explicit, implicit

Cover logo

Implicit casts are not declared in your code using the parenthesis syntax above. Explicit casts are those you write out in the code. The C# compiler sometimes handles implicit casts the same way as if you used the cast operators.

Tip:Numeric comparisons, such as comparing an int to a long, also cause implicit casts.

Note:In these cases, lots of computations are taking place in our code that we do not see.

Long, int

Long type

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. It is important to realize this happens.

Long
C# program that converts long and int

using System;

class Program
{
    static void Main()
    {
	long l = 1000L;
	int i = 1000;

	if (l == i)
	{
	    Console.WriteLine("True");
	}
    }
}

Intermediate language after compilation

.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
}
Abstract squares

In this example, two conv instructions are generated. The first conv instruction is used to load long value. The second conv instruction is used to compare long to int. There are 12 lines of instructions.

Here:The long's value is loaded with a conv instruction. Then an implicit conversion is done when the two variables are compared.

Note:Numeric types (long, int) always are converted before compared. Sometimes with uint and int no conv instructions are emitted.

Avoid numeric cast

Programming tip

Numeric casts are not always needed. Here we see 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.

Intermediate Language
C# program that does not use casts

using System;

class Program
{
    static void Main()
    {
	int l = 1000;
	int i = 1000;

	if (l == i)
	{
	    Console.WriteLine("True");
	}
    }
}

Intermediate language after compile

.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
}
Program

In the example, we removed some casting between long and int and eliminated two conv instructions from being emitted in the IL. Note that the semantics of the program also changed and restrict the possible values of the long type.

In one program, I cast safely between long and int. I found that there were several conv instructions emitted. Implicit conversions between long and int were being performed in one of my comparison, if-statement, expressions.

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

Safe?

Question

Do safe implicit casts cause any harm to programs? 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.

Tip:Most of the time, implicit casts are not worth worrying about—but they are always worth understanding.

File size

Note

File sizes in Windows are reported in longs. Code that casts these values to 32-bit ints is normally safe. These casts, however, are always translated into conv instructions. Exceptions due to invalid casts are also possible.

Also:Sometimes methods specify one integer type, but callers have another integer type. Often it is possible to simply change one method.

Number suffix

The C# Programming Language: C-Sharp

Suffixes in the C# programming language refer to the characters such as L or UL on the end of literal numbers. These force the compiler to treat the value as a number of a certain type.

Otherwise:The compiler will use static analysis to try to infer the type of the number, or simply raise a warning.

Suffix

String, int

Convert or change

To convert or parse a string into an integer, you need to use int.Parse or a similar method. These methods are not considered numeric casts or conversions, but data type conversions. Please see the article on the subject.

int.Parse for Integer ConversionChar type

Char:In the C# language, char is defined as a Unicode character, not specifically a numeric value type. It follows different rules.

And:You cannot compare a char to an int directly. You can cast the char to an int explicitly.

Char

Operators

Operator keyword

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.

ImplicitExplicitUsing Conversion Operators: MSDN

Checked

Check

The checked keyword is available to prove in your program that the numbers you are using are not overflowing or being truncated in any way. This can improve the correctness of certain algorithms.

Info:Checked provides a way to more easily detect when a number type is not large enough to store the required value.

Checked

MaxValue, MinValue:Each numeric type has a maximum and minimum value. Please see my article on this subject.

int.MaxValue

Summary

The C# programming language

Here we saw examples and notes about numbers such as integers and doubles in the C# language. Overall, C# has performance with numeric types in the same ballpark as C++ and other compiled languages.

But:You should be mindful of implicit casting, which occurs when you might not at first expect.


C#: Number