Bitwise Operators

These operators obtain results based on the logical relationships of individual bits within a binary counting system. Bitwise logic is also known as Boolean logic or Boolean mathematics.


Bitwise Operators

Symbol Operation
| OR
& AND
~ NOT
^ XOR (exclusive OR)
<< Shift Left
>> Shift Right
>>> Shift Right Zero Fill

The bitwise AND ("&") and bitwise OR ("|") are briefly detailed in the logical operators topic. Additional information on these operators appear below.


For our examples, let's use 8-bit binary numbers to practice. They are represented as 4 bits, a space, and the last four bits.

Example: 0001 1010 = 26


Bitwise 'OR' operator ("|"):

Will compare the bits of two numbers, and return a number with a 1 in every bit that has a 1 in either number. This is basically used to 'add' a bit to a number.

Example: 0001 1010 | 0001 0101 = 0001 1111


Bitwise 'AND' operator ("&"):

Will compare the bits of two numbers, and return a number with a 1 in every bit that has a 1 in both numbers. This is used to see if a bit exists in a number.

Example: 0001 1010 & 0000 1000 = 0000 1000


Bitwise 'NOT' operator ("~"):

Will compare the bits of two numbers, and return a number with 1s where neither number has a 1. This is used to subtract a bit from a number.

Example: 0001 1010 ~ 0100 0011 = 1010 0100


Bitwise 'XOR' operator ("^"):

Will compare the bits of two numbers, and return a number with a 1 in every bit that has a 1 in one number, but not the other. This is used to toggle a bit in a number.

Example: 0001 1010 ^ 0001 0000 = 0000 1010 and 0001 1010 ^ 0000 0100 = 0001 1110


Bitwise 'shift left' operator ("<<"):

Will take the bits of a number and move the 1s to the left a certain number of places. This is used to multiply by powers of 2.

Example: 0001 1010 << 2 = 0110 1000


Bitwise 'shift right' operator (">>"):

Will take the bits of a number and move the 1s to the right a certain number of places. This is used to divide by powers of 2.

Example: 0001 1010 >> 2 = 1000 0110


Bitwise 'shift right zero fill' operator (">>>"):

Will take the bits of a number and move the 1s to the right a certain number of places. The ones and zeros (bits) that pass the right bound of the byte are discarded, and the left side of the byte is padded with zeros (zero bits). This is used to divide by powers of 2.

Example: 0100 1111 >>> 2 = 0001 0011


Why on earth do I want to know this?

The trick to using bitwise arithmetic is to stop thinking of the numbers as numbers, but as groups of Yes/No flags. Each bit can represent a TRUE/FALSE value which you would check against.


To use this effectively, you would have some preset values like this:

int QUEST_ONE_DONE = 1;
int QUEST_TWO_DONE = 2;
int QUEST_THREE_DONE = 4;
int QUEST_FOUR_DONE = 8;

Notice that each value is a power of 2. This means it can be represented by a binary number with only a single 1 in place. That place value is the location for the 'flag' that represents a certain TRUE/FALSE value.


A practical example using the values set above.

  1. I want to know if a PC has done a particular quest.
  2. I want to eliminate the number of variables stored on the PC that depict the state of the quests.
  3. I want a cleaner, more efficient way of checking the status of my quests.
  4. I check a local int on the PC that I have called 'nQuestFlags' with the following line:
  5. // assumes the following code executes in one of the PC's
    // events; if not use, the appropriate Get* function to retrieve
    // the PC (in place of OBJECT_SELF)
    int nQuestFlags = GetLocalInt(OBJECT_SELF, "nQuestFlags");
    if (nQuestFlags & QUEST_TWO_DONE) 
    { 
         // he did the quest 
    } 
    else 
    { 
         // he did not do the quest 
    } 
    
  6. When the PC completes quest 3, I add the flag with this line:
  7. // again, assumes this executes in one of the PC's events
    // as OBJECT_SELF refers to the PC
    nQuestFlags = nQuestFlags | QUEST_THREE_DONE;
    SetLocalInt(OBJECT_SELF, "nQuestFlags", nQuestFlags);
    

