Not just 10

Binary numbers, hexadecimal numbers, we all heard of it or even saw them. But how do you count binary, or even calculate with them?

Base numbers

Decimal system

A very well-known system is the decimal system, which everyone has learned in school, where counting is as simple as one, two, three. We were told that “deca” is Latin for ten, and we form any number with one of 10 single digit numbers: 0, 1, 2, …, 9. Hence the name “decimal” system.

We were also thought that for example the number 2345 consists of

  • 5 units (or 5 x 1)
  • 4 tens (or 4 x 10)
  • 3 hundreds (or 3 x 100)
  • 2 thousands (or 2 x 1000)

Add that up by 2000 + 300 + 40 + 5, we get 2345. Neat.
At a later age, the maths teacher explained it is nothing more than a simple mathematical formula

(2 * 103) + (3 * 102) + (4 * 101) + (5 * 100)

Since 10 is the “base” for our decimal system, each number gets multiplied by “10 to the power of n”, or 10n, with n being the position of the number, starting from position 0 at the far right. Finally, all numbers are added up, or to put that in a general formula
 

(z * 10n) + … + (y * 102) + (x * 101) + (w * 100)

Binary system

So now that you can count to 10 in a “deca”-cimal system, let’s learn to count to 2 in a “bi”-nary system. Just as 10 is our base number for the decimal system, 2 is the base number for the binary system, and as you may have guessed, “bi” means two. When looking at a binary number, for example 1101, it can be noted in a mathematical formula as

(1 * 23) + (1 * 22) + (0 * 21) + (1 * 20)

which equals to (1 * 8) + (1 * 4) + (0 * 2) + (1 * 1) = 13
So binary 1101 equals to 13 in our decimal system.

Hexadecimal system

If you know that “hexa” means 16, you probably figured out by now that 16 is the base for the hexadecimal system, so to calculate for example 23 to a decimal value

(2 * 161) + (3 * 160) = (2 * 16) + (3 * 1) = 35

So hexadecimal 23 equals 35 in our decimal system.

Notations

So what if you see the number 10. Does it equal to 10, 2 or 16. Depending how you look at it, all the answers are right.

  • (1 * 21) + (0 * 20) = (1 * 2) + (0 * 1) = 2 if 10 is a binary number
  • (1 * 101) + (0 * 100) = (1 * 10) + (0 * 1) = 10 if 10 is a decimal number
  • (1 * 161) + (0 * 160) = (1 * 16) + (0 * 1) = 16 if 10 is a hexadecimal number

As you can see, working with different system can become very confusing very fast. So this is why there are notations. For a decimal system, just write down the number as is. For the hexadecimal system, there are a few options to write the number 23

  • 0x23 (most commonly used)
  • h23
  • (16)23 (rarely used)

For the binary system, the number is noted in bytes, and usually split with a space every 4 bits.
For example: 1001 1010

What about 0xAF

When looking at hexadecimal numbers, you may have noticed that it’s not all 0 to 9 but sometimes also contains letters A to F. Since our decimal system has only 10 numbers to represent a single digit, there’s a need for presenting numbers 11, 12, 13, 14 and 15 in the hexadecimal system. This is done by using the first letters of the alphabet A, B, C, D, E and F. To convert the number 0xAF, the math remains the same
(A * 161) + (F * 160) ; with A being 10 and F being 15
= (10 * 16) + (15 * 1) = 160 + 15 = 175
Yes, hexadecimal AF is equal to 175 in the decimal system.

Hex vs Bin vs Dec

Here’s a handy conversion table

Dec Hex Bin
0 0x0 0000
1 0x1 0001
2 0x2 0010
3 0x3 0011
4 0x4 0100
5 0x5 0101
6 0x6 0110
7 0x7 0111
8 0x8 1000
9 0x9 1001
10 0xA 1010
11 0xB 1011
12 0xC 1100
13 0xD 1101
14 0xE 1110
15 0xF 1111

More binary

It takes a little practice, but if you regularly write a program, especially in microcontrollers, PLCs, … it may be handy to know the power of 2 by hard. That way you can very easily convert a binary number to a decimal number, and even a hexadecimal number, since 0x0 to 0xF corresponds with 0000 to 1111 (or 0 to 255). So 0x8F is in binary 1000 1111. For this part, just look at the table above. Knowing the power of 2 by hard, you can easily convert to decimal by 1 + 2 + 4 + 8 + 128 = 143.

Let’s take a look at that number bit by bit

Binary number 1 0 0 0 1 1 1 1  
Bit number (n) 7 6 5 4 3 2 1 0  
2n 128 64 32 16 8 4 2 1  
  1 * 128 0 * 64 0 * 32 0 * 16 1 * 8 1 * 4 1 * 2 1 * 1  
Sum 128 0 0 0 8 4 2 1 = 143

