Expressions and operators · about 40 min

Bitwise operators and number bases

Why this matters

Underneath every value is a pattern of bits, and some jobs work directly on those bits: Unix file permissions (chmod 755), network masks (255.255.255.0), colors (#ff8800), hardware registers, compact sets of on/off options ("flags"), checksums and hashing. You will meet hexadecimal in error messages, memory addresses and byte dumps constantly. This lesson teaches you to read binary, octal and hex fluently and to use Python's bitwise operators to test, set and clear individual bits.

Positional number systems

The number 254 in decimal means 2 hundreds, 5 tens and 4 ones: each position is worth 10 times the one to its right. Other bases work the same way with a different multiplier.

decimal 13   = 1*10 + 3*1
binary  1101 = 1*8 + 1*4 + 0*2 + 1*1             = 13
octal   15   = 1*8 + 5*1                         = 13
hex     d    = 13                                (digits 0-9, then a=10 ... f=15)
binary:  1100 1000      hex digit per 4 bits:  c    8     ->  0xc8
binary:  11 001 000     octal digit per 3 bits: 3  1   0   ->  0o310

Writing and printing other bases in Python

Integer literals can be written in any of these bases using a prefix. They all produce the same kind of int object; the base is only about spelling:

print(0b11001000)   # binary literal
print(0o310)        # octal literal
print(0xc8)         # hex literal (case-insensitive: 0xC8)
print(0xc8 == 200)
200
200
200
True

To go the other way, from int to text in a base, use bin, oct and hex, which return strings with the prefix. And int(text, base) (from the conversion lesson) parses text:

n = 200
print(bin(n), oct(n), hex(n))
print(int("c8", 16), int("11001000", 2))
0b11001000 0o310 0xc8
200 200

format(n, "b"), format(n, "x") and f-strings (f"{n:08b}") give the digits without prefix and with padding; the strings module covers formatting.

The bitwise operators

These operators work on the binary representation of ints, bit by bit:

operator   name          rule for each bit            example with 12 (1100) and 10 (1010)
a & b      AND           1 if both bits are 1         1000 = 8
a | b      OR            1 if either bit is 1         1110 = 14
a ^ b      XOR           1 if exactly one bit is 1    0110 = 6
~a         NOT           flips every bit              ~12 = -13
a << n     left shift    moves bits n places left     12 << 1 = 24
a >> n     right shift   moves bits n places right    12 >> 2 = 3
a, b = 0b1100, 0b1010
print(bin(a & b), bin(a | b), bin(a ^ b))
print(1 << 10)      # 2 ** 10
print(200 >> 4)     # 200 // 16
0b1000 0b1110 0b110
1024
12

Shifting left by n multiplies by 2n, and shifting right by n is floor division by 2n. Python ints never overflow, so 1 << 100 is simply a large exact number.

NOT and negative numbers

Hardware stores negative integers in two's complement: flip all bits and add one. Python ints act as if they had infinitely many bits in two's complement, so negative numbers have infinitely many leading 1s. That makes ~x always equal -x - 1:

print(~5, ~0, ~-1)
print(bin(-5))
print(-5 & 0xFF)    # view -5 as an 8-bit byte
-6 -1 0
-0b101
251

bin(-5) shows a minus sign and the magnitude, not the bit pattern. To see the fixed-width pattern you would get in C, mask with & 0xFF (8 bits), & 0xFFFF (16) and so on: 251 is 11111011, the 8-bit two's complement of -5.

Bit flags

A classic use: storing several yes/no options in one integer, one bit each.

READ = 0b100     # 4
WRITE = 0b010    # 2
EXEC = 0b001     # 1

perms = READ | WRITE        # set two flags
print(bin(perms))
print(perms & WRITE != 0)   # test a flag
print(perms & EXEC != 0)
perms |= EXEC               # set a flag
perms &= ~WRITE             # clear a flag
print(bin(perms))
perms ^= READ               # toggle a flag
print(bin(perms))
0b110
True
False
0b101
0b1

The idioms to remember:

Unix permissions are exactly this. chmod 754 sets the 9-bit value 0o754: owner rwx (7 = 4+2+1), group r-x (5 = 4+1), others r-- (4). Python's os.stat(path).st_mode & 0o777 gives these bits for a real file, and the stat module names the constants (stat.S_IROTH is 0o004). The standard library also offers enum.Flag for named flag sets, which you may meet later.

Precedence of bitwise operators

In Python, from high to low: unary ~, then arithmetic, then shifts << >>, then &, then ^, then |, and only then comparisons. So:

x = 6
print(x & 4 != 0)     # (x & 4) != 0
print(1 << 2 + 1)     # 1 << (2 + 1): + binds more tightly than <<
True
8

This differs from C, where x & 4 != 0 means x & (4 != 0), a famous source of bugs. Even in Python, parentheses make bit expressions much easier to read.

Common mistakes

Recap

Going further: run ls -l and stat -c '%a %A' somefile in a terminal, then decode the octal mode by hand. Read about int.bit_length() and int.bit_count() (Python 3.10+) in help(int).

Exercises

Exercise 1 · quiz

Bit by bit

What are 6 & 3, 6 | 3 and 6 ^ 3? Type the three results separated by spaces.

Answer

In binary, 6 is 110 and 3 is 011. AND keeps bits set in both: 010 = 2. OR keeps bits set in either: 111 = 7. XOR keeps bits set in exactly one: 101 = 5.

Exercise 2 · quiz

Bitwise NOT

What does ~5 evaluate to in Python?

Answer
  • -6

Python ints behave as if they had infinitely many bits in two's complement, so ~x is always -x - 1. ~5 flips ...000101 into ...111010, which is -6. There is no fixed width (like 8 or 32 bits) unless you impose one with a mask, for example ~5 & 0xFF is 250.

Exercise 3 · write code

One number, four bases

Read a whole number (it may be negative) and print it in decimal, binary, octal and hexadecimal using bin, oct and hex, exactly like this (for 200):

dec: 200
bin: 0b11001000
oct: 0o310
hex: 0xc8

File: main.py

Starting point

n = int(input())
print("dec:", n)

Example

input

200

expected output

dec: 200
bin: 0b11001000
oct: 0o310
hex: 0xc8

Example

input

-5

expected output

dec: -5
bin: -0b101
oct: -0o5
hex: -0x5
Hint 1

bin(n), oct(n) and hex(n) return strings with a 0b, 0o or 0x prefix.

Reference solution
n = int(input())
print("dec:", n)
print("bin:", bin(n))
print("oct:", oct(n))
print("hex:", hex(n))

Exercise 4 · write code

Decode Unix permissions

Unix file permissions are 9 bits, usually written as 3 octal digits: owner, group, others, each digit being read (4) + write (2) + execute (1). Read a permission string such as 754 (octal digits, no prefix) and print, exactly (for 754):

mode: 0o754 = 492
owner: 7
group: 5
others: 4
others can read: True
group can write: False

Extract each digit with a right shift and a mask ((mode >> 6) & 7 for the owner). Test the single bits with & against the constants 0o004 (others read) and 0o020 (group write).

File: main.py

Starting point

mode = int(input(), 8)
print("mode:", oct(mode), "=", mode)

Example

input

754

expected output

mode: 0o754 = 492
owner: 7
group: 5
others: 4
others can read: True
group can write: False

Example

input

620

expected output

mode: 0o620 = 400
owner: 6
group: 2
others: 0
others can read: False
group can write: True
Hint 1

Each octal digit is 3 bits. The owner digit sits 6 bits up, the group digit 3 bits up.

Hint 2

& 7 (binary 111) keeps only the lowest three bits.

Hint 3

In Python, & binds more tightly than !=, so mode & 0o004 != 0 means (mode & 0o004) != 0.

Reference solution
mode = int(input(), 8)
print("mode:", oct(mode), "=", mode)
print("owner:", (mode >> 6) & 7)
print("group:", (mode >> 3) & 7)
print("others:", mode & 7)
print("others can read:", mode & 0o004 != 0)
print("group can write:", mode & 0o020 != 0)

Exercise 5 · write code

Is it a power of two?

A positive power of two has exactly one bit set (1, 10, 100, ... in binary). Subtracting 1 turns that bit off and all lower bits on, so n & (n - 1) is 0 exactly for powers of two (and for 0, which is not a power of two).

Read a whole number n and print exactly (for 64):

64 is a power of two: True

Use one boolean expression (no if), and make sure 0 and negative numbers give False.

File: main.py

Starting point

n = int(input())
print(n, "is a power of two:", n & (n - 1) == 0)

Example

input

64

expected output

64 is a power of two: True

Example

input

1

expected output

1 is a power of two: True
Hint 1

The starter is right for positive numbers but says True for 0.

Hint 2

Add a guard with and: n > 0 and ....

Reference solution
n = int(input())
print(n, "is a power of two:", n > 0 and n & (n - 1) == 0)