Bitwise in Practice: Flipping Bits
00:00 In the previous lesson, I showed you bit shifting in Python. In this lesson, I’ll give you several examples of where bitwise operations get used in practice.
00:10 A common use of bitwise operators is to manipulate a bit flag. A bit flag is just a binary value where each bit represents something that is either on or off in your software.
00:22 These were extremely common when memory was more expensive, but less so now. I don’t know it for a fact, but I’d be very surprised if each of the dots in the original Pac-Man game wasn’t a bit in a binary flag. As Pac-Man consumed a dot, the bit in the flag would get toggled.
00:39 A more memory-intensive mechanism would be a list or array of Booleans in Python. There are situations, though, where a binary flag is easier to read and interact with than such a list.
00:52
Consider the following code in the top window. The Box
object describes a box you might draw to the screen, having between zero and four sides.
01:01 I actually wrote something similar in the open source library Asciimatics recently for a widget that draws rectangular borders. Within the class, there are five constants.
01:13 The first is used to indicate that the top of the box should be drawn. The second is the bottom. I’ve written it here as one bit shifted a single position to underscore the fact that this is a bit flag. You’re just as likely to write the decimal number 2 in the real world.
01:30 The third and fourth flags are for the left and right sides. Each subsequent value being a bigger bit shift. The fifth constant is a bitmask used to determine if either the left or right sides are on.
01:45 It’s built using the left and right constants combined with a bitwise OR operation. The initializer on line nine takes an integer. A more robust one would check that only valid bits are used or mask out the value, but I was lazy when I wrote this code.
02:03
There are two methods of interest. The first, on lines 13 and 14, checks if any side is turned on. It does this by applying the bitmask called .SIDES
against the internal bit flag. If the box has either the left or right side turned on, the result of this AND operation will be a positive number.
02:23
You could just return that, but for completeness’s sake, I cast it to a Boolean value. The second method checks if both the left and right sides are on. This is a similar kind of calculation to the one before. The .SIDES
bitmask is supplied against the internal bit flag. But this time, rather than just checking truthiness, it is compared against the mask itself.
02:45
If both left and right are on, then the result of the AND will be equivalent to the mask. This time, I didn’t bother with the cast to Boolean. Let’s play with this in the REPL. First, I’ll import a Box
.
03:01
Now I’ll create a Box
specifying the top and right sides.
03:09
And because the right side is there, .has_side()
is evaluated as True
, but .has_both_sides()
is False
.
03:22
Let me construct another Box
,
03:28
this time with everything but the bottom side. .has_side()
is True
, and .has_both_sides()
is True
. Although you could achieve the same thing with four Booleans, checking if one or both sides is on would mean multiple if ... else
clauses.
03:47 Sometimes, a binary flag is not only more compact, but also easier to read. With the correct application of bitwise operations, you can arbitrarily turn on or off any bit in a bit flag.
04:01
Let’s look at another example to see this in practice. Like the Box
example, this Doors
object stores an internal flag. This one is called .door_state
.
04:15
The initializer sets the starting value to 0
, which will mean all the doors are closed. The .open_door()
method opens the door in the given position.
04:25
Opening a door means setting the corresponding bit in the flag. To set a specific bit, first, you create a mask by shifting 1
by the specified position, then |= (OR equals) the mask with the flag.
04:39
All the bitwise operators support an op-equals variant, meaning you don’t have to write self.door_state = self.door_state | <mask>
. It saves a bit of typing. To close the door, you do something similar.
04:54
First, create a mask for the bit representing the door’s position. Then, invert it. The resulting mask will have 1s for everything but the door you’re closing. &=
(AND equals) this mask, and the door in question will be set to 0
.
05:10 You can open and close a door. How about querying the door’s state? I’ve implemented two different ways of accomplishing the same thing. Lines 12 and 13 are the first version. Once again, construct a position bitmask. With the mask, simply AND it with the flag.
05:27
The result will be greater than 1
if the position in question is set to open. As all numbers, but 0
are truthful, this can be used like a Boolean.
05:37
The second variation on the same thing is in .is_open2()
. This time, instead of creating a position mask, I shift the flag value by position.
05:48 Remember, this doesn’t actually change the flag. It returns a new value that is the shifted version of the original. This will cause the least significant bit of the result to be the bit representing the door in question.
06:02
The last step is to use a mask of 1
to determine the truthiness of that least significant bit. Both methods .is_open()
and .is_open2()
accomplish the same thing.
06:14
The one advantage of .is_open2()
is that it always returns either 0
or 1
, whereas .is_open()
returns 0
or some positive number.
06:24
Depending on your use case, that 0
or 1
thing could be useful. The final method of the Doors
object toggles the door in a given position.
06:34
If it is open, it gets closed. If it is closed, it gets opened. This might seem strange in real life, but in a game where there’s only one button to change the state of the door, toggling would be exactly what you’d want. To toggle a bit, you use the XOR operator. Like with the other methods, create the position bitmask, but this time, ^=
(XOR equal) the value.
06:56
Remember that if both bits are on, XOR results in 0
. If the door’s closed, the XOR results in 1
, meaning open. If the door’s open, 1 ^ 1
is 0
, closing the door. Let’s play with this object in the REPL.
07:14 First, I’ll import it in.
07:19 Then I create a hallway. Now I’m going to open door number three.
07:28
Monty Hall, eat your heart out. Looking at the door state, you see that the 2 to the third bit is set, giving 8
.
07:42 Looking at the binary equivalent, you see the fourth position on. Evidently, our hallways are zero-indexed. Now I’ll open another door.
07:57 And the binary state of the doors.
08:03
Testing is open. Any value greater than 0
means the door is open. Doing it again with a closed door gives the appropriate result. Let me try this with the .is_open2()
variant.
08:24
Notice the result is still True
, but the value is different.
08:32 And that’s a closed door. Finally, let’s toggle something.
08:42 The fourth bit got turned off, leaving just the sixth on. Toggling it again
08:54
puts it back in the 40
state. Another use of bitwise operations is when dealing with colors, colors are commonly stored in RGB format. This is typically specified in hexadecimal.
09:10 Remember that each digit in hex corresponds to four bits of binary. The RGB format is short for Red, Green, Blue, with an RGB value having a byte for each color.
09:22
There’s often a fourth byte for transparency, or alpha channel, but I’ll be ignoring that for simplicity’s sake. Consider the hex value 7F8053
.
09:36
This corresponds to 7F
of red, that’s half intensity, 80
of green, one bit over half intensity, and 53
hex of blue, about a third of full intensity.
09:51 Those three colors combined turn into that drab olive. Don’t blame me, blame DaVinci. And I do the same thing that I just did in that color swab. Here’s the decimal equivalent.
10:10 You can peel the red out of the color by shifting out the green and blue bytes. That’s two bytes, or 16 bits. and red is 127 of possible 255, making it half intensity.
10:26 Showing the red in hex shows you that the two red digits have been extracted.
10:36 To do the same for blue, you want to first isolate the blue portion using a mask and then you can bit shift the result. There’s the decimal. And the hex value showing you the correct blue digits.
10:52 Now for the green. Green is already the least significant thing, so no shifting needed this time. Just mask out the other values. Here’s the decimal and the hex. By using these operations, you can isolate the colors that make up the full RGB value.
11:14 You could use other bitwise operations or even additions and subtractions to change the intensities of these values. Turning up the red by 10% would be a simple multiplication on the red portion. Once you’ve made the changes to a part of the color, all you need to do to combine it is shift things back together.
11:38
Here, the value lisa
is OR-ed values of red
shifted by two bytes, blue
shifted by one byte, and green
. As I didn’t change any of the values, lisa
and mona
should be the same.
11:55 And—as they say in the country that probably shouldn’t own this Italian painting—voilà! The next lesson is optional. It does a deeper dive into using the same techniques that you saw in this lesson. The lesson after that, if you decide to skip this one, is on big- and little-endian byte order.
Become a Member to join the conversation.