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:
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:
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):
A 32-bit number (Glulx):
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:
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:
The basic print statement always outputs decimal numbers. To see the result in binary, you might use:
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:
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:
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:
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:
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:
and if you change "y=20;" to become "y=0;", you'll get:
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:
evaluates to 1 if x is greater than y and less than z, and otherwise to 0. Another example:
evaluates to 1 if x is 7 or if y is 52, otherwise to 0.
Here's a more interesting example:
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:
However, they are different because:
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:
prints CORRECT if both x or y are true (that is, non-zero). This statement is probably wrong:
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:
print CORRECT if x is false (that is, zero). This statement is also probably wrong:
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:
At the beginning of this file, he justifies its existence thus:
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:
Hard to read, error-prone, repetitious: ugh! Much much better like this:
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:
and the sexy way:
5.3 Using a logical NOT
You don't want to do anything unless x is 1 and y is 2
It somehow feels 'wrong' to need an else clause in these circumstances. Better to invert the condition:
or alternatively to recast it thus:
(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:
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:
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:
whereas this is safer:
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:
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.]