One final example to demonstrate the flexibility that bitwise operators and arithmatic provide appears below.

// prototype
void testQuest(int nQuests);
// pseudo-constants (bit flags)
int QUEST_ONE_DONE = 1;
int QUEST_TWO_DONE = 2;
int QUEST_THREE_DONE = 4;
int QUEST_FOUR_DONE = 8;

void main()
{
    // demonstrate the flexibility of bitwise operations
    int nQuest = 0;
    // let's complete quest one
    nQuest = nQuest | QUEST_ONE_DONE;
    // let's now complete quest three
    nQuest |= QUEST_THREE_DONE; // same as "nQuest = nQuest | QUEST_THREE_DONE;"
    // what does the log print out?
    testQuest(nQuest);
    // examining the log, you would see
    /*
        Quest one complete
        Quest three complete
    */
    testQuest(QUEST_ONE_DONE | QUEST_TWO_DONE | QUEST_THREE_DONE | QUEST_FOUR_DONE);
    // examining the log for the above statement, you would see
    /*
        Quest one complete
        Quest two complete
        Quest three complete
        Quest four complete
    */
}

void testQuest(int nQuests)
{
    // writes the completed quest to the log file
    if (nQuests & QUEST_ONE_DONE)
        PrintString("Quest one complete");
    if (nQuests & QUEST_TWO_DONE)
        PrintString("Quest two complete");
    if (nQuests & QUEST_THREE_DONE)
        PrintString("Quest three complete");
    if (nQuests & QUEST_FOUR_DONE)
        PrintString("Quest four complete");
}
Bitwise AND and OR in logical evaluations (if/then-type tests)

Instead of using the short-circuit logical evaluators and ("&&") and or ("||") you can use bitwise AND ("&") and OR ("|") in their place, assuming the default NWN values of TRUE ("1") and FALSE ("0") are used. Of course using these operators on statements that do not return a TRUE or FALSE will perform mathematical operations that will not give you the results you seek unless you are specifically trying to use bitwise arithmatic (see above).

// these two simple functions are used for demonstrating bitwise
// AND and OR
int LogicalTestOne(int iReturn)
{
   PrintString("Test One Ran...");
   return iReturn;
}

int LogicalTestTwo(int iReturn)
{
   PrintString("Test Two Ran...");
   return iReturn;
}

The following code sample uses the above defined functions and outputs to the log:

void main()
{
     if (LogicalTestOne(1) & LogicalTestTwo(2))
     {
          PrintString("Both tests returned true.");
     }
}

Examining the log, we find the following:

Test One Ran...
Test Two Ran...

But we expect to see "Both tests returned true." in the log file as well! What happened?


To understand this you need to understand what the values of 1 and 2 are and how the bitwise operator works. First the value of 1 in binary is 0001 and the value of 2 is 0010. The bitwise AND returns a 1 or a 0 if both positions of the comparitors are 1. in the case of 1 & 2 the result is 0:


0001
0010
----
0000

More bitwise fun comes with the | operator. What the OR operator does is return a 1 if either of the bits in the position are 1. So evaluating 0 | 2 results in:


0000
0010
----
0010

Which is 2. Now for the fun part, by definition 0 is FALSE and all other values resolve to NOT-FALSE or TRUE (even negative numbers; only 0 is FALSE). This can be evidenced by running the script with the values of 2 & 3:


0010
0011
----
0010

And 0010 is 2 which resolves to TRUE. The danger here occurs when you test numbers like 1 & 2:


0001
0010
----
0000

Or 3 & 4:


0011
0100
----
0000

Or 7 & 8:


0111
1000
----
0000

However, going back to my original note, IF you stick with the predefined values (as defined in nwscript.nss) of FALSE (0) and TRUE (1) you can safely exchange && with &. While it is true that the bitwise operators are different creatures than logical operators, informed developers can and do exchange them. The secret is understanding what they do.




 author: Ryan Hunt, editor: Charles Feduke, additional contributor(s): Joseph Berkley
 Send comments on this topic.