C for Yourself - part 4

This month in our C series, David Matthewman shows how to add, subtract, multiply, divide and generally mess about with variables.

So, what can you do with variables in C?

Well, you can add, subtract, multiply and divide them, bit-shift them, bitwise AND, OR and Exclusive-OR them, compare them and assign them values.

Variable types

Last month's article described some of the variable types available in C. Broadly, these fell into three types: character, integer and floating point.

Character variables are to all intents and purposes byte-long integer variables.

Integer variables can either be signed or unsigned, and both integer and floating point variables can come in a number of sizes.

You must bear this in mind whenever you perform arithmetic upon two variables of a different type, although most of the time C sorts everything out automatically.

You will run into problems if you attempt something foolish, such as converting -1.37253 into an unsigned int variable, or a double into a float variable too small to contain it.

Certain operations which work on a number binary digit by binary digit, such as bit shifting, only make sense when applied to integers, as floating point numbers do not have a standard binary representation. Such operations can therefore only be applied to integer variables.

Because compilers may not all treat negative numbers in the same way, it is also wise only to apply them to unsigned and positive integers.

When performing operations such as adding two or more variables, the variables may have different types.

C automatically converts the variables so that they have the same type before performing the addition (or whatever). C does this according to a series of rules which can be summarised as follows.

Firstly, all 'small integer' variables like char and - in some implementations - short int are 'promoted' to int. This ensures that the variables in the expression at least have the range of int variables.

All variables are then converted to the highest type present in the expression, where the types are (highest first):

Whether long int or unsigned int has a higher precedence depends upon the compiler.

Normally this conversion shouldn't worry you, with the notable exception of division which will be covered later.

Remember that you can force the conversion of a variable from one type to another by casting it - prefixing the variable with the type to which it is to be converted in brackets - as was explained last issue.

Binary operators

The easiest operators to understand are those which take two variables (which may also be constants, of course), and perform an operation on them, yielding a result.

These are called binary operators. Addition, subtraction, multiplication and division are the most familiar of these, and are represented by +, -, * and / respectively.

There is an added subtety with division; if both variables are integers the result is also an integer, whereas if one is a floating point number then so is the result. For instance, the expression:

1/x

will be the reciprocal of x is x has the type float, double, or long double, but will be either zero or one otherwise.

1/(double)x

will always be the reciprocal of x, which is more likely to be what was intended. The expression:

x%y

gives the remainder of x divided by y. Both variables must in this case be integers, otherwise the concept of a remainder obviously makes no sense.

Integer variables may be bit-shifted, which in a binary representation is equivalent to a multiplication or division by zero.

Right shifting is represented by >> and left-shifting by <<. Hence:

a=4; b=1;
c = a>>b;
d = b<<a;

will put a value of 2 in c and a value of 16 in d. It is safest to right-shift only unsigned integers, otherwise the compiler may include the sign bit in the shifting or ignore it.

Bitwise OR, AND and EOR of two variables is performed by the |, & and ^ characters respectively.

For a given binary operator op, there is a shorthand way in C of writing:

var = (var1) op (var2)

which is

var1 op= (var2)

Not only is this shorter and neater, it can help the compiler generate more efficient code.

An example of this is the following line of code, used to mask off the last byte of an integer:

integer &= 0xFF;

which ANDs the integer with 255.

Unary operators

There are a number of unary operators which operate on a single variable or constant.

The ++ and -- operators, placed either before or after a variable, increment or decrement the value of the variable and may be used as a command of their own, as in:

i++;
--counter;

However, they can also be used as part of a larger expression, in which case their positioning relative to the variable is significant.

If they are after it, then the variable is incremented or decremented after its value is used; if before it, then the value is incremented or decremented before its value is used. Hence:

a = 1; b = 1;
c = a++;
d = ++b;

leaves c with a value of one, and a, b and d with values of two.

Other unary operators are concerned with various forms of negation. The - operator is a simple minus operator, which negates the value of a variable.

There is also a + operator which does not change the value of a variable - very useful, that one.

The ~ operator produces the 'one's complement' of the variable's value. It is a bitwise NOT operator which converts each one-bit in a variable into a zero-bit and vice-versa.

Lastly, the | operator is a logical NOT operator which returns zero if the variable is non-zero and one otherwise. Hence, if x has a value eight (binary 1000):

+x is eight
-x is minus eight
~x is a bit pattern, with ones everywhere except the fourth bit from the right which is a zero
|x is zero.

If x has value zero:
+x is zero
-x is zero
~x is a bit pattern with ones everywhere
|x is one.

Assignments

The assignment operator = is almost too obvious to list, and we have used it freely in the series so far without explaining its syntax. However, it is important to note that:

x = 2;

is not only a statement, it is an expression with value 2. This is different from Basic, where it effectively has a 'value' TRUE, and has two important consequences.

Firstly, it permits constructions of the form:

x = y = z = 100;

which sets each variable to 100. Secondly, the = operator cannot be used in comparisons. For instance in:

if (i = 20)

the brackets will always give a value of 20, which is taken to be TRUE, and i will end up being set to 20 in the process.

This is not usually what is intended. When we look at if statements, we will see that the == operator should be used for testing for equality.

Next issue we will venture into the territory of array and points, two very different ways of looking at the same variable type.


Sidebox: "Precedence"

In an expression:

x = y + 5 * z;

the C compiler needs rules as to whether to do the multiplication or the addition first. All the operators have a 'precedence' or ranking, and those with a higher precedence are evaluated first - in this case the multiplication.

Any expression in brackets is evaluated first. The operators described here have the following precedence (highest first):

~, ++, --, unary +, unary -
*, /, %
binary +, binary -
<<, >>
&
^
|
op=

In an expression containing many operators of the same type, the operations are performed from left to right, except for the unary operators and those with the form op=, which are evaluated from right to left. Hence:

i = j * k * ++~(float)f

is equivalent to:

i = (j * k) * (++(~((float)f)))

The Precedence program on the cover disc sets up a number of complicated expressions, and then prints out the results. Try to work out what the results should be before running the program. There is a compiled version of the program also on the disc, for those without C compilers.


Source: Acorn User - 148 - November 1994
Publication: Acorn User
Contributor: David Matthewman