Skip to main content
Editor

Binary Numbers and Bits: How Computers Represent Data

Beginner40 min4 exercises55 XP
0/4 exercises

Everything a computer stores, from a game score to a selfie, is built out of bits: tiny values that are either 0 or 1. Programs group those bits and give each group a meaning, which is how one phone can hold numbers, letters, colors and sound. Once you can read binary, you can convert numbers by hand, compare them, and predict the mistakes a computer makes when it runs out of bits.

What are bits and bytes?

Bit is short for binary digit. A bit has only two possible values, 0 or 1. Inside a device, a bit might be a tiny switch that is off or on, a spot on a drive magnetized one way or the other, or a flash of light in a cable that is there or not there.

Computing devices represent data digitally. That means if you zoom all the way in on any value they store, a song, a message or a number, you find bits at the bottom.

A group of 8 bits is a byte. One bit can only tell two things apart, but every bit you add doubles the number of different patterns. Two bits give 4 patterns (00, 01, 10, 11), three bits give 8, and n bits give 2 to the power of n, written 2 ** n in Python.

Each extra bit doubles the number of patterns
for num_bits in [1, 2, 3, 4, 8, 16]:
patterns = 2 ** num_bits
unit = "bit " if num_bits == 1 else "bits"
print(f"{num_bits:>2} {unit} can form {patterns:,} different patterns")
Output
 1 bit  can form 2 different patterns
 2 bits can form 4 different patterns
 3 bits can form 8 different patterns
 4 bits can form 16 different patterns
 8 bits can form 256 different patterns
16 bits can form 65,536 different patterns

A byte can form 256 patterns, enough to give a different code to every letter, digit and punctuation mark on a keyboard. That doubling also tells you how many bits you need: to give 100 things their own pattern, you need 7 bits, because 6 bits make only 64 patterns and 7 bits make 128.

How can the same bits mean different things?

Bits on their own have no meaning. A program decides what a group of bits stands for, and that is an example of abstraction: reducing complexity by focusing on the main idea. When you text a friend, you think about words, not about the thousands of bits that carry them. Abstraction hides the details that do not matter for the job at hand and brings the useful ones together into something you can work with.

Bits are grouped to represent abstractions such as numbers, characters (letters, digits, emoji) and colors. Because the meaning comes from the program, the same sequence of bits can stand for different kinds of data in different contexts. The byte 01000001 is the number 65 to a calculator. To a text app it is the capital letter A, because the standard character code used by computers gives the letter A the number 65.

Colors work the same way. Many screens describe each pixel with three bytes: how much red, green and blue light to mix, each from 0 to 255.

One byte as a number and as a letter, three bytes as a color
bits = "01000001"
as_number = int(bits, 2) # read the bits as a binary number
as_letter = chr(as_number) # look that number up in the character code
print(f"{bits} as a number: {as_number}")
print(f"{bits} as a letter: {as_letter}")
pixel = ["11111111", "10100101", "00000000"] # red, green, blue
red, green, blue = [int(byte, 2) for byte in pixel]
print(f"{' '.join(pixel)} as a color: red {red}, green {green}, blue {blue}")
# red 255, green 165, blue 0 is orange
Output
01000001 as a number: 65
01000001 as a letter: A
11111111 10100101 00000000 as a color: red 255, green 165, blue 0

The code also shows the places a data value can live in a program. It can be stored in a variable (bits), in a list of items (pixel), or as a constant typed straight into the code (the 2 in int(bits, 2)). Values can also be passed into a procedure as input and come back as its output: int(bits, 2) takes text in and hands a number back. (Python calls procedures [functions](/python/python-functions/).)

How does a computer store something smooth, like sound?

Analog data have values that change smoothly over time instead of jumping in steps. The temperature outside rises gradually through a morning, the sky dims a little at a time at sunset, and the speed of a bike rolling downhill changes continuously. There is always another value between any two moments.

Bits cannot hold infinitely fine detail, so devices store a close approximation. The usual method is sampling: measure the analog signal at regular intervals. Each measurement is called a sample, and it is rounded to a value that fits the number of bits set aside for one sample. A typical music file keeps 44,100 samples for every second of sound.

Standing in for real-world analog data with digital data is another example of abstraction. You keep the details that matter, enough to sound right to a human ear, and drop the rest.

Sampling a smooth signal with 3 bits per sample
import math
BITS_PER_SAMPLE = 3
MAX_LEVEL = 2 ** BITS_PER_SAMPLE - 1 # 3 bits store the levels 0 to 7
def sound_wave(t):
"""A smooth, analog signal that moves between 0 and 1."""
return (math.sin(t) + 1) / 2
for step in range(8): # measure at regular intervals
t = step * 0.8
exact = sound_wave(t)
level = round(exact * MAX_LEVEL) # round to a level that fits
print(f"t={t:.1f} exact={exact:.3f} stored level={level} bits={level:03b}")
Output
t=0.0  exact=0.500  stored level=4  bits=100
t=0.8  exact=0.859  stored level=6  bits=110
t=1.6  exact=1.000  stored level=7  bits=111
t=2.4  exact=0.838  stored level=6  bits=110
t=3.2  exact=0.471  stored level=3  bits=011
t=4.0  exact=0.122  stored level=1  bits=001
t=4.8  exact=0.002  stored level=0  bits=000
t=5.6  exact=0.184  stored level=1  bits=001

