Bit twiddling in a CSS age

Got a curious question the other day. One I had never really thought much about but after a discussion it became clear to me why the questions were the way they were.

Bit flipping and bit masking is not intuitive.

And unless you’ve had to do it then approaching it for the first time may no make a pile of sense.

For instance, if you have a look at the binary representation of a value in a Uint8, with code like

Dim i As UInt8 = 8
Dim s As String = Bin(i)

break

that 8, in binary, is 1000 (note I consider it a bug that any leading 0’s are not shown – they should be as the value 1000 is only 4 bits not all 8. Xojo’s Bin and ToBinary do not show it which is, incorrect, IMHO)

A Uint8 is 8 bits wide/ a Unit16 is 16 bits wide. A Uint32 is 32 bits wide. And any value stored in there uses ALL those bits either set to 0 or 1. So a 32 bit version of 8 should be

&b00000000000000000000000000001000

With this starting bit of info out of the way bit masking using the built in operators, AND, OR, NOT and XOR becomes somewhat simpler. Especially if you write things in binary using the &b notation although with really big values it becomes hard to read. This is often why you see people use HEX – or hexadecimal. It’s more compact. Octal is another form that is less often used.

If we start with our original value, 8 , as a Uint8 (or &b00001000) and want to test if certain bits are set then the AND operator is VERY useful.

In binary the LOW BIT, or bit 0, is the RIGHT MOST one – just like in decimal the ones is right most, tens next to that and so on.

So if we want to test if bit 3 is set in &b00001000 we can do

dim result as integer = 8 AND &b00001000
if result <> 0 then
  // tada ! bit 3 IS set !!!!
end if

The AND operator will take two bit patterns and match them up. Where there is a 1 in both operands, 8 and &b00001000, the result will have a 1. Since 8 is, in binary &b00001000 we get a result of &b00001000 – bit 3 is set in both 8 and so the result gets a 1 in that bit position.

     8 => &b00001000
AND       &b00001000
---------------------
          &b00001000

If we try a different value, 9, then we get results like

     9 => &b00001001
AND       &b00001000
---------------------
          &b00001000

Note the representation of 9 is basically “8 + 1”. Again when we apply the AND operator we get a result that is not 0. From that we know that in the two patterns some bits matched and gave us a result that was NOT all 0’s.

Other operators do slightly different things.

The OR operator will make sure that bits in the result are set to 1 if a bit is set to 1 in either operand. if we wanted to make sure that bit 3 WAS set then we could use OR

     8 => &b00001000       0 => &b00000000
OR        &b00001000    OR      &b00001000
---------------------   ------------------
result    &b00001000    result  &b00001000

In the left example above we started with the bit set. OR will not change it and so it remains set in the result. In the right hand example we start with the bit NOT set. OR makes sure it is set and the result has the bit set.

The NOT operator will flip all bits in the result. It only takes 1 operand. Any that are set to 1 will be flipped to be 0 in the result. Any that are 0 will be flipped to 1.

     8 => &b00001000       0 => &b00000000      170 => &b10101010
NOT                     NOT                    NOT
---------------------   ------------------     ------------------
result    &b11110111    result  &b11111111     result  &b01010101

In the left most example above we started with the bit set. NOT will change it and so is cleared result. But not also inverts all the other 0’s to 1’s as shown in the middle example. In the right hand example we start with the bit pattern for 170 (&b10101010). When we use NOT it flips every bit to the opposite.

XOR is the weirdest one. it full name is “exclusive or”. What that means it in its 2 input operands the result will be a 0 every where the inputs match. And a 1 everywhere they do not.

     8 => &b00001000        0 => &b00000000         170 => &b10101010
XOR       &b10101010    XOR      &b10101010     XOR        &b10101010
---------------------   -------------------     ---------------------
result    &b10100010    result   &b10101010     result     &b00000000

In the left most example you can see that we get 1’s in every spot where the second operand has a 1 and the first doesnt. But because bit 3 matches we get a 0 instead of a 1. The middle example shows that when one operand is all 0 you get a copy of the second operand. And in the right most we go no 1’s because all the spots in both match in every position and so we get all 0’s.

As I said writing these out for anything larger than an 8 bit value as binary is tedious and error prone. And so you often see hexadecimal used.

So how to determine the “right” hex to use ?

Again, EVERY integer is ALWAYS as the FULL width of whatever type you used. So always make sure you write things out in full.

We’ll start simple with an 8 bit value.

To convert &b10101010

  • write it in groups of 4 bits each(&b1010 1010)
  • each group of 4 bits is ONE hexadecimal “character”
  • the low bit position in the group of 4 is like the “ones” in decimal, the next like the tens and so on.
  • the lowest bit, if set, means a value of 1
  • the next bit, if set, means a value of 2
  • the next bit, if set, means a value of 4
  • the next bit. if set, means a value of 8

so in our case, &b1010, means, working from right to left, 0 + 2 + 0 + 8 or 10 * (You can check thins in Xojo code if you care to)

Now the screwy part. Our normal counting system is base 10. so whenever we get to 10 we start a new position to the left of the current one. In hexadecimal you count up to 16 – not 10. And so up to 9 things are the same but then for 10-15 we use the letters A to F (A = 10, B = 11, C = 12, D = 13, E = 14, F = 15) You convert each group of 4 bits exactly the same way.

So in hexadecimal our value is &hAA (= &b1010 1010) Again you can check this in code

if &hAA = &b10101010 then
  msgbox "Norm was right"
end if

Because hex is much more compact its widely used. And for large values its simpler to read and can be used just as effectively for bit masking. You just have to know how to convert you binary bit masks into hex.

*The number theory behind how decimal, binary, hexadecimal, octal and other counting systems works is really quite interesting. They’re all positional based systems and the difference is the base they use, not so much HOW they use the digits in the specific counting system. Heck we all use one every day that is base 60 and it seems perfectly normal whenever the minutes go by and turn into hours 😛