WelcomeWhat's NewsORLibraryDownloadsLinksWebMaster@OnyxRing.com

This site
produced
656719
pageviews since
8/19/2004

Today's date:
9/26/2017






Author: Sonja Kesserich
Title: Bitwise and Logical Operators in Inform

Creation Date: 8/9/2004 6:09:54 PM
Last Updated: 8/9/2004 6:09:54 PM





BITWISE AND LOGICAL OPERATORS IN INFORM

n a programming language like Inform, the operators are those little symbols such as + - * / = which combine numbers and variables to form expressions. For example, when you see something like:


 average = (x + y)/2; 

you're looking at an Inform statement which uses an assignment operator "=", an addition operator "+" and a division operator "/", plus a pair of parentheses (...) to ensure that the addition happens before the division. Even if the word "operator" itself isn't familiar to you, the role of arithmetic symbols like these shouldn't come as much of a surprise. Likewise, you're probably fairly comfortable with conditional operators such as ">=", which tests if one value is greater than or equal to another value:


 if (average >= 7) print "Well done!";

Sections 1.6 through 1.8 in the Inform Designer's Manual introduce the operators which are most frequently found, and Table 1 provides a full list. As well as the commonplace arithmetic and conditional operators, there are others whose behaviour is sometimes less intuitive. This article focuses on two families which are easily confused -- bitwise operators and logical operators -- and explains their different uses.

1. Binary Numbers

Before getting started on the operators, we need to touch briefly on how data is stored. Computers munch down all information thrown at them and finally convert it into Binary, a base-2 numbering system of 0's and 1's. Binary represents electrical impulses either in an ON or OFF state, just like switches. The computer deals only in the flow of impulses; humans need to devise conventions so that certain combinations of these two digits represent numbers, letters, pixels, musical tones or any other kind of data.

When you type a number as part of a program, Inform assumes that you've typed a decimal (base-10) number unless you include a special prefix. There are two of these: "$" introduces a hexadecimal (base-16) number, and "$$" introduces a binary (base-2) number -- see section 1.4 of the DM. For example, here are the three ways of typing the number of years in a century:

     100    $64    $$1100100

All of those have the same value (though the first form is the most common and natural-looking), and they're all stored in the computer in the same way (in binary -- the third form). As another example, the three numbers:

     100    $100    $$100

are in everyday decimal worth respectively: one hundred, two hundred and fifty-six, and four. In this article, we won't mention hexadecimal numbers again, and we'll use "$$" to denote a binary number.

Inform works both with 16-bit numbers (for the Z-Machine) and 32-bit numbers (for Glulx) -- "bit" stands for BInary digiT. This means that each of your numbers is converted to a sequence of either sixteen or thirty-two bits, each bit having a value of 1 or 0.

A 16-bit number (Z-Machine):

     $$1001100011100101

A 32-bit number (Glulx):

     $$00001100111010010011100110101100

2. Bitwise Operators

Working directly with binary values is cumbersome; fortunately, you rarely need to. There are occasional situations, however, when programmers want to (or have to) work with low-level data, and this is where the bitwise operators make an appearance.

Inform defines three bitwise operators, which enable you to manipulate the individual bits in a number:

& bitwise AND operator merges two numbers
| bitwise OR operator merges two numbers
~ bitwise NOT operator changes one number

The & (AND) operator compares and merges two numbers bit by bit. For each of the 16 (or 32) comparisons, the output bit at that position is set to 0 unless both bits being compared are 1, in which case it too is set to 1 (that is, an output bit is set to 1 if the first input bit AND the second input bit are 1). So:

$$1010 & $$1100 evaluates to $$1000

(Note: You may find this clearer if you write it with the equivalent bits vertical aligned:

$$ 1 0 1 0 &
$$ 1 1 0 0
------------- evaluates to
$$ 1 0 0 0

where you can see that both input bits are 1 only in the leftmost case.)

The | (OR) operator compares and merges two numbers bit by bit. For each of the 16 (or 32) comparisons, the output bit at that position is set to 1 unless both bits being compared are 0, in which case it too is set to 0 (that is, an output bit is set to 1 if the first input bit OR the second input bit is 1). So:

$$1010 | $$1100 evaluates to $$1110

The ~ (NOT) operator inverts all of the bits of a number, changing each 0 to 1, and 1 to 0. So:

~$$1100 evaluates to $$0011

(Note: We are using small numbers for the sake of clarity. $$1100 should be read as $$0000000000001100 on the Z-machine, $$00000000000000000000000000001100 on Glulx. This would only affect -- in the examples -- the NOT result, where all 0's to the left of our defined number turn into 1's.)

So far, so good; you can study the binary bit patterns and see what's going on. But remember, it's perfectly valid to use a bitwise operator with decimal numbers:

100 & 20

You have to bear in mind that the operator always works on the bit patterns of the decimal numbers. If you were to read this expression using normal English, you might say "100 AND 20", which perhaps sounds like the result would be 120. But:

$$1100100 & $$0010100 evaluates to $$0000100 (4 in decimal notation)

