Numeric Types

Numeric Types

C# has a number of predefined numeric types. The names and details of which can be found in the table below.

Type Description Minimum Maximum Bits
byte Unsigned Byte 0 255 8
sbyte Signed Byte -128 127 8
short Signed Short Integer -32768 32767 16
ushort Unsigned Short Integer 0 65535 16
int Signed Integer -2147483648 2147483647 32
uint Unsigned Integer 0 4294967295 32
long Signed Long Integer -9×1018 9×1018 64
ulong Unsigned Long Integer 0 1.8×1019 64
float Single Precision Number ±1.5×10-45 to ±3.4×1038 7 digits 32
double Double Precision Number ±5×10-324 to ±1.7×10308 15 or 16 digits 64
decimal Decimal Number ±10-28 to ±7.9×1028 28 or 29 digits 128

From the table sbyte, short, int and long are all known as signed integral types. Signed means that the numbers can have a negative value but at the expense of some range. The first bit indicates positive or negative.

Byte, ushort, uint, and ulong are all unsigned integral types and the only difference is these type can only take on a positive form but have additional range. The bit that would be used to represent positive/negative is used for range.

The last set of numbers are referred to as floating-point numbers, these include float, double and decimal, usually, these types of numbers are more useful for scientific and financial calculations as they can have numbers after the decimal point.

Numeric Literals

There are two different ways to represent the value in an integral type literal, these are the decimal (base-10) number system and the hexadecimal (base-16) number system.

int a = 32;
int b = 0x20; //HEX represents the decimal value 32

long c = 2147483648; //One more than the maximum value for an int
long d = 0x80000000; //HEX represents the decimal value 2147483648

//C# 7 supports underscores for readability
long e = 1_000_000_000_000;

//C# 7 Also supports specifying numbers in binary
long f = 0b0101_0111_0001_1100_0011_1010;

To declare a real literal you can either use a decimal or exponential notation, examples are below:

double g = 1.9;
double h = 1E06;

The compiler will attempt to infer numeric literals into integral or double types. It does this by checking to see if the literal contains ‘E’ for exponential otherwise it will check to see if the literal can fit into the types, starting with the smallest types.

var type = 1.0.GetType();         //Double or double
var type = 2E04.GetType();        //Double or double
var type = 1.GetType();           //Int32 or int
var type = 0x100000000.GetType(); //Int64 or long

Hexadecimal

Hexadecimal is not that unlike the decimal number system we are used to, the only difference being instead of using the numbers 0-9 in each position, we can use 0-F.

https://en.wikipedia.org/wiki/Hexadecimal

Hex vs base 10 numbers

When we think of the number 456 in the decimal number system, we know this is:

Hex numeric types

It is a similar principle, however we use 16 as a base.

Converting the hexadecimal number 0xBCE to decimal would look like the following

Hex to dec conversion

Numeric Suffixes

If you wish to explicitly define the type of literal you want to use then you can use a suffix. It does not matter if the suffix is uppercase or lowercase.

Numeric type suffixes

From this list, the F and M suffixes are the most significant as they are the hardest for the compiler to automatically infer.

float f = 2.35f;        //Otherwise would infer double
decimal d = 2384.234M   //Otherwise would infer double

Numeric Type Conversion

If the destination type can represent all possible values that the source type can handle then an implicit conversion will occur. For example, if you wished to convert from int to long then the compiler knows it will be able to handle this is in all situations.

If the destination type cannot support all possible values of the source type then an explicit conversion will need to be used, you will need to use the cast operator.

int toConvert = 50;
long destination = toConvert;                   //Implicit conversion
short explicitDestination = (short) toConvert;  //Explicit conversion

float floatingPointToConvert = 1.0f;
double floatingPointDestination = floatingPointToConvert; //Implicit

int integralToFloat = 1;
float floatDestination = integralToFloat; //int to float is implicit

float floatToIntegral = 1.0000000000000001f;
int integralDestination = (int) floatToIntegral; //Explicit Conversion

When you make an explicit conversion from a floating-point type to an integral type you will lose information as there is truncation. You will lose the numbers after the decimal point, which are also not rounded.

Arithmetic Operators

With the exception of the byte/sbyte and short/ushort integral types, all numeric types support the following arithmetic operators.

It is worth noting that division on Integral types will always round down and truncate any remainders. There is a special type of error when you try to divide a number by 0, this is called a DivideByZeroException.

+    Addition
e.g. int a = 4 + 6;

-    Subtraction
e.g. int b = 11 - 5;

*    Multiplication
e.g. int c = 4 * 9;

/    Division
e.g. int d = 8 / 2;

%    Remainder after Division
e.g. int e = 10 % 3;

Incrementing and Decrementing

If it is only required that a number need to be incremented or decremented by one then there is a shorthand notation for this. This notation is using the ++ or — operators.

//To add 1 to a number
int a = 1;

Console.WriteLine(++a); //a == 2 Outputs 2
Console.WriteLine(a++); //a == 3 Outputs 2

Console.WriteLine(--a); //a == 2 Outputs 2
Console.WriteLine(a--); //a == 1 Outputs 2

Overflow on Integral Types

As Integral types are effectively a number of bits used to represent a value, it is possible that an operation may exceed the maximum range of this representation. Take an unsigned byte which has 8 bits.

Overflows on integer types

The maximum number that can be represented is 255. If we go to add 1 to this value, we will not encounter an error but instead will experience called wrap around. The compiler expects that the type has more bits and will perform the operation as so. This will result in the following.

More overflow

As we only have 8 bits, the number will actually be 0 (the eight 0s).

The same effect can be seen when you subtract from the minimum value represented by an integral type, the value will wrap around to the max representation. To get the maximum and minimum values represented by a type.

<integralType>.MinValue; //Minimum Value
<integralType>.MaxValue; //Maximum Value

Checking for Overflows

For safety, you can check to see if an overflow is going to occur on the operation and generate an OverflowException if it does. To do this wrap your operations in the checked operator like so.

int total = checked(a * b);

// Or use it as a block
checked
{
    int total = a * b;
}

The checked statement is relevant when using the ++, –, +, – * and / operators.

Non-runtime expressions are always checked by the compiler during compilation. If you exceed the maximum value of an integral type during compilation – the compiler will warn you.