With a little bit of practice, you barely need to think about this. But how does it help in programming? Performing bit masking and shifting bits left or right, is actually done with these number (1, 2, 4, 8, …) so if you can remember this up to 128, you can work with bytes (8 bit). Take it up to 65535 (216) you can handle integers (16 bit) and if you feel really smart (232 = 4294967296) if you want to handle 32 bit integers.

Bit-masking

Take the number 56 and visualise it as a binary (hint: it’s 0011 1000).
Write the number down in binary form in a table, bit by bit.
Now think of the number 1 and visualise that in binary (0000 0001).
Write that down in the next row.
In the top row, write down the bit numbers and their power of 2.

Bit n°76543210
2n1286432168421
5600111000
100000001

Read the last 2 rows bit by bit, and apply the AND (noted as &) function.
Write the result in a next row.
Bit 0: 0 AND 1 = 0
Bit 1,2,6,7: 0 AND 0 = 0
Bit 3,4,5: 1 AND 0 = 0

Bit n°76543210
2n1286432168421
5600111000
100000001
AND (&)00000000

The result is 0000 0000, which is also 0 in decimal.

Now do the same, but in stead of 1, use the number 8 (0000 1000).

Bit n°76543210
2n1286432168421
5600111000
800001000
AND (&)00001000

Now the result is 0000 1000 which in decimal is 8.

Now do the same for every power of 2.

Bit n° 7 6 5 4 3 2 1 0  
56 0 0 1 1 1 0 0 0  
& 1 0 0 0 0 0 0 0 1 = 0
& 2 0 0 0 0 0 0 1 0 = 0
& 4 0 0 0 0 0 1 0 0 = 0
& 8 0 0 0 0 1 0 0 0 = 8
& 16 0 0 0 1 0 0 0 0 = 16
& 32 0 0 1 0 0 0 0 0 = 32
& 64 0 1 0 0 0 0 0 0 = 0
& 128 1 0 0 0 0 0 0 0 = 0

The result wil be all 0000 0000 (0 in decimal) except for
0000 1000 which is 8 in decimal.
0001 0000 which is 16 in decimal.
0010 0000 which is 32 in decimal.
(Did you notice that 8+16+32 is 56?)

What is so important about this method is every time a bit is high in our number 56 (bit 3, bit 4 and bit 5), the result is different from 0. That is how bit-masking is done and how you can tell a computer which bit is high and which is low.

The next example will clarify this further

byte masked[8];      // define array of 8 masked bytes
byte testValue = 56; // 0011 1000

masked[0] = testValue & 1;   // 0011 1000 & 0000 0001 = 0
masked[1] = testValue & 2;   // 0011 1000 & 0000 0010 = 0
masked[2] = testValue & 4;   // 0011 1000 & 0000 0100 = 0
masked[3] = testValue & 8;   // 0011 1000 & 0000 1000 = 8
masked[4] = testValue & 16;  // 0011 1000 & 0001 0000 = 16
masked[5] = testValue & 32;  // 0011 1000 & 0010 0000 = 32
masked[6] = testValue & 64;  // 0011 1000 & 0100 0000 = 0
masked[7] = testValue & 128; // 0011 1000 & 1000 0000 = 0

If you prefer, you can also use the masks in their binary form

byte masked[8];      // define array of 8 masked bytes
byte testValue = 56; // 0011 1000

masked[0] = testValue & B00000001; // or you can write B1
masked[1] = testValue & B00000010; // or you can write B10
masked[2] = testValue & B00000100; // or you can write B100
masked[3] = testValue & B00001000; // or you can write B1000
masked[4] = testValue & B00010000; // or you can write B10000
masked[5] = testValue & B00100000; // or you can write B100000
masked[6] = testValue & B01000000; // or you can write B1000000
masked[7] = testValue & B10000000;

But if you want to use a shorter method, understanding the power of 2 will save you quite some time writing code

for (int b=0; b<=7; b++) {
  masked[b] = testValue & pow(2,b);
}

By checking if the masked testValue is 0 or equal to the mask, you can determine whether the bit is high (2n) or the bit is low (0).

for (int b=0; b<=7; b++) {
  masked[b] = testValue  & pow(2,b);

  // masked[b] is either 2 to the power of b, or 0
  if (masked[b] == pow(2,b)) {
    ; // The bit is HIGH
  }
  else {    // masked[b] == 0
    ; // The bit is LOW
  }
}

There is a better method, but that will be explained after the chapter bit-shifting.

Bit-shifting

Bit-shifting is literally what you probably except. Moving bits left or right.
Take the number 56 again (0011 1000) and shift it 2 bits to the right:
0011 1000 –1 bit–> 0001 1100 –1 bit–> 0000 1110
Or shift number 56 (0011 1000) 2 bits to the left:
0011 1000 <-1 bit– 0111 0000 <-1 bit– 1110 0000