Also, 100 | 20 is worked as $$1100100 | $$0010100, which evaluates to $$1110100, 116 in decimal notation.

You can demonstrate this using the example program in section 1.4 of the DM, with a small variation: 


 [ Main x y;
   x=100; y=20;
   print "Today's number is ", x & y, ".^"; 
 ];

This outputs:


 Today's number is 4. 

The basic print statement always outputs decimal numbers. To see the result in binary, you might use:


 [ Main x y;
   x=100; y=20;
   print "Today's number is ", (bin) x & y, ".^"; 
 ];
 [ bin x i;
   print "$$";
   for (i=16 : i>0 : i--) {
     if (x < 0) print "1"; else print "0";
     x = x + x;
   }
 ];

In theory, you can apply bitwise operations to lots of thingies, if you understand their binary representation. In practice, when coding a normal Inform game you'll probably be using bitwise operators with the same frequency as Mandarin Chinese...

3. Logical operators

Whereas the bitwise operators are fairly rare, you'll frequently encounter the logical operators, usually in conditional statements like if and while . Happily, they're also easier to understand, since their only concern is whether a number is false (that is, zero) or true (any non-zero value). For this section, we don't need to worry about binary bit patterns.

There are three logical operators:

&&logical AND operator tests two numbers
||logical OR operatortests two numbers
~~logical NOT operator tests one number


In each case, the result of the test can only be false (0) or true (1).

The && (AND) operator determines whether both numbers are true. So:


 x && y 

evaluates to 1 if x is true and y is true , and otherwise to 0. Note that the tests are performed left-to-right: if x turns out to be false then the result is bound to be 0, so the game doesn't bother testing y.

The || (OR) operator determines whether either number is true . So:


 x || y 

evaluates to 1 if x is true or y is true , and otherwise to 0. Note that the tests are performed left-to-right: if x turns out to be true then the result is bound to be 1, so again the game doesn't bother testing y.

The ~~ (NOT) operator inverts a number, changing true to false and false to true. So:


 ~~x 

evaluates to 0 if x is true , and otherwise to 1.

