Bitwise Operators: A Bit Wise
Posted by
Brad Wood
Aug 02, 2008 08:25:00 UTC
Here's a couple functions you've probably rarely used in ColdFusion: bitand(), bitor(), bitnot(), bitxor() etc. Frankly I've rarely needed any of them, but this week I did find a clever use for bitand(). I was messing around with a simple database-driven calendar I put on my church's website because I promised them a reoccurring event feature. I needed a simple way to store 12 "monthly" checkboxes without using 12 columns in the database.There's probably a few billion ways to do this, but for the sake of simplicity, speed, coolness, and obfuscating my code to the point where no one else could ever manage it I decided to use some bitwise operations to store the months. Hmm, even though your processor talks binary at its lowest level, the speed point might very well be moot with such a high level language like ColdFusion. The obfuscation probably cancels out the simplicity too. I guess that just leaves us with coolness, but that's enough for me.
To determine the value of each digit in a number, take the base of the number to the power of the position (starting at 0). The first 4 digits in base 10 (the number system you're used to) reading right to left are 1, 10, 100, and 1000. It's no coincidence that if this were Jeopardy, the other correct question for that answer would be "What is 10 to the power of 0, 1, 2, and 3?" In binary or base 2 the first four digits are 1, 2, 4, and 8. (2 to the power of 0, 1, 2, 3) Check out the formatbasen() function.
Okay, I heard that yawn out there... I'll get to the point. If I want to conveniently store several flags in one field, I can concoct a string of 1's and 0's where each digit represents a flag. In my scenario I wanted to store which of the 12 month checkboxes the user had checked. Therefore the string 010000000111 represents January, February, March and November. Convert that from binary to decimal for easy storage and we get 1031. (1 + 2 + 4 + 1024)
Ok, so now we've represented our flags as a series of 1's and 0's and even converted it to decimal, how can we easily tell if a particular month is represented. Well, for starters, we could treat the binary as a string and mid() it, but that would cheating. Besides, if this were like C++ or something it wouldn't be as fast. A bitwise operation can make this trivial.
One method of isolation that bit can be to use the bitmaskread() function. The following code says to convert 1031 to binary (010000000111), start at the 11th digit (the first is 0), and read one digit.
0 and 0 = 0
1 and 0 = 0
0 and 1 = 0
1 and 1 = 1
Take the following example:
111000
101010
101000 The first digits (starting from the right) were 0 and 0 so the first digit of the result is 0.
The second digits were 0 and 1 so the second digit of the result was 0 again.
Third digits were the same as the first.
The fourth digits were 1 and 1 so the fourth digit of the result is 1.
you get the picture... This may seem mundane, but what it allows us to do is quickly see if a series of bits are "turned on" or set to 1. If we perform a bitand() operation using a mask that represents the bits we are looking for the result will be equal to our mask ONLY if each of the bits we cared about were a 1. Let's say we want to test a string of bits and see if the first, third, and fifth bits are "on". Easy-- just take 10101 and perform a bitand() against your string. The 0's in your mask will cancel out the corresponding bits in the string being tested, the 1's will ONLY carry into the result if the corresponding digit in the test string was also one. 11111
10101
10101 -- Passes: The result equals the mask! 11110
10101
10100 -- Fails: The result doesn't equal the mask because the first bit was not on. That's the simple math going on the background, but you don't have to worry about it-- you can handle the decimal form of the number and mask. Let's refer back to our string of bits that represents January, February, March, and November. 010000000111 (1031 in decimal) Let's test it, to see if the March bit is on. The mask for March is 100 in binary, 4 in decimal. (insignificant digits are padded with 0's for the operation) Behind the scenes this happens: 010000000111
000000000100
000000000100 -- Passes: The result equals the mask! In ColdFusion, all you have to code is:
[code]#BitMaskRead(1031,10,1)#[/code]The highlighted portion is returned: 010000000111
[code]<cfif BitMaskRead(1031,10,1) eq 1> The flag for November was set </cfif>[/code]Another slightly cooler way is to use the bitand() function. Bitand literally takes two numbers and performs and logical and operation on each corresponding binary digit. Truth table for AND
0 and 0 = 0
1 and 0 = 0
0 and 1 = 0
1 and 1 = 1
Take the following example:
111000
101010
101000 The first digits (starting from the right) were 0 and 0 so the first digit of the result is 0.
The second digits were 0 and 1 so the second digit of the result was 0 again.
Third digits were the same as the first.
The fourth digits were 1 and 1 so the fourth digit of the result is 1.
you get the picture... This may seem mundane, but what it allows us to do is quickly see if a series of bits are "turned on" or set to 1. If we perform a bitand() operation using a mask that represents the bits we are looking for the result will be equal to our mask ONLY if each of the bits we cared about were a 1. Let's say we want to test a string of bits and see if the first, third, and fifth bits are "on". Easy-- just take 10101 and perform a bitand() against your string. The 0's in your mask will cancel out the corresponding bits in the string being tested, the 1's will ONLY carry into the result if the corresponding digit in the test string was also one. 11111
10101
10101 -- Passes: The result equals the mask! 11110
10101
10100 -- Fails: The result doesn't equal the mask because the first bit was not on. That's the simple math going on the background, but you don't have to worry about it-- you can handle the decimal form of the number and mask. Let's refer back to our string of bits that represents January, February, March, and November. 010000000111 (1031 in decimal) Let's test it, to see if the March bit is on. The mask for March is 100 in binary, 4 in decimal. (insignificant digits are padded with 0's for the operation) Behind the scenes this happens: 010000000111
000000000100
000000000100 -- Passes: The result equals the mask! In ColdFusion, all you have to code is:
[code]<cfif bitand(1031,4)> March was checked </cfif>[/code]Let's say your flags are coming from the database and your month is dynamic as you loop over your dates. No problem:
[code]<cfif bitand(qry_flags.flags,2^(month(now())-1))> Current month was checked </cfif>[/code]This simply takes the number of the month, subtracts one because we start at zero, and raises 2 to that power to find out the decimal representation of our mask. If this had been May, we would have done 2^4, or 16 or 1000. In case you are curious, I used the following code to save the checkboxes. The checkboxes all had a value of 1 and I cfparam'ed them to 0.
[code]<cfset this_occurrence_flags = (form.monthly_occurrence_flags_1 * 1) + (form.monthly_occurrence_flags_2 * 2) + (form.monthly_occurrence_flags_3 * 4) + (form.monthly_occurrence_flags_4 * 8) + (form.monthly_occurrence_flags_5 * 16) + (form.monthly_occurrence_flags_6 * 32) + (form.monthly_occurrence_flags_7 * 64) + (form.monthly_occurrence_flags_8 * 128) + (form.monthly_occurrence_flags_9 * 256) + (form.monthly_occurrence_flags_10 * 512) + (form.monthly_occurrence_flags_11 * 1024) + (form.monthly_occurrence_flags_12 * 2048)> [/code]What might have been simpler would have been for the checkbox values to equal 1, 2, 4, 16, 32 etc and then just straight up add them. Or for that matter, I could have simply concatenated them as 1's and 0's in to a string and fed them into the inputbasen() function with a radix of 2. I dunno- the more I think about it, the more ways I think of that I could have done it. Well, live long, prosper, and use some bitwise operations at least once in your life. You may not become a better person for it, but SOMEONE will look at your code down the road and think, "Wow, this guy was good!" Well, either that or they will curse your name for needlessly complicating the app. You decide.
Tags: ColdFusion, Technology
duncan
Great explanation. I've only used BitAnd stuff once before I think. I'd probably have rewritten your last block of code to: <cfset this_occurrence_flags = 0> <cfloop index="i" from="1" to="12"> <cfset this_occurrence_flags = this_occurrence_flags + ( form["monthly_occurrence_flags_#i#"] * (2^(i-1)) )> </cfloop>
Brad Wood
@duncan: I like it. Why copy and paste 12 lines when you are doing a repeatable and predictable operation!
Trevor
An extension of this that we use with VB, but are porting to CF.
For applications that keeps on needing new 'user choices', we have a 100 char (say) text field (OPTIONS), initialised to 'GGGGG....'
Each of these characters is a byte, with values from 0 to 255 To keep it printable (for debug), we restrict it to 'A' to 'Z' 'G' is used as the base so we can have negative values. (a VB CheckBox returns -1 if ON; VB OptionGroups (radio) return an integer)
This allows for 100 different options, each option can be in range -7 to + 20 If at any time in the future you need more options, just append more 'GGG' to the options string.
function GetOption(as_Options, ai_Option) GetOption = Asc(Mid(as_options, ai_Option, 1)) - Asc('G')
function SetOption(as_Options, ai_Option, ai_Value) Mid(as_Options, ai_Option, 1)) = Chr$(ai_value + Asc('G'))
example: GHFGIGGGGG option1 = 0 option2 = 1 option3 = -1 option4 = 0 option5 = 2 option6 = 0