Representing Integers in Binary
We’re going to build upon material we talked about last time. If you have not read that one before, and especially if you are not familiar with counting in binary–then go back and read that post before reading this one. It will make a lot more sense if you do.
Binary (base-2) and other bases for number systems was mostly a mathematical toy until computers came along. Most people used the base-10 decimal system that we are all quite familiar with, and were happy.
Alas, in the very early days of computing, it became very clear that a computer would be way easier to make if every piece of hardware designed to store data could use two states instead of ten. Suddenly, what was a mathematical toy became essential to the computing world, because much that had been done in the binary number system could immediately be applied to the computing world.
If you only have two states to represent data in a computer–on and off or 1 and 0–then the binary system is a natural fit.
C# and virtually all other languages use the binary number system to represent integers in memory.
I’ll keep it simple here and look at only a single byte, but the concepts are identical if you’re using 2, 4, or 8 bytes. Those can just store much bigger numbers.
With any data type, you must come up with a way to represent the possible values using the bits that are reserved for values of the type.
The simplest example is the
bool type, which really only needs to represent two values:
C# says, “
false is assigned to the bit pattern
true is assigned to the bit pattern
00000001. While we’ll generally only use
true, if we somehow end up with any other bit pattern, such as
10000001, that will also be treated as
Other data types are not as simple. Fortunately, all of the integer types work on similar principles.
For all of the integer types, it works like this. We assign the number 0 to be the bit pattern
00000000. The number 1 is the bit pattern
00000001. Then we proceed to count in base-2/binary. The number 2 is the bit pattern
00000010. The number 3 is the bit pattern
00000011. The number 4 is
Representing Negative Values in Binary
So how do we represent negative values?
As we saw in the previous post, in the math world, we’d just slap a negative sign on the front. -101 is a valid negative base-2 number.
On a computer, however, we can’t set a bit to “negative”. We need some way to represent negative values using only zeroes and ones.
There are a lot of ways we could do this.
One simple way that is not used for integers is to just pick a single bit to be the sign bit. A 0 means positive and a 1 means negative. If we were to do this, then
00000001 would mean 1 while
10000001 would mean -1.
Floating-point types actually use this strategy, but integers do not.
Instead, integers use something much stranger. (I say “strange,” and it will be for a human. But it makes the circuitry in the hardware much simpler, which is why it is done this way.) Signed integer types will use a representation called two’s complement representation.
The highest positive number that 8 bits can store is the bit pattern
01111111, which is 127.
When you add one to this, the bit pattern does the normal thing and just rolls over to the next bit pattern, which would be
But this number represents the largest negative number the type can represent: -128, in the case of 8 bits.
And basically, numbers just go up from there. The bit pattern
10000001 represents -127. The bit pattern
10000010 is -126, and so on, until you get to
11111101 which is -3,
11111110 which is -2, and
11111111, which is -1.
When you add one more to the bit pattern, you’ve overflowed the whole type and it gets reset back to the bit pattern of
00000000, which is 0 again.
A little weird, right?
But it does make sense from the perspective of designing and building CPUs, so it is what virtually every programming langauge anywhere uses, despite the weirdness.
At any rate, all of the integer types use this general pattern. Signed types will use two’s complement to represent negative values, while unsigned types will just use that bit on the big end to represent one more place value.
Floating-point types use a very different scheme that we will save for another day.