What if 6 (0000 0110) is shifted 2 bits to the right, and then 2 bits to the left:
0000 0110 –1 bit-> 0000 0011 –1 bit-> 0000 0001
0000 0001 <-1 bit– 0000 0010 <-1 bit– 0000 0100
Notice that once a bit has shifted out, it is not recovered by shifting it back.
Instead, a 0 will just appear in it’s place.

In Arduino, shifting right is done with the operator >> and left with <<

byte binData = B01001000;  // Start value 0100 1000
binData >> 1;  // Shift 1 place right: 0010 0100
binData << 1;  // Shift 1 place left: 0100 1000
binData >> 4;  // Shift 4 places right: 000 0100  (pushed out bit 3)
binData << 4;  // Shift 4 places left: 0100 0000  (bit 3 doesn't return) 

Bit-masking and bit-shifting

So how does bit-shifting help if we have bit masking? Bit-shifting has it’s own use off-course, but combined with bit-masking, it becomes even more easy to figure out which bits are high and which bits are low.

for (int b=0; b<=7; b++) {
  // Shift bit completely to the right, then mask with 0000 0001
  pin[b] = readRegister >> b & B00000001;

  // Now pin[b] can only be 1 or 0
  if (pin[b] == 1) {
    ; // The bit is HIGH
  }
  else {    // pin[b] == 0
    ; // The bit is LOW
  }
}

Rather than calculating with powers of 2 (1, 2, 4, 8, …) this way the result is always 0 or 1. Looking at the example above, let’s apply that to number 56.

0011 1000 >> 0 & 1
–> Shift 0 bit to the right 0011 1000, then mask bit 0 -> this is 0
0011 1000 >> 1 & 1
–> Shift 1 bit to the right 0001 1100, then mask bit 0 -> this is 0
0011 1000 >> 3 & 1
–> Shift 3 bit to the right 0000 0111, then mask bit 0 –> this is 1

For bit 4 and 5 the result will also be 1, all other bits result in 0.
This way, bit-shifting and bit-masking combined, can very easily tell you which bits are high or low.

Practical use

A practical example where bit-shifting and bit-masking can be useful is when working with port registers. Image inputs 0,1,2,3,4,5,6,7 need to be read. One way to do this is with digitalRead(0), digitalRead(1), … This is very time-consuming and could be done much faster using port registers.

If you want to read more about port registers, check out the article “Speed read/write ArduinoI/O“.

All you need to know for know is that the line
pinRead = PIND;
will read inputs 0 to 7 (on an Arduino Uno) and store them as a byte where each bit represents the state of the pin. (bit 0 = pin 0 … bit 7 = pin 7).

So to very quickly read 8 inputs all at once, take a look at this example

byte pinRead;  // Define variable pinRead as a byte (8 bits)
bool pin[8];   // Define an array of 8 booleans

/****************************************************************
      SETUP
****************************************************************/
void setup()
{
  // Set pins 0 to 7 as INPUTs
  for (p=0; p<=7; p++) {
    pinMode(p, INPUT);
  }
}

/****************************************************************
      LOOP
****************************************************************/
void loop()
{
  // Read PORT register D (pins 0 to 7 on an Arduino Uno)
  pinRead = PIND;    // Read port register D

  // Store each pin state in the boolean array
  for (b=0; b<8; b++) {
    pin[b] = bool(pinRead >> b & 1);  // Shift 'b' bits (0..7) and bitmask with 0000 0001 (bit 0)
  }
}
// END

Image pin 3, 4 and 5 are high, the other pins are low.
pinRead will hold the value 56 (0011 1000)
(see where this is going ?)
Now each array element will hold 56 shifted by b bits and masked with 1.
Just as was explained with bit-masking and bit-shifting.

Writing bits in a byte

Reading bits is great, but what if writing bits is required. Is there an easy way to do that?

Off-course, just by using bit-masking with the AND function (a), you can also use bit-masking with the OR function (|).

Quick reminder

Bit ABit BAND (&)OR (|)
0000
0101
1001
1111

Remember number 56 (0011 1000)?
Lets write bit 3 and set it low, without touching the rest.

byte startValue = 56;  // 0011 1000
byte newValue = startValue & B11110111; // 0011 1000 & 1111 0111 = 0011 0000

0011 1000 AND 1111 0111 = 0011 0000 –> bit 3 is low, rest remained the same.
Now lets set bit 3 back high, without changing the rest.

byte startValue = 48;  // 0011 0000
byte newValue = startValue | B00001000; // 0011 0000 | 0000 1000 = 0011 1000

0011 0000 OR 0000 1000 = 0011 1000 –> bit 3 is high again, rest remained the same.

This is where bit-masking can be a great help to manipulating bits. Just as you can read port registers, you can write to port registers as well.