Skip to the content of the web site.

Operators

We can generally categorize the behavior of operators by grouping them into five different categories:


Floating-point arithmetic

Four operators are involved with floating-point arithmetic: +, -, *, and /. Because floating-point numbers store only finite precision, in general, the vast majority of floating-point operations result in the addition of new errors into our calculation; thus, it is critical to reduce the number of computations made. For example, while we usually write and evaluate polynomials as

$a_4 x^4 + a_3 x^3 + a_2 x^2 + a_1 x + a_0$,

you may remember that C++ does not have an exponentiation operator, so you might try

	y = a[4]*x*x*x*x + a[3]*x*x*x + a[2]*x*x + a[1]*x + a[0];

but this requires 14 floating-point operations. Instead, the most efficient way to do this is to realize

$a_4 x^4 + a_3 x^3 + a_2 x^2 + a_1 x + a_0 = (((a_4 x + a_3)x + a_2) x + a_1) x + a_0$

and you now realize that

	y = (((a[4]*x + a[3])*x + a[2])*x + a[1])*x + a[0];

requires only eight operations. Thus, while the representation on the right is likely awkward for humans, it is actually the desirable representation for computers!

Another feature of floating-point numbers is that they can only approximate non-zero real numbers on a specific range:

  • No larger than approximately $1.80 \times 10^{308}$ in absolute value.
  • No smaller than approximately $4.94 10^{-324}$ in absolute value.

If you perform a calculation that makes a result larger than the largest possible value, it has a special value inf or -inf that is used, instead; and if you perform a calculation that results in a number that is too small to be stored, 0.0 or -0.0 is used instead. You will see that 1.0/0.0 evaluates to inf and 1.0/inf evaluates to 0.

In secondary school, you also learned that 0.0/0.0 is indeterminate, because it could be any number. Similarly, $\sqrt{-1}$ is not a real number, either. When the result of a computation can either not be determined or is not real, the result is NaN or nan, standing for not-a-number.

You can now go to an example of floating-point arithmetic on repl.it.


Integer arithmetic

You may wonder why we are differentiating floating-point operations from integer operations; after all, aren't they the same? In the world of mathematics, they are, but how computers implement integer operators is different than they are implemented for floating-point operators, and with good justification.

Given two integer operands, +, - and * tend to behave the same way that normal arithmetic does, but remember that the largest short is 32767 and the largest unsigned short is 65535. What happens if you add 1 to either of these:

    short           max16{32767};
    unsigned short umax16{65535};

    std::cout << ( max16 + 1) << std::endl;
    std::cout << (umax16 + 1) << std::endl;

You will note that the output is -32768 and 0, respectively. This effect is called overflow for signed integers and carry for unsigned integers.

To understand what is happening, you already know that one hour after 12 o'clock is 1 o'clock, and if you use European time, one hour after 23:00 is 0:00. Suppose you were to multiply $323 \times 543$ but you were only allowed to write down three decimal digits. The correct answer is $175389$, but if you can only store three digits, you're left with $389$. This is exactly what C++ does, only with binary numbers, not decimal numbers.

You may be wondering how negative numbers work. Consider, again, the 24-hour clock analogy, but let $23:00$ be $-1:00$ (one hour before midnight), and $12:00$ (noon) be $-12:00$ (or twelve hours before midnight). You now see that $11:00$ plus one hour ends up being $-12:00$, and $-12:00$ plus one hour is $-11:00$. The integer representations of negative numbers do the same thing, only using a representation called 2s complement.

Division is also different, for the division of two integers must be an integer, so C++ discards any remainder:

    std::cout << ( 13/5) << std::endl;    // prints  2
    std::cout << ( 39/7) << std::endl;    // prints  6
    std::cout << (-39/7) << std::endl;    // prints -6

There is a fifth arithmetic operator not defined for floating-point numbers: the modulus operator or the remainder operator, and its symbol is the %. Basically, if you perform long division, the remainder when when there are no digits before the decimal point to bring down is the result of this operator:

    std::cout << ( 13/5) << std::endl;    // prints  3
    std::cout << ( 39/7) << std::endl;    // prints  4

A quick test to see if a number is even is to check if the remainder when divided by two is 0:

    std::cout << ( 7%2) << std::endl;    // prints 1
    std::cout << ( 8%2) << std::endl;    // prints 0
    std::cout << ( 9%2) << std::endl;    // prints 1
    std::cout << (10%2) << std::endl;    // prints 0

For the most part, as a nanotechnology engineering student, you will not use the effects of overflow and carry; instead, you should make sure that you are using an integer data type that is large enough for what you need, and for the most part, if you are counting objects, int and unsigned int should be sufficient.


Comparison operations

Like Python and Matlab, the comparison operators ==, !=, <, <=, >= and > all take two operands and return true or false depending on whether the arguments satisfy the specific relationship. All comparison operators are Boolean-valued operators, as the result is either true or false.

