Since boolean values can only be false (zero) or true (one) then they could just be represented by a single bit but due to the way computers are made, the smallest thing you can store in memory is a single byte (8 bits.) Thanks to this every time you declare or use a bool you're neglecting seven lonely bits who'll never be used! In the big picture of things those 7 bits don't really matter but that might change if you're using large numbers of bools. This post will talk about what you can do if wasting those 7 bits aren't an option and some other useful/useless tricks.
Before we get into the guts of this post I want you to understand that you'll probably never use any of this thanks to the fact that modern day computers have so much memory that saving a few bits from bools is just a drop in the ocean, there are a two possible applications that I can think of however which we will discuss later.This post however is really intended to get you thinking and broaden your horizons on the amazing things you can do with computers.
Shoving 8 bools into a single byte
Important bit-wise operations
AND (&): This operation multiplies each pair of bits to determine the resulting bit.
Examples: 0101 & 0011 = 0001 1010 & 1011 = 1010 1111 & 0110 = 0110
OR (|): This operation takes each pair of bits and if either bit is 1 then the resulting bit is 1 otherwise zero.
Examples: 1000 | 0100 = 1100 1010 | 1100 = 1110 1111 | 0010 = 1111
XOR (^): This operation is called exclusive or, it takes each pair of bits and if only one bit is 1 then the resulting bit is 1 otherwise zero.
Examples: 1000 ^ 0100 = 1100 1010 ^ 1100 = 0110 1111 ^ 0010 = 1101
Shift Left (<<): This operation shifts all bits to the left by a number, any bits that fall off the left end are discarded while any bits that come in from the right side are set to zero. There is also a right shift (>>) which does the opposite.
Examples: 0001 << 1 = 0010 0011 << 2 = 1100 0101 << 2 = 0100
Down to business
boolValues = 0010 0011 (35 in decimal) boolPosition = 5
Shift 0x01 to the left by boolPosition
0000 0001 << boolPosition =
0000 0001 << 5 =
Compare against boolValues
0010 0000 & boolValues =
0010 0000 & 0010 0011 =
It's fairly simple on it's own, we are negating the boolean isTrue and then Exclusive Or'ing it against boolValues.
Before we continue though I want to explain something very important about the -isTrue part of the statement.
If isTrue is true then the value would be 0000 0001 or the number 1 in decimal.
When negated the resulting value would be 1111 1111 or the number -1 in decimal.
You might be wondering why negating 0000 0001 = 1111 1111 and that's because C++ uses a two's complement system for numbers and therefore negation != inverting the binary representation.
FUN FACT: In a one's complement system negating 0000 0001 = 1111 1110!
Now we take a look at the combination of these two expressions: (-isTrue ^ boolValues) & (1 << boolPosition)
To help explain I'm going to show you two examples (of just this expression) where we are setting the 2nd boolean value to true, one example will show what happens when the value we are setting is false, and the other when it was already true.
When the value we are setting is false:
When the value is already true:
That last AND operation is very important, when we are simply toggling one of the boolean values we get the bit we left shifted back from the AND operation. However if we try to set one of the values to true when it is already true we just get zeroes back.
Essentially what's going on is we are checking to see if the bit we are trying to change is already the value we are attempting to change it to, if it's already what we want we get all zeroes back, otherwise just the bit we want to change remains set.
Here's what happens when you're setting a bool to false:
When the value is already false:
Now lets take a look at the very last part of the function:
boolValues ^= (-isTrue ^ boolValues) & (1 << boolPosition)
The ^= operator is an XOR= which performs an XOR between the values on the left and right of the operator and then stores the result in the left value.
This last XOR operator is performed upon the current value of boolValues and the either the left-shifted bit (if the boolean value we are setting doesn't already equal isTrue) or all zeroes. In the case that the left shifted bit and the matching bit in boolValues are different then the bit in boolValues changes (to true or false.) Here are two diagrams showing the entire expression, the final XOR statement is the value boolValues will be set to:
When we are setting the bool to false.
Alright 700+ words later we're finally done explaining 2 lines of code, but hey it was worth it right?
The easier way of doing this
Something important to keep in mind with bit fields however is that the size of the struct will always be in bytes, so if you only use 4 bits it still will take up a byte of memory.
Imagine having to send 1000 bools across the network, that would mean 1000 bytes! By packing the bools on the server and then unpacking them on the client sending 1000 bytes of data can be accomplished in just 125 bytes!
Another possible application would be if you had to write (or store) a large number of bools in a very small space. Who knows when you'll ever need to do this but hey maybe one day you will!
In the end
Special Thanks to: Judy Cheng, Anthony Saulls