Operators

We have met the assignment operator a few times while working our way through the essential detail on variables. Now we can start looking at how to use those variables in interesting ways using operators.

The assignment Operator

Any expression to the right of an assignment operator is evaluated and the result assigned to the variable to the left of the operator. This can be simple like:

a = 5;

or more complex with an expression to the right of the operator.

a = b * x + z – 12;

Arithmetic Operators

The list of operators includes some familiar ones from arithmetic with some changes to suite a programming language.

Operator
+ Used to add two numeric values together (The String class also implements this operator to concatenate (join) combinations of strings and/or char variables.)
- Subtraction
/ Division (÷ is not on the standard keyboard)
* Multiplication (using an x would cause confusion as that could also be a variable name)
% modulo

The modulo operator might be a new arithmetic operator for some. It calculates the remainder when one integer value is divided by another.

int result = 9 % 5; // the variable result has the value 4
int result = 15 % 5; // the variable result has the value 0
int result = 3 % 5; // the variable result has the value 3

Just as you learned in school maths class, C arithmetic operators have rules of precedence. Plus and minus have a lower precedence than multiply, divide and modulo. Otherwise arithmetic operators are evaluated left to right.

x = 5 + 3 * 5; // results in x having the value 20 and not 40.

Just as in normal arithmetic, brackets can be used to enforce any desired precedence with expressions within brackets being evaluated first. This is true for all operator types, including comparison operators – coming up next.

Comparison Operators

We have a set of operators used to compare two values or the contents of two variables

Operator
== Equality operator (X == Y tests if X is equal to Y)
!= Not equal. (X != Y tests if X is not equal to Y)
< Less than (X < Y tests if X is less than Y)
> Greater than. (X > Y tests if X is greater than Y)
<= Less than or equal (X <= Y tests if X is less than or equal to Y)
>= Greater than or equal ( X >= Y tests if X is greater than or equal to Y)

All of these operators return a Boolean value (true or false). In a similar way to arithmetic operators, the comparison operators <,> , <=, and >= have precedence over == and != while they in turn have precedence over && or || (see next page). You can use brackets to ensure that things are evaluated as you intend them to be.

Comparison operators are most often seen as part of a conditional test where you might write something like

if (val1 <= val2) {…} as we will see shortly.

Compound Operators

We then have a group of operators known as compound operators.

Operator
++ This operator adds 1 to the operand but has two flavours. X = Y++; sets X equal to Y and afterwards increments Y. X = ++Y; increments Y and then sets X to that new value. So, if Y had a value of 5 it would end up with a value of 6 in each instance but in the first case X would be 5 and in the second 6, the same as Y.
-- This operator decrements the operand and again comes in two flavours. If Y has a value of 6 then X = Y—; sets X to 6 and Y to 5 while X = --Y; sets both X and Y to 5.
+= X += 5; is shorthand for the expression X = X + 5; The compound operator format of the expression can help make your code more readable
-= Similarly X -= 7; is shorthand for X = X – 7;
*= X *= 5; is equivalent to X = X * 5;
/= X /= 3; is equivalent to X = X / 3;
%= X %= 2; is equivalent to X = X % 2;
&= X &= Y; is equivalent to X = X & Y; and results in a bit manipulation of an integer value X where X and Y are “anded” together. More on this useful feature later.
|= X |= Y is equivalent to X = X | Y; and sets the bits of X to X or Y. Again, more later in the text.

Logical Operators

Operator
&& Logical AND. (X > 3 && Y < 6) is true if X is greater than 3 AND Y is less than 6
|| Logical OR. (X > 3 || Y < 6) is true if X is greater than 3 OR Y is less than 6 – thus true if either or both conditional test is true.
! Logical NOT returns true if the operand is false and vice versa. So (!X) evaluates as true if X is false.

Brackets are often required in expressions where there are multiple &&s or ||s as they have the same precedence. [Appendix 3 has a complete C precedence table.] How about something like:

if(a >= 5 || (b == 6 && (c <= 7 || d == 8))) {…}

Logical operators are used in expressions to return a Boolean true or false. These are generally used in conditional statements. We shall shortly see them in action.

Logical functions for char variables

There are some built in Arduino C functions designed to test char values and these return true or false. I have described them as Arduino functions as their names differ to the standard C versions found in the relevant char type library.