Note, in C++, ! generally means not whereas in Matlab, you use ~, so != in C++ (and Python) is ~= in Matlab.

Note, all of these return false if both operands are NaN.


Logical operations

Given two Boolean-valued operands, you can query as to whether at least one is true or if both are true, or if a single Boolean-valued operand is false. In Python, the means of doing this is to use or, and and not. C++ uses special characters for these operators:

	(bool_expr_1 || bool_expr_2);      // Logical or
	(bool_expr_1 && bool_expr_2);      // Logical and
	!(bool_expr_1)                     // Logical not

Now, you'll note I put parentheses around all of the expressions. Please, follow this lead. Do not do something like:

         if a == b or c < d and not f( e ):     # In Python (poor style)
         if ( a == b || c < d && !f( e ) ) {    // In C++ (poor style)

Always group your expressions with parentheses: anyone reading your code (including yourself one month into the future) will thank you:

         if (a == b) or ((c < d) and not f( e )):     # In Python (good style)
         if ( (a == b) || ((c < d) && !f( e )) ) {    // In C++ (good style)

Remember that & is the ampersand character, which is derived from the Latin et, which literally translates to "and".

You may ask yourself, why || and && are used and not just one of each. This is because C was originally designed to write operating systems, and in such situations, bit-wise operations (to be discussed later) are more common. Also, the logical AND and logical OR operators allow for short-circuit evaluations, so if you have two or more operands linked by && or ||, then the first can stop as soon as one of the operands is false (in which case the entire expression must be false) and the second will stop as soon as one of the operands is true (in which case, the entire expression must be true:

    // If (a == b) is 'false', there is no need to test the other two,
    // if (a == b) is 'true', but (c == d) is 'false', again, we can stop, and
    // only if (a == b) and (c = d) are both 'true' do we test (e == f).
    if ( (a == b) && (c == d) && (e == f) ) {
        // Do something if all three conditions are 'true'
    } else {
        // Do something if any of the three conditions are 'false'
    }

    // If (a == b) is 'true', there is no need to test the other two,
    // if (a == b) is 'false', but (c == d) is 'true', again, we can stop, and
    // only if (a == b) and (c = d) are both 'false' do we test (e == f).
    if ( (a == b) || (c == d) || (e == f) ) {
        // Do something if any one of the three conditions are 'true'
    } else {
        // Do something if all three conditions are 'false'
    }

Bit-wise operations

Recall that all integer data types are a fixed number of bits, and each bit is either 0 or 1, and the first can be interpreted as false and the second as true.

Thus, given two 32-bit integers (eight bytes), you could compare each of the bits in order, and as if either one of the bits is 1 (bit-wise OR) or if both of them are 1 (bit-wise AND):

        10110110110100101000101010101010
      | 10011011101000010011010100011111
        10111111111100111011111110111111

        10110110110100101000101010101010
      & 10011011101000010011010100011111
        10010010100000000000000000001000

The complement operator (bit-wise not) ~ changes all 0s to 1 and all 1s to 0:

      ~ 10011011101000010011010100011111
        01100100010111101100101011100000

There is a third bit-wise binary operator, and that is the bit-wise exclusive-or or the bit-wise XOR. The exclusive-or of two operands returns true if one but not both of the operands are true, and false otherwise, and the operator is ^.

        10110110110100101000101010101010
      ^ 10011011101000010011010100011111
        00101101011100111011111110110111

Important, there is no ^^ operator in C++. This is for two reasons:

  • true in C++ is 00000001 and false is 00000000, and all Boolean-valued operations (comparison or logical) return one of these, so (a == b) ^ (c == d) still works.
  • There is no possibility for short-circuit evaluation to occur with the exclusive-or operator (we must always determine the truth of all operands) so, given the first point, there is nothing to be added by having a ^^ operator.

Another set of bit operators are the bit-shift operators.

The bit-shift operators value << n and value >> n move the bits of value to the left and to the right n bits. The value n must be positive. For example, the binary representation of $1285$ as a short is 0000010100000101. If we left-shift this by 2, the result is 0001010000010100. If we right-shift this by 5, the result is 0000000000101000. You will note that the bits shifted beyond the 16 bits are lost.


What are bit operators good for?

Most nanotechnology students will likely not use bit operators, but just to explain where these are useful, these operators can be used to examine or change individual bits in an integer data type:

For example, if n is a positive integer, then this assigns m an integer with only the nth bit set to 1:

    unsigned int nth_bit{1 << n};

Now, the following evaluates to nth_bit if the nth bit of value is 1 and 0 otherwise:

    value & nth_bit;

To set the nth bit of value to 1, use

    value = value | nth_bit;

To set the nth bit of value to 1, use

    value = value ^ nth_bit;

To set the nth bit of value to 0, use

    value = value & ~nth_bit;