Today we’ll be talking about binary numbers.
Here’s the video version of the article:
Computers don’t understand numbers. They only receive a signal or not. So, it’s an either-or mechanism: either there is a signal or not. Two possible values: signal – no signal, yes – no, or, as it is actually used, 0 – 1. That’s all computers can understand as far as numbers are concerned. That’s why everything we input is finally translated into sequences of 0’s and 1’s. We call each of these small bricks a bit.
Table of Contents
Binary Numbers vs Decimal Numbers
But using just the two bits, 0 and 1, we can only make two numbers: 0 and 1. Just like with our decimal system: with our ten digits (0-9) we can make only 10 numbers. What do we do if we need a larger number? We just add more digits. So, the next number after 9 is 10, with two digits, then 11, 12, 13, and so on. We can go on like this until we again run out of digits on the second position, which is the case at 19. Then we increase the first number to 2, thus getting 20. We go on like this until we run out of digits at both positions, which happens at 99. Then we have to add a third digit at the beginning. After we get at 999, we have to add a fourth digit, and so on.
But what about a system that consists of just two digits? Actually, it works exactly the same:
We call this system binary. There are only two digits, out of which we can make any number.
Bytes and Bits
We call a sequence of 8 bits a byte. We use bytes as the basic unit of memory in computer science, although sometimes we perform operations on particular bits. That’s where the bitwise operators come in handy.
So, let’s have a look at a byte:
This is a byte and each 0 or 1 is a bit.
Decimal Representation of a Binary Number
So, is there a way of telling which decimal number this binary number corresponds to? Sure, we can use a function which does the work for us:
>>> int(0b10011011)
155
This function may also work with 2 parameters: the first one is the literal binary sequence passed as a string with or without the prefix, the second parameter is the base, which in the case of the binary system is 2. Here’s how it works:
>>> int("10011011", 2) # without the prefix
155
>>> int("0b10011011", 2) # with the prefix
155
The int function returns the decimal representation of the binary number passed as the argument. Binary numbers are prefixed with 0b, the number itself consists only of 0’s and 1’s.
How to Do It Manually?
This is the way we usually do it, but it doesn’t hurt to know how to do it manually.
First let’s have a look at our commonly used decimal system. Let’s pick an arbitrary number like 283. As we use the decimal system, each digit in the number represents a power of ten, from right to left. So, the rightmost digit represents 10 to the power of 0, the next to the left digit represents 10 to the power of 1, and so on. If we multiply the digits’ values by their corresponding powers of ten and then sum it all up, we’ll get at our value:
It works the same way for binary numbers or any other system like octal or hexadecimal. What differs is the base which is raised to the increasing powers. So, in the decimal system the base is 10, that’s why we raise 10 to the powers of 0, 1, 2, etc. In the octal system it’s 8, in the hexadecimal system it’s 16. The base in the binary system is 2 and we raise 2 to the powers of 0, 1, 2, 3, etc. and then sum it all up.
Here’s how we calculate the decimal value of the binary number 0b10011011 mentioned above:
So, we have the same result as from the function before.
The maximum decimal value which can be held in one byte is 255. This is the case when we have 1’s in all positions:
Binary Representation of a Decimal Number
Now, if we have a decimal number and want to know what its binary representation is, we can use the bin function:
>>> bin(155)
'0b10011011'
How to Do It Manually?
Naturally, this can also be achieved by hand. We just have to divide the number by 2 and write down the remainder next to it. Then we divide the result by 2 again and write down the remainder, and so on. We keep doing it until we get at 0. This is floor division, which means we get integers as results and not floats.
You can read my article about true division and floor division if you need a refresher.
Unlike in typical floor division, here the remainder is not discarded, but taken note of. When done with all the divisions, we just read the remainders in reverse. It’s easier to understand if you look at an example.
Let’s find out what the binary representation of the decimal number 155 is. We already know it’s 10011011, but let’s see how to get at this result:
Now all we have to do is read the remainders from bottom to top: 10011011. That’s exactly what we were supposed to get.
Bit Length
As mentioned before, a byte is a very small unit. It can only hold values up to 255. For values greater than 255, we need more bytes. But in real world conditions we would have to count the bytes we need in millions, billions or more. That’s why we need larger units, like kilobytes, megabytes, gigabytes, terabytes and so on.
You can use the bit_length method on an integer object to check how many bits are necessary to hold the number in binary notation. For example the number 3 in binary notation is 11, so it takes up 2 bits. We can use the method on literals if they are enclosed in parentheses:
>>> (3).bit_length()
2
We can also use the method on variables:
>>> num = 458
>>> num.bit_length()
9
Let’s check it out:
>>> bin(458)
'0b111001010'
Yes, we need 9 bits.