Function
isAlpha() Returns true if a char value is (greater than or equal to ‘a’ and less than or equal to ‘z’) OR (greater than or equal to ‘A’ and less than or equal to ‘Z’)
isAlphaNumeric() Same as isAlpha() but also returns true if the char value is in the range ‘0’ to ‘9’.
isAscii() Returns true of a char value is a valid ASCII code.
isControl() Returns true if the char has a value in the ASCII code range designated as control codes. The decimal value of the char would be in the range 0 to 31 or 127 (delete).
isDigit() Returns true if the char value is in the range ‘0’ to ‘9’. That is the decimal value range 48 to 57 inclusive.
isGraph() Returns true if the char value is a printable character other than a space or horizontal tab.
isHexadecimalDigit() Returns true if the char is a valid hexadecimal digit. These are in the range ‘0’ to ‘9’ and ‘a’ to ‘f’ or ‘A’ to ‘F’
isLowerCase() Returns true if the char is in the range ‘a’ to ‘z’
isPrintable() Returns true if the char is printable (including the space character)
isPunct() Returns true if the char is a punctuation character. Punctuation is broadly defined as any printable character other than a space, digit or letter.
isSpace() Returns true if the char is a space character, line feed (‘\n’), tab (‘\t’), form feed or vertical tab.
isUpperCase() Returns true if the char value is between ‘A’ and ‘Z’ inclusive.
isWhitespace() Returns true if the char is a space or horizontal tab character (the visual spaces preceding or between other characters in a line). Not to be confused with isSpace()

Bitwise Operators

The first two bitwise operators look similar to the first two logical operators but manipulate bits rather than evaluating to a Boolean true or false.

Operator
& X = X & Y; The bitwise AND operator compares the bits in X and Y. Where they are both 1 then the resulting bit is 1 and where one is 0 then the resulting bit is 0.
| X = X | Y; The bitwise inclusive OR operator compares each bit in X and Y. Where either bit is 1 then the resulting bit is set to 1 and otherwise is set to 0.
^ X = X ^ Y; the bitwise exclusive OR compares the bits in X and Y. Where one of the bits is 1 and the other is 0 then the resulting bit is set to 1. Otherwise the resulting bit is set to 0.
~ X = ~Y; The bitwise NOT operator is also known as the Ones Complement operator and sets all of the bits in the operand to the opposite value. So, a 1 becomes 0 and a 0 becomes 1. The change includes the sign bit.
<< X = X << 3; The Left bit-shift operator moves the bits in a value to the left by the specified number of bits. The rightmost bits are set to 0. In the example, if X was a char with the value 7 (binary 0000111) then the result would be 56 (binary 0111000). If the shift left had been 4 places then the result would have been -96 as the leftmost bit would be set making the value negative.
>> X = X >> 3; The Right bit-shift operator moves the bits in a value to the right by the specified number of bits. If the variable type is a signed integer then the leftmost bits are set to the value of the sign bit (which may be 1 or 0), otherwise they are set to 0.

The bit-shift operators are often used as a quick way to double or halve an integer value. The left shift operator in effect multiplies an integer by 2 raised to the power of the number of bits in the shift. X = X << 2; effectively multiplies the value in X by 4 (2 to the power of 2).

The right shift replication of the sign bit may not be what you intend so take care when right shifting negative integer variables.

Bitwise operators in action

Bitwise operators are used frequently to set and test Arduino register values as well as in general processing. A few examples will probably be a useful reference and bring some clarity to the operator descriptions.

We could start with two byte variables x and y. If x is set to 3 and y to 5 then the bits set before and after the main bitwise operators take effect can be seen in the following table

bit x y x&y x|y x^y x=~y
7 0 0 0 0 0 1
6 0 0 0 0 0 1
5 0 0 0 0 0 1
4 0 0 0 0 0 1
3 0 0 0 0 0 1
2 0 1 0 1 1 0
1 1 0 0 1 1 1
0 1 1 1 1 0 0

Remembering that a zero value is interpreted as false and a non-zero value is deemed to be true. You should be happy after checking the table that (x & 2) would be true while (y & 2) would be false.

The expression x = x | 8; would set bit 3 in x to 1, so x would now hold the value (decimal) 11. This is similar to adding 8 to 3 but if we repeated the process the result would still be (decimal) 11. This demonstrates that the “or” operator is not the same as the addition operator.

Following the above with x = x ^ 8; would unset bit 3 and re-set x to the value 3.

While x = ~y set the unsigned byte integer value to 250, if the byte was designated as a char integer (which is signed) then the value would be -6 (bit 7 is the sign flag and the value is in 2s complement format).

Relax though, mostly we just do ands (&), ors (|) and the odd xor (^) which are the easy ones.

