Getting CSharper #4: Working with numbers

Continuing our road through C#, that already covered a basic C# program and how types are used in C#, now we will work with numeric types that, together with string and arrays, are the most used types in C#.



Built-in numeric types

We saw in the last post that C# has some predefined types to represent numbers. To represent integers, C# has 8 types, sbyte, short, int, long and their respective unsigned versions, byte, ushort, uint and ulong. They can be used to represent numbers in the following sizes, 8 bits(sbyte), 16 bits(short), 32 bits(int) and 64 bits(long). When we need represent real numbers, C# gives to us 3 options. Float and double are normally used for scientific calculations while decimal is normally used for financial calculations. The table below describes each one of these types, their sizes and their ranges.

Type
Size (bits)
Range
sbyte8-2^7 to 2^7 - 1
byte80 to 2^8 - 1
short16-2^15 to 2^15 - 1
ushort160 to 2^16 - 1
int32-2^31 to 2^31 - 1
uint320 to 2^32 - 1
long64-2^63 to 2^63 - 1
ulong640 to 2^64 - 1
float32-3.4 x 10^38 to 3.4 x 10^38
double64±5.0 × 10 ^ −324 to ±1.7 × 10 ^ 308
decimal128-7.9 x 10^28 to 7.9 x 10^28



Declaring Numbers

Numbers in C# can be declared using decimal, hexadecimal or exponential notation.

int decimalNotation = 10; // 10
int hexadecimalNotation = 0xA; // 10
double exponentialNotation = 1E2; // 100



Type Inference

When we are declaring a number, the compiler always tries to infer the type of a given number is. If a given number contains a decimal point or an exponential notation (E), the given number is a double. Otherwise, the compiler tries to find a integer type that can represent the given number, the following order is used: int, uint, long and ulong.

1.0                  // double
1.25                 // double
1E2                  // double
1                    // int
10000000000000000000 // ulong

When declaring a number, we can also use numeric suffixes (U, L, UL, F, D, M) to force the compiler to infer a given type.

1U // uint
1L // long
1UL // ulong
1F // float
1D // double
1M // decimal



Type Conversions

Numeric conversions in C# can be implicit or explicit. If the destination type can represent all the possible values that a given type can, an implicit conversion can be executed. Otherwise, if a given type can represent more values that the destination type a cast (explicit conversion) have to be performed.

For example, when converting from short to int, we can make an implicit conversion, since an int can represents all the possible values that a short can represent. Otherwise, when converting from long to int, an explicit conversion is required, with possible losses, since a long can represent more values that an int.

Between float and double the same thing happens, you can make an implicit conversion from a float to a double, and have to make an explicit conversion when converting from a double to a float.

When converting to a decimal, an exception exists, all integer values can be converted implicitly to a decimal. Otherwise, a explicit conversion is required.

short shortNum = 1;
int intNum = 1;
long bigLongNum = 1100000000000000000;
long smallLongNum = 1;

intNum = shortNum;                 // 1 - implicit conversion
intNum = (int)smallLongNum;        // 1 - explicit conversion
intNum = (int)bigLongNum;          // 82706432 - explicit conversion with loss

float floatNum = 1.0F; 
double smallDoubleNum = 2.0;
double bigDoubleNum = 10E200;

smallDoubleNum = floatNum;         // 1.0 - implicit conversion
floatNum = (float)smallDoubleNum;  // 1.0 - explicit conversion
floatNum = (float)bigDoubleNum;    // +infinite - explicit conversion with loss

decimal decimalNum = 0;

decimalNum = 1U;                   // 1
decimalNum = 1;                    // 1
decimalNum = 1L;                   // 1
decimalNum = 1UL;                  // 1
decimalNum = (decimal) 1F;         // 1
decimalNum = (decimal) 1D;         // 1



Operators

C# has the same basic operators than other languages (+,-,/,*) and due to being a C-Like, it also has unary operators with before and after increments/decrements.

int a = 3, b = 2;

Console.WriteLine(a + b); // 5
Console.WriteLine(a - b); // 1
Console.WriteLine(a/b);   // 1 - divisions between integers are truncated
Console.WriteLine(a*b);   // 6
Console.WriteLine(a++);   // 3 - increment after to print the number
Console.WriteLine(++a);   // 5 - increment before to print the number
Console.WriteLine(b--);   // 2 - decrement after to print the number
Console.WriteLine(--b);   // 0 - decrement before to print the number



Double or Decimal

Decimal should be used when accuracy is important, money for example. Otherwise, we can use double or float. A common mistake when learning C# is to use double to represent financial values, there is a reason to avoid it, float and double are represented internally in base 2, that is, it can only represent precisely base 2 numbers, while decimal is represented in base 10, that is, it can represent floating point numbers more precisely but there is a cost, performance.

double doubleA = 0.1F;
double doubleB = 1F;

Console.WriteLine(doubleB - 10 * doubleA);   // -1,49011611938477E-08
Console.WriteLine(doubleB + 10 * doubleA);   // 2,00000001490116

decimal decimalA = 0.1M;
decimal decimalB = 1M;

Console.WriteLine(decimalB - 10 * decimalA); // 0,0
Console.WriteLine(decimalB + 10 * decimalA); // 2,0



See also other posts from this series

Getting CSharper #1: A short introduction to C#
Getting CSharper #2: Understanding a C# Program
Getting CSharper #3: Understanding C# Types

Leave a Reply

Your email address will not be published. Required fields are marked *