C# Primitive Types Part 3: Integral Types

C# Primitive Types Part 3: Integral Types

In the first part of our journey through C# primitive types, we explored the world of char and bool. Next, I introduced byte, sbyte, struct and compared value types with reference types. This time, let’s dive into integral types - those that represent whole numbers. Integral types are the backbone of many applications, providing the means to store and manipulate integers efficiently. Whether you're working with a simple counter or processing large datasets, understanding these types is crucial.


What Are Integral Types?

In C#, integral types are numeric types used to store whole numbers, both positive and negative, as well as zero. These types differ in their range, size, and whether they allow negative values.

C# provides a variety of integral types, split into two categories:

  1. Signed types: Allow both negative and positive values.

  2. Unsigned types: Only allow non-negative values.

Quick Overview

Here’s a handy summary of C# integral types:

TypeSize (bits)RangeSigned/Unsigned
sbyte8-128 to 127Signed
byte80 to 255Unsigned
Int16(short)16-32,768 to 32,767Signed
UInt16(ushort)160 to 65,535Unsigned
Int32(int)32-2,147,483,648 to 2,147,483,647Signed
UInt32(uint)320 to 4,294,967,295Unsigned
Int64(long)64-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807Signed
UInt64(ulong)640 to 18,446,744,073,709,551,615Unsigned

Signed Integral Types

Signed types allow both positive and negative values. They’re the go-to choice when you need to work with numbers that could dip below zero.

sbyte

  • Size: 8 bits

  • Range: 128 to 127

  • Use case: Rarely used directly, but useful when working with low-level data or specific byte manipulations.

short

  • Size: 16 bits

  • Range: 32,768 to 32,767

  • Use case: Suitable for memory-constrained environments when the range of int is overkill.

int

  • Size: 32 bits

  • Range: 2,147,483,648 to 2,147,483,647

  • Use case: The most commonly used integral type in C#. Perfect for counters, loops, and most numerical calculations.

long

  • Size: 64 bits

  • Range: 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

  • Use case: Ideal for scenarios requiring large numerical ranges, such as astronomy or financial calculations.


Unsigned Integral Types

Unsigned types only store positive numbers (including zero). They’re useful when you know negative values aren’t needed, which allows a broader positive range.

byte - Commonly used in graphics, file I/O, and any scenario where a single byte represents a value.

ushort - Used in networking and when working with data that doesn’t require signed numbers.

uint - Useful for applications involving large, non-negative datasets such as indexing large arrays.

ulong - Perfect for ultra-large values in fields like cryptography and big data.


When to Use Which Integral Type?

  1. Stick to int unless you have a specific reason to use another type.

    • It's the most optimized and widely supported integral type.
  2. Use smaller types (sbyte, byte, short, ushort) for memory-critical scenarios, such as embedded systems or networking.

  3. Go for larger types (long, ulong) when dealing with very large numbers, such as scientific computations or monetary systems.

  4. Prefer unsigned types when negative values are not possible, and you need the extra positive range.

A Peek Under the Hood

All integral types in C# are value types, meaning they are stored directly in the memory location where they’re declared. This makes them efficient for most operations, as there’s no additional memory overhead.

Moreover, integral types are backed by the System.Int32, System.Byte, etc., in the .NET framework. This means they come with additional functionality, such as parsing and formatting.

Funny examples from day to day tasks…

Junior lied in his CV - Annual Bonuses

HR requested an app to calculate the new annual salary for employees after applying annual bonuses. The company assigned this task to a Junior Developer named Junior. Junior asked the HR team about the highest salary in the company. He learned that the maximum salary ranges between 25k and 30k. Let’s see how he approached this task.

Junior clearly lied in his CV. After learning about the maximum value of current salaries, he vaguely recalled a conversation with a friend about the short data type and its range. His friend mentioned that short can hold integer values up to around 30,000 and slightly more.

“Cool! Let’s use short for salaries!” - Junior exclaimed confidently.

Junior then implemented a class to hold employee data, including the employee's name and salary:

After that, he agreed with the HR team that the yearly bonus would be $10,000. “Cool! That’s fine! Let’s do it!” - He said confidently.

He loaded the data from the database into his model. (Just to clarify, the code shown on the screen is simplified. I would never commit such a heresy if I spent more than 15 minutes of my priceless time on it.)

Then, he iterated through each employee and added the annual bonus to their salary.

“Great! I’m done! That was quick. This programming stuff is a piece of cake.” - Junior declared proudly.

“Ok… Let's click RUN!”. All of a sudden, a Senior Developer burst out shouting, “What the F just happened to the production database and our salaries?!” For context, his name was John.

Panic spread quickly as everybody started running queries on the database, trying to figure out what Junior had failed to notice…

Overflow of integers

When the value of a signed integer, such as short or int, exceeds its maximum value and wraps around to a negative value, this phenomenon is called integer overflow.

In most programming languages, integers have a fixed size in bits. For example:

  • A signed 16-bit short has a value range of −32,768-32,768−32,768 to 32,76732,76732,767.

  • A signed 32-bit int has a value range of approximately −2.1×109-2.1 \times 10^9−2.1×109 to 2.1×1092.1 \times 10^92.1×109.

When an operation produces a result outside this range, the value "overflows" and wraps around to the opposite end of the range due to the way binary arithmetic handles such situations. This behavior is typically a result of how integers are stored using two's complement representation.

C# uses two's complement representation for signed integers, which means:

  • Values exceeding the maximum wrap around to the minimum.

  • Values below the minimum wrap around to the maximum.

Junior Blowing Up a Small Town…

In a quaint little town, a small store offers 10 free parking spots for its clients. Normally, this is plenty. But today, business is booming, and every spot is occupied. Then, Junior arrives… Let’s see what chaos is about to unfold.

To tackle this scenario, let’s design a car parking counting system:

Looks good! We can't have negative values for free parking spots, right? Let’s run the code.

Did Junior just invent a way to solve the parking problem in every city in the world?

Like before, the value of a variable underflowed its range and skyrocketed to the opposite side. Just to be sure, let's add a Console.WriteLine(uint.MaxValue) and compare the results.

This should definitely be handled in a different way, perhaps with a simple and straightforward if statement or exception handling. But that's a topic for a different post.

Conclusion

Integral types are the cornerstone of numerical computations in C#. Whether you're building a game, processing data, or crunching numbers in a financial app, choosing the right integral type can impact both performance and memory usage.

In the next part, we’ll explore double, single and IntPtr types in C#. Until then, happy coding!

Follow for more! ;)