Other bit level functions available within the Arduino environment include bit(), bitSet(), bitClear(), bitWrite(), bitRead() plus highByte() and lowByte() and we will explore these in another section.

Operators for Pointers

Pointers are an important C language concept and will be dealt with in detail, but for the record at this stage:

Operator
& X = &Y; Known as the Reference operator, stores the memory address of Y in X.
* X = *Y; Known as the Indirection operator and can be used to access the variable it points to. (Sometimes confusingly called the Dereferencing operator.)

Other Operators

And finally, it is arguable that sizeof and casts are operators so to keep a few additional things collected into one place making them easy to find later we have.

Operator
sizeof() X = sizeof(Y); sets X to the size of Y in bytes. If Y was declared as a char array: char Y[6]; then sizeof(Y) would return 6 but sizeof(Y[5]) would return 1 as that is the size of an element of that array.
( ) The cast operator can be used to cast one data type to another. Example: float x = (int)myDouble; would set x to the integer part of the value stored in myDouble.
? The ternary operator – see the section on conditional statements
-> See stuct pointers and class pointers later in the book
, comma, mostly used to separate multiple expressions in a for statement

Overflows and Casts (implicit or otherwise)

If your program code uses the assignment operator (=) to set the value of an integer variable to one that is outside of the range for the type then the code will proceed without error. As running the following code will demonstrate:

void setup() {
  Serial.begin(115200);
  byte x = 34; // maximum byte value is 255
  int y = 32765;
  x = y;
  Serial.println(x);
}

The Serial Monitor will display the value of x as 253. Where did the 253 come from? We can add a couple of lines to the end of the setup() function.

Serial.println(x, BIN);
Serial.println(y, BIN);

And the Serial Monitor will then show:

253
11111101
111111111111101

to demonstrate that the 8 bits of the byte type have been set the same as the bits in the low (rightmost) byte of the int. That binary value represents 253 in decimal notation.

There were no compiler errors or warnings and when the program executed the process did its best to comply with the assignment statement. The result however might have proved unexpected as far as the programmer was concerned.

Note: if the variable x had been a char then the decimal result would have been -3 with the same bits set.

Casting Variable Types

With the C language, you can be pretty confident that any numeric expression to the right of an assignment operator (=) will be evaluated and then converted to the type of the variable being assigned to. This can result in integer overflows or in some cases produce a compiler error where the data types are considered incompatible. Where the variable types to the right and left of the assignment operator vary then this automatic conversion is known as an implicit cast. As mentioned above, C also has an explicit cast operator that is less used but can prove very useful.

An explicit cast can be used to remove ambiguity in an expression or to force an expression to be evaluated in a particular way.

The syntax is:

(target_type)variable_to_cast;

The following short program explores a couple of use cases for the cast operator.

void setup() {
  Serial.begin(115200);
  int x = 9;
  int y = 2;
  float f = x/ y;
  Serial.print("x / y = ");
  Serial.println(f);
  f = x / (float)y;
  Serial.print("x / (float)y = ");
  Serial.println(f);
  int a = 123;
  int b = 456;
  int c = a + b;
  Serial.print("a + b = ");
  Serial.println(c);
  c = (char)(a + b);
  Serial.print("(char)(a + b) = ");
  Serial.println(c);
}

The (float) cast was used to ensure that the division of x by y was performed using floating point arithmetic and not integer arithmetic. The (char) cast applied a constraint to the sum of a and b before the result was stored in an int variable.

In-built conversion functions

The Arduino C documentation includes some explicit variable type conversion functions that you can use.

Function
char(x) Converts any value into a char. A float is truncated to any integer part. Negative values or integers greater than 127 may have unexpected results
byte(x) Converts any value to a byte. Floats are truncated to any integer part and numbers greater than 255 will overflow
int(x) Converts any value to an int type. Similar restrictions to above. Warning, does not convert a numeric character to the integer form as int(‘9’) would return 57. You could write something like int x = int(aChar) – 48; where aChar was in the range ‘0’ to ‘9’.
word(x) Converts any value x to a word type.
word(h,l) Creates a word type using the value h as the high byte and l as the low byte. On a 32 bit board this function sets the two lower bytes of the word.
long(x) Converts any value to a long type.
float(x) Converts any value to a float type.

It may help to avoid a future bug to know that an expression like y = float(75 / 2) will set y to 37.0 as the expression passed to the float() conversion function is first evaluated using integer arithmetic because that expression uses integer constants.. However y= float(75 / 2.0) will set y to 37.5 as the float constant (2.0) ensures floating point arithmetic when the expression within the brackets is evaluated.