(Note: You should be aware of the way Inform handles the precedence of either bitwise and logical NOT operators. Please check http://www.firthworks.com/roger/informfaq/ for more information).

Here again is the example program in section 1.4 of the DM, now showing a logical operation:


 [ Main x y;
   x=100; y=20;
   print "Today's number is ", x && y, ".^"; 
 ];

This outputs:


 Today's number is 1. 

and if you change "y=20;" to become "y=0;", you'll get:


 Today's number is 0. 

The examples in this section have shown the logical operators testing the values of variables x and y. More commonly, though, you find them working on the outcome of the conditional operators. For example:


 (x > y) && (x < z) 

evaluates to 1 if x is greater than y and less than z, and otherwise to 0. Another example:


 (x == 7) || (y == 52) 

evaluates to 1 if x is 7 or if y is 52, otherwise to 0.

Here's a more interesting example:

 
 Object box "box" Physics_lab
  with name 'black' 'box',
   description "It's a box with a strange mechanism attached to it.";
   before [; 
      Open: if (Schroedinger in Physics_lab || student in Physics_lab) 
                 "~Halt!~, screams a voice, ~You arre interrferring vith a most 
                 necessarry experriment!~";
   ],
   after [; 
      Open: if (kitten in self && radiation_source has is_decayed)
                 "There's a dead kitten inside the box.";
     ],
 has static container openable;

The before routine intercepts the player's curiosity if either Erwin Schroedinger or his student are hanging around in the physics' laboratory. The after routine shows the macabre contents of the box if there is a kitten inside and if the radiation source has decayed (releasing the infamous poison gas).

4. Comparing Bitwise and Logical operators

The bitwise and logical operators are similar because:

  • they use the same names: AND, OR and NOT.


  • they use the same symbols: & | ~ for the bitwise operators, && || ~~ for the logical operators.


  • However, they are different because:

  • the bitwise operators treat each of the 16 (or 32) bits in a number individually (roughly like the arithmetic operators do), whereas the logical operators consider all 16 (or 32) bits as a single entity (roughly like the conditional operators do).


  • the result of a bitwise operation can be any numeric value (for the Z-machine, -32768..0..32767), whereas the result of a logical operation can only ever be 0 or 1.


  • The differences are important because, if you use the wrong operator, your game will still compile; worse, it will work correctly some of the time. For example, this statement:

    
     if (x && y) print "CORRECT^"; 

    prints CORRECT if both x or y are true (that is, non-zero). This statement is probably wrong:

    
     if (x & y) print "DUBIOUS^"; 

    because it will print DUBIOUS if the operation x&y evaluates to a non-zero quantity (the bit patterns of x and y must have one digit set to 1 in the same position). Here's another example:

    
     if (~~x) print "CORRECT^"; 
    

    print CORRECT if x is false (that is, zero). This statement is also probably wrong:

    
     if (~x) print "DUBIOUS^"; 

    because it will print DUBIOUS for all values of x apart from -1.

    5. A "bit" of praxis

    As we said, you are not likely to use bitwise operators unless you need to perform some low-level data manipulation or become concerned about wasting memory. Adam Cadre, author of many superb IF pieces, has published a library extension called Flags.h (it's part of a menu-based conversational system, used to manage what had been already said and when). You will find it here:

    http://www.ifarchive.org/if-archive/infocom/compilers/inform6/library/contributions/

    At the beginning of this file, he justifies its existence thus:

                   
     "This is a simple system to provide on/off flags that only take up one bit of memory; thus, 
     there's no need to waste memory by declaring a variable such as "doneflag" or some such, 
     allocating an entire eight bits to a variable that will never be anything other than 0 or 1." 
    

    You'll find a fine example of bitwise operator's usage in Flags.h, and although it's a surprisingly short piece of code, it's not easy to follow until you feel comfortable with some topics in Inform, such as arrays, routine calls and return values, and -- naturally -- bitwise operators.

    On the other hand, logical operators abound in games. Each time you need to test for more than one condition going on (or definitely not going on) in a given scenario, a logical operator will elbow its way into your code with startling ease. Let's take a look at some common examples.

    5.1 Using a logical AND

    Suppose you want to do something (here denoted by printing YES) when x is 1 at the same time as y is 2. Here's one way:

     
     if (x == 1)
       if (y == 2)
         print "YES^"; 
       else
         print "NO^";
     else
       print "NO^";

    Hard to read, error-prone, repetitious: ugh! Much much better like this:

                   
     if ((x == 1) && (y == 2)) 
         print "YES^";
     else
         print "NO^";

    5.2 Using a logical OR

    Same sort of thing; you want to do something when x is 1 or when y is 2. The clunky way:

    
     if (x == 1)
       print "YES^";
     else
       if (y == 2)
         print "YES^"; 
       else
         print "NO^";

    and the sexy way:

                   
     if ((x == 1) || (y == 2)) 
       print "YES^";
     else
       print "NO^";

    5.3 Using a logical NOT

    You don't want to do anything unless x is 1 and y is 2

    
     if ((x == 1) && (y == 2)) 
       else
     print "YES^";

    It somehow feels 'wrong' to need an else clause in these circumstances. Better to invert the condition:

    
     if(~~((x == 1) && (y == 2))) 
       print "YES^";

    or alternatively to recast it thus:

    
     if ((x ~= 1) || (y ~= 2)) 
       print "YES^";

    (Note: ~~(A && B) is the same as (~~A) || (~~B), and ~~(A || B) is the same as (~~A) && (~~B).)

    5.4 Avoiding run-time errors

    One vitally useful technique for avoiding run-time errors relies on the logical operators working left-to-right. Suppose you wrote this as part of an object definition:

    
     if (child(self) has general) 
       print "DUBIOUS^";     
    

    to test if an object's eldest child object has its general attribute set. Unfortunately, you'll get a run-time error if the object happens to have no children. Here's how to save the day:

    
     if (child(self) && child(self) has general) 
       print "CORRECT^";

    First check if there is a child; then and only then test its attribute. Here's a similar example for object properties. This may cause an error if object x doesn't define a myprop property:

    
     if (x.myprop > 5)
       print "DUBIOUS^";

    whereas this is safer:

    
     if (x provides myprop && x.myprop > 5) 
        print "CORRECT^";


    5.5 Testing many different things

    You can use conditions to check if an attribute has been set or not, what are the objects involved in an action, what action is being carried on, the current state of an object property, the value held in a variable... All these may be combined with logical operators as need arises. For instance:

    
     if (action == ##AttackWith && noun == troll && second == axe) {
        remove axe;
        "The troll watches politely as the axe hits his head and breaks in a thousand 
           pieces.  ~Zorry!~, he says, ~It zeemz that I didn't explain mezelf properly. It'z zwordz 
           that do the trick.";
     }
     if (location == Fast_food_palace && (noun == burger || noun == fries) 
             && clerk has friendly)
        "The clerk checks that nobody's looking and gives you a quick wink.
           ~I wouldn't recommend that unhealthy stuff, sir. Why don't you try our pizza 
           instead?~";
     if (noun provides size && noun.size >= 3 && 
            rolling_suitcase notin player && 
         player has exhausted) print_ret "You try to lift ", (the) noun, " but 
         your arms seem to have other ideas.";


    6. Conclusion.

    As we have seen, bitwise and logical operators behave quite differently from each other even if their syntax is roughly similar. In game design, where it's necessary to constantly test the current state of objects and situations, logical operators become helpful tools to combine conditions, while bitwise operators may be safely ignored most of the time -- it never hurts, however, to know what they are and do, in case you come across a circumstance where they might prove needful.

    [Thanks a lot to Roger Firth, whose unmerciful editorial pencil was decisive to bring this article to its present form.]



    Copyright 2004 - 2017 : Sonja Kesserich
    OnyxRing.com has been given the potentially non-exclusive right to display the content of this article. However the original author retains all rights. Permission to reproduce this article -- either in part or in whole -- is left strictly to the discretion of the original author.

    Table of ContentsAuthorsSearchIndex
    Would you
    recommend this
    article to
    someone else?
    You bet I would! Heck No!