The stored levels follow the shape of the wave, but they are not the exact values. Taking samples more often, or giving each sample more bits, makes the copy closer to the real thing. The cost is a bigger file.

How does binary place value work?

Data can be written in different number bases. Decimal (base 10) is the system you grew up with: it uses only the digits 0 to 9. Binary (base 2) uses only the digits 0 and 1. Both follow the same rule: a digit's position decides what it is worth.

In the decimal number 3,482, the 4 is worth 400 because it sits in the hundreds place. Positions are numbered from the right, starting at 0, and each place value is the base raised to the power of the position: 10⁰ = 1, 10¹ = 10, 10² = 100, 10³ = 1,000. A digit's value is the digit times its place value.

Binary uses base 2, so its place values are 2⁰ = 1, 2¹ = 2, 2² = 4, 2³ = 8, 2⁴ = 16, and so on. Each place is worth double the one to its right. Here is the binary number 10110:

Position43210
Place value168421
Bit10110

Its value is 1×16 + 0×8 + 1×4 + 1×2 + 0×1 = 22.

How do you convert binary to decimal?

Multiply each bit by its place value and add up the results. Since every bit is 0 or 1, that is the same as adding the place values that have a 1 under them. The program below does it one position at a time, starting from the rightmost bit.

Binary to decimal with place values
bits = "101101"
total = 0
position = 0
for bit in reversed(bits): # start with the rightmost bit
place_value = 2 ** position
print(f"position {position}: {bit} x {place_value:>2} = {int(bit) * place_value}")
total += int(bit) * place_value
position += 1
print(f"{bits} in decimal is {total}")
Output
position 0: 1 x  1 = 1
position 1: 0 x  2 = 0
position 2: 1 x  4 = 4
position 3: 1 x  8 = 8
position 4: 0 x 16 = 0
position 5: 1 x 32 = 32
101101 in decimal is 45
Exercise 1: Binary to Decimal
Write Code

Write to_decimal(bits). It takes a binary number as a string of 0 and 1 characters and returns its decimal value as an integer. Work it out with place values instead of calling int(bits, 2), which would do the whole job for you.

to_decimal('1101')  ->  13
to_decimal('0')     ->  0
def to_decimal(bits):
# Add up bit x place value for every position
pass

How do you convert decimal to binary?

There are two common methods, and they always agree.

Method 1: subtract place values. List the place values up to the number (32, 16, 8, 4, 2, 1 for anything under 64). Starting from the largest, write 1 if that place value fits into what is left and subtract it, otherwise write 0. For 37: 32 fits (5 left), 16 does not, 8 does not, 4 fits (1 left), 2 does not, 1 fits. Reading the digits gives 100101. This method is quick on paper.

Method 2: divide by 2 again and again. Write down each remainder, keep dividing the quotient, and stop at 0. The remainders, read from last to first, are the bits. This method is easy to turn into a program.

Decimal to binary by repeated division
number = 37
remainders = []
while number > 0:
remainder = number % 2
print(f"{number:>2} / 2 = {number // 2:>2} remainder {remainder}")
remainders.append(str(remainder))
number = number // 2
binary = "".join(reversed(remainders)) # the last remainder is the leftmost bit
print(f"37 in binary is {binary}")
print(f"Python's bin() agrees: {bin(37)}")
Output
37 / 2 = 18 remainder 1
18 / 2 =  9 remainder 0
 9 / 2 =  4 remainder 1
 4 / 2 =  2 remainder 0
 2 / 2 =  1 remainder 0
 1 / 2 =  0 remainder 1
37 in binary is 100101
Python's bin() agrees: 0b100101

Why does it work? The remainder after dividing by 2 says whether a number is odd (1) or even (0), and that is exactly its rightmost bit. Dividing by 2 and dropping the remainder slides every bit one place to the right, so the next remainder is the next bit. Python's built-in bin() puts 0b in front of the digits to mark them as binary.

Exercise 2: Decimal to Binary
Write Code

Write to_binary(n). It takes a whole number n (0 or more) and returns its binary form as a string with no leading zeros. Use repeated division by 2 instead of bin().

to_binary(13)  ->  '1101'
to_binary(0)   ->  '0'
def to_binary(n):
# Divide by 2 again and again; each remainder is a bit
pass

How do you compare and order binary numbers?

To compare two binary numbers by hand, line them up so they have the same number of digits by writing zeros on the left. Leading zeros never change a value, the same way 007 is still 7. Then read from the left: the first position where the numbers differ decides, and the number with the 1 in that position is larger.

Compare 01101 and 01011. The first two digits match. In the third digit, the first number has a 1 and the second has a 0, so 01101 (13) is larger than 01011 (11). Without leading zeros, a binary number with more digits is always the larger one: 1000 (8) beats 111 (7).

Sorting binary numbers as text vs as values
readings = ["1000", "111", "1011", "10"]
def value_of(bits):
return int(bits, 2)
print("Sorted as text: ", sorted(readings))
print("Sorted as numbers:", sorted(readings, key=value_of))
Output
Sorted as text:    ['10', '1000', '1011', '111']
Sorted as numbers: ['10', '111', '1000', '1011']
Exercise 3: Find the Largest Binary Number
Write Code

Write largest_binary(numbers). It takes a list of binary numbers written as strings and returns the one with the largest value, exactly as it appears in the list (keep any leading zeros). The list always has at least one item, and no two items have the same value.

largest_binary(['101', '1000', '11'])  ->  '1000'

Here max() on the text would wrongly pick '11'.

def largest_binary(numbers):
# Compare values, not text
pass

Check your understanding: binary

What is the decimal value of the binary number 11001?

Answers

Which binary number is equal to the decimal number 52?

Answers

Which list shows the binary numbers 1011, 110, 1101 and 111 in order from least to greatest?

Answers

What is displayed when this code segment runs?

PROCEDURE toDecimal(bitList)
{
total ← 0
FOR EACH bit IN bitList
{
total ← total * 2 + bit
}
RETURN(total)
}
 
DISPLAY(toDecimal([1, 0, 1, 1]))
Answers

What happens when a number needs more bits than it has?

In many programming languages, an integer gets a fixed number of bits, such as 8, 16, 32 or 64. That limits the range of values it can hold and the math you can do with them. With 8 bits, the biggest pattern is 11111111, which is 255. The true answer to 255 + 1 is 100000000, and that needs 9 bits. There is nowhere to put the extra bit, so the result overflows.

Depending on the language, an overflow stops the program with an error or wraps the value around and starts counting again from 0, like a two-digit stopwatch rolling from 99 to 00. Wrapping is worse in a way, because the program keeps running with a wrong answer. Here is an 8-bit coin counter in a game:

An 8-bit coin counter overflows
NUM_BITS = 8
def store(value):
"""Keep only the rightmost NUM_BITS bits, like an 8-bit counter."""
bits = format(value, "b") # the value written in binary
kept = bits[-NUM_BITS:] # bits that do not fit on the left are lost
return int(kept, 2)
coins = 250
for reward in [3, 2, 1, 10]:
true_sum = coins + reward
coins = store(true_sum)
print(f"true sum {true_sum:>3} = {format(true_sum, 'b'):>9} stored: {coins:>3}")
Output
true sum 253 =  11111101  stored: 253
true sum 255 =  11111111  stored: 255
true sum 256 = 100000000  stored:   0
true sum  10 =      1010  stored:  10

The player picked up one more coin and lost 255 of them.

Other languages provide an abstraction in which integers can grow as large as the computer's memory allows. The language on the exam reference sheet works this way, so exam questions written in its pseudocode never overflow. Python works this way too:

Python integers grow as needed
big = 2 ** 100
print(big)
print(f"This number needs {big.bit_length()} bits, and Python handles it")
Output
1267650600228229401496703205376
This number needs 101 bits, and Python handles it
Exercise 4: Add on a Fixed-Size Device
Write Code

A sensor stores every number in a fixed number of bits and wraps around on overflow: any bits that do not fit are thrown away. Write add_fixed(a, b, num_bits) that returns the value the sensor stores after adding a and b. Both a and b already fit in num_bits bits.

add_fixed(5, 3, 4)   ->  8   (8 fits in 4 bits)
add_fixed(15, 1, 4)  ->  0   (16 needs 5 bits, so it wraps)
def add_fixed(a, b, num_bits):
# Keep only the part of the sum that fits in num_bits bits
pass

Why is 0.1 + 0.2 not exactly 0.3?

Numbers with a fractional part, called real numbers (Python calls them floats), are also stored in a fixed number of bits. That limits their range and the math you can do with them, and many of them can only be stored as approximations. The value 0.1 has no exact binary form, in the same way that 1/3 has no exact decimal form (0.3333... never ends). The computer stores the closest value its bits allow, and the small difference is a round-off error. Add up enough of these and the errors show.

Round-off errors with real numbers
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
total = 0
for _ in range(10):
total += 0.1
print(total)
print(round(total, 2) == 1.0) # round before comparing
Output
0.30000000000000004
False
0.9999999999999999
True

Check your understanding: what bits can and cannot do

Which of the following statements about representing data with bits are true? Select two answers.

Select 2 answers.

Answers

A game adds 0.1 to a player's speed ten times, starting from 0, and then checks whether the speed equals 1.0. The check comes out false. Which of the following best explains why?

Answers

A phone app records a singer by sampling the sound. Which change would make the recording a closer approximation of the real sound?

Answers

A school gives each of its 1,200 students a unique ID number stored in binary. What is the fewest number of bits that each ID can use?

Answers

Exam topics: 2.1 Binary Numbers (DAT-1.A, DAT-1.B, DAT-1.C)

Related Tutorials

Was this tutorial helpful?