Binary and Hexadecimal Notation: A refresher for Computer Programming
Binary and hexadecimal notation are used everywhere. Let's brushen up on the topic if you're already familiar. For people that are new to it let me know if there are places that need revision for clarity. Pros, let me know where I'm wrong. Binary notation: Binary notation represents decimal numbers as bits or ones and zeros. In binary each digit is a bit which can be either 1 or 0. This is the number 1 represented as binary: 0001. This is the number 2 represented as binary: 0010. There are four bit places in the example above. That makes them 4-bit binary numbers. The number is read from the rightmost place first or the least significant bit: 0101 ↑ first digit (least significant bit) in a binary number 2s complement: All the bits of any given binary number together represent a number in a system called 2s complement, where each bit is a representation of 2^nth power : The rightmost digit (lsb) is either 0 or 2^0 power. The second from the rightmost digit is either 0 or 2^1 power. The third from the rightmost digit is either 2^2 power. The fourth from the rightmost digit is either 2^3 power. And so on... 0 0 0 0 (lsb) 2^3 2^2 2^1 2^0 0 or 8 0 or 4 0 or 2 0 or 1 0010 equals 2 as a decimal value. 2s complement is a system for representing numbers in binary format. Each bit is a power of two starting with 0 at the very rightmost digit. 8 (2^3) 4 (2^2) 2 (2^1) 1 (2^0) binary result as integer 0 0 0 1 0001 1 0 0 1 0 0010 2 0 0 1 1 0011 3 0 1 0 0 0100 4 Using 2s complement binary notation, any positive integer can be expressed. I'm not going into negative numbers for now - let's keep it simple. You can figure out any binary number manually with simple addition: 1101 ↑ First bit = 1 1101 ↑ Second bit = 0 1101 ↑ Third bit = 4 1101 ↑ Fourth bit = 8 1 + 4 + 8 = 13 1101 = 13 Number of bits in binary notation To be explicit, when we talk about bits, this is what is meant: 4-bit binary notation: 0000 8-bit binary notation: 0000 0000 16-bit binary notation: 0000 0000 0000 0000 Binary arithmetic Basic binary arithmetic like addition and subtraction may not impress you. However, Binary numbers can be used to perform some mathematical wizardry, as you will see. Binary addition In binary addition two overlapping positive bits will result in a carry over. See 1 + 1: 0001 (1) + 0001 (1) ------ = 0010 (2) Binary subtraction Binary subtraction is straightforward, simply subtract overlapping bits. 0001 (1) - 0001 (1) ------ = 0000 (0) Binary Logic: AND and OR Here's where binary numbers get really fun. Logical operations like AND and OR ect on binary numbers are just like a truth table, but flipped on its side like a math(s) operation. Just line them up vertically like a math problem: Logical AND AND: 1111 (15) & 0011 (3) ------ = 0011 (3) Even more spectacular: AND operations can act as what's called bit masking because only the bits from the "masking" number will remain. For example: binary AND any number with 3 (as we did above 0011), and only the lower two bits will remain. This can be useful for checking when a bit is set: bool isBitOneOrTwoSet; uint8_t bitMask = 3; // 0011 as binary uint8_t number = // some value if(number & bitMask){ isBitOneOrTwoSet = true; } Challenge: Can you think of a good way to write a function that tells you the bit index of the highest bit set in a binary number? Common operations with bit masking: 0xFF // Mask for a byte (8 bits) 0xFFFF // Mask for a word (16 bits) 0x0F // Mask for lower 4 bits 0xF0 // Mask for upper 4 bits 0x01 // Mask for least significant bit 0x80 // Mask for most significant bit Logical OR OR is taking a back seat to AND in this write up. It works the same way as you would expect. OR: 0001 (1) | 0010 (2) ------ = 0011 (3) What's most impressive to me about OR is that at first glance it just looks like addition, but remember it's an OR operation not addition. Take 1 and 1 for example, its result is 1: 0001 (1) | 0001 (1) ------ = 0001 (1) This is a bit "hand wavy", but it may be used in situations where writing to a row of pixels. Suppose an application needs to write to a display. Here is our display. It's a 32w x 9h grid of bits: 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 each pixel in the display is either on or off pixels are written to in blocks of 16-bit chunks (first row of pixels is two 16-bit chunks) a

Binary and hexadecimal notation are used everywhere. Let's brushen up on the topic if you're already familiar. For people that are new to it let me know if there are places that need revision for clarity. Pros, let me know where I'm wrong.
Binary notation:
Binary notation represents decimal numbers as bits or ones and zeros. In binary each digit is a bit which can be either 1 or 0.
This is the number 1 represented as binary: 0001
.
This is the number 2 represented as binary: 0010
.
There are four bit places in the example above. That makes them 4-bit binary numbers. The number is read from the rightmost place first or the least significant bit:
0101
↑ first digit (least significant bit) in a binary number
2s complement:
All the bits of any given binary number together represent a number in a system called 2s complement, where each bit is a representation of 2^nth power :
- The rightmost digit (lsb) is either 0 or 2^0 power.
- The second from the rightmost digit is either 0 or 2^1 power.
- The third from the rightmost digit is either 2^2 power.
- The fourth from the rightmost digit is either 2^3 power.
- And so on...
0 | 0 | 0 | 0 (lsb) |
---|---|---|---|
2^3 | 2^2 | 2^1 | 2^0 |
0 or 8 | 0 or 4 | 0 or 2 | 0 or 1 |
0010
equals 2
as a decimal value.
2s complement is a system for representing numbers in binary format. Each bit is a power of two starting with 0 at the very rightmost digit.
8 (2^3) | 4 (2^2) | 2 (2^1) | 1 (2^0) | binary | result as integer |
---|---|---|---|---|---|
0 | 0 | 0 | 1 | 0001 |
1 |
0 | 0 | 1 | 0 | 0010 |
2 |
0 | 0 | 1 | 1 | 0011 |
3 |
0 | 1 | 0 | 0 | 0100 |
4 |
Using 2s complement binary notation, any positive integer can be expressed. I'm not going into negative numbers for now - let's keep it simple. You can figure out any binary number manually with simple addition:
1101
↑ First bit = 1
1101
↑ Second bit = 0
1101
↑ Third bit = 4
1101
↑ Fourth bit = 8
1 + 4 + 8 = 13
1101 = 13
Number of bits in binary notation
To be explicit, when we talk about bits, this is what is meant:
4-bit binary notation:
0000
8-bit binary notation:
0000 0000
16-bit binary notation:
0000 0000 0000 0000
Binary arithmetic
Basic binary arithmetic like addition and subtraction may not impress you. However, Binary numbers can be used to perform some mathematical wizardry, as you will see.
Binary addition
In binary addition two overlapping positive bits will result in a carry over. See 1 + 1:
0001 (1)
+ 0001 (1)
------
= 0010 (2)
Binary subtraction
Binary subtraction is straightforward, simply subtract overlapping bits.
0001 (1)
- 0001 (1)
------
= 0000 (0)
Binary Logic: AND and OR
Here's where binary numbers get really fun. Logical operations like AND
and OR
ect on binary numbers are just like a truth table, but flipped on its side like a math(s) operation. Just line them up vertically like a math problem:
Logical AND
AND
:
1111 (15)
& 0011 (3)
------
= 0011 (3)
Even more spectacular:
AND
operations can act as what's called bit masking because only the bits from the "masking" number will remain. For example: binary AND
any number with 3
(as we did above 0011
), and only the lower two bits will remain.
This can be useful for checking when a bit is set:
bool isBitOneOrTwoSet;
uint8_t bitMask = 3; // 0011 as binary
uint8_t number = // some value
if(number & bitMask){
isBitOneOrTwoSet = true;
}
Challenge: Can you think of a good way to write a function that tells you the bit index of the highest bit set in a binary number?
Common operations with bit masking:
0xFF // Mask for a byte (8 bits)
0xFFFF // Mask for a word (16 bits)
0x0F // Mask for lower 4 bits
0xF0 // Mask for upper 4 bits
0x01 // Mask for least significant bit
0x80 // Mask for most significant bit
Logical OR
OR
is taking a back seat to AND
in this write up. It works the same way as you would expect.
OR
:
0001 (1)
| 0010 (2)
------
= 0011 (3)
What's most impressive to me about OR
is that at first glance it just looks like addition, but remember it's an OR
operation not addition. Take 1 and 1 for example, its result is 1:
0001 (1)
| 0001 (1)
------
= 0001 (1)
This is a bit "hand wavy", but it may be used in situations where writing to a row of pixels. Suppose an application needs to write to a display. Here is our display. It's a 32w x 9h grid of bits:
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
- each pixel in the display is either on or off
- pixels are written to in blocks of 16-bit chunks (first row of pixels is two 16-bit chunks)
- a pixel is on when the bit related to the pixel is on
In the example above if a row looks like this:
00000000000000011100000000000000
Then pixels 17, 18, and 19 are on. To write to other pixels in the row the dev might use bitwise or:
11100000000000000
| 00000000000001110
-------------------
= 11100000000001110
You can imagine how this might be helpful in something like: function drawPixel(x, y);
Challenge: Create your own api for a screen of pixels. What would the drawPixel implementation look like?
Hexadecimal Notation
Hexadecimal notation offers a more compact way of viewing and expressing binary numbers. It lets us represent numbers above 9 using a single character.
hexadecimal number | decimal |
---|---|
0 | 0 |
1 | 1 |
2 | 2 |
3 | 3 |
4 | 4 |
5 | 5 |
6 | 6 |
7 | 7 |
8 | 8 |
9 | 9 |
A | 10 |
B | 11 |
C | 12 |
D | 13 |
E | 14 |
F | 15 |
A hexadecimal number can be recognized because it always begins with 0x
: 0x0
Hexadecimal Anatomy:
- Prefix (0x) - indicates hexadecimal formatting of the number
- Digits - values that come after
0x
range from 0 to F - Places - each digit after the
0x
represents a nibble of 4 bits expressed from left to right
hexadecimal numbers range from 0 to F:
hex | binary | integer |
---|---|---|
0x0 | 0000 |
0 |
0x1 | 0001 |
1 |
0x2 | 0010 |
2 |
0x3 | 0011 |
3 |
0x4 | 0100 |
4 |
0x5 | 0101 |
5 |
0x6 | 0110 |
6 |
0x7 | 0111 |
7 |
0x8 | 1000 |
8 |
0x9 | 1001 |
9 |
0xA | 1010 |
10 |
0xB | 1011 |
11 |
0xC | 1100 |
12 |
0xD | 1101 |
13 |
0xE | 1110 |
14 |
0xF | 1111 |
15 |
Hexadecimal Concatenation
One of the benefits of hexadecimal notation is the way values can be stacked or concatenated to create higher bit representations:
hex | # of bits | binary | integer |
---|---|---|---|
0x1 | 4 | 0001 |
1 |
0x11 | 8 | 0001 0001 |
16 + 1 = 17 |
0x111 | 12 | 0001 0001 0001 |
128 + 16 + 1 = 145 |
0x1111 | 16 | 0001 0001 0001 0001 |
4096 + 128 + 16 + 1 = 4241 |
0x11
is the same as 0001 0001
which when expressed as integer is 17. So a hexadecimal number is a concatenation expression of each nibble of 4 bits from left to right - pretty cool right!? This can be expressed as a formula.
In a two-digit hexadecimal number, the first digit can be thought of as A and the second digit as B. The hex value can be calculated with the formula: A * (2^n number of bits) + B
:
0xFF
↑ Digit A
0xFF
↑ Digit B
0xFF <-- two digits, making this a concatenation of two 4-bit numbers
The above makes the equation: F x 2^4 + F
or 15 x 16 + 15 = 255
.
hex | binary | formula | mathematical representation | integer |
---|---|---|---|---|
0xFF | concat 1111 and 1111
|
A x (2^4) + B | 15 x 16 + 15 | 255 |
0xEF | concat 1110 and 1111
|
A x (2^4) + B | 14 x 16 + 15 | 239 |
0xEFEF | concat 1110 1111 and 1110 1111
|
A x (2^8) + B | 239 x 256 + 239 | 61423 |
- A four-digit hexadecimal number can be thought of as concatenating two 8-bit hexadecimal values.
What about 12-bit concatenation or other bit lengths?
To concatenate any number of bits, simplify the problem by adding each hexadecimal digit's value individually. Take the value 0xBCE
for instance:
0xBCE
= 0xB00 + 0x0C0 + 0x00E
To represent this as binary, we left shift the hexadecimal value by 4 bits for each hexadecimal place:
1011 0000 0000 (0xB00) = 11 x 16^2
+ 0000 1100 0000 (0x0C0) = 12 x 16^1
+ 0000 0000 1110 (0x00E) = 14 x 16^0
----------------
= 1011 1100 1110 (0xBCE) = (11x16^2) + (12x16^1) + (14x16^0)
The above is called bit shifting. In C, the expression below is equivalent to what we did above:
// shifts 'B' 8 bits to the left
// shifts 'C' 4 bits to the left
// Logical OR concatenates `B`, `C` and E!