Operators


Now that you have been introduced to the predefined data types (refer to Chapter 2), you can begin to learn more about how to use these data types in combination with operators in order to perform calculations. For example, you can make calculations on variables that you have declared.

Beginner topic: Operators

Operators specify operations within an expression, such as a mathematical expression, to be performed on a set of values, called operands, to produce a new value or result. For example, in Listing 3.1 there are two operands, the numbers 4 and 2, that are combined using the subtraction operator, . You assign the result to the variable total.

Listing 3.1. A Simple Operator Example

total = 4  2;


Operators are generally broken down into three categories: unary, binary, and ternary, corresponding to the number of operands 1, 2, and 3, respectively. This section covers some of the most basic unary and binary operators. Ternary operators appear later in the chapter.

Plus and Minus Unary Operators (+, )

Sometimes you may want to change the sign of a numerical variable. In these cases, the unary minus operator () comes in handy. For example, Listing 3.2 changes the total current U.S. debt to a negative value to indicate that it is an amount owed.

Listing 3.2. Specifying Negative Values [1]

// National Debt to the Penny decimal debt = -8279328785833.43M;

[1] As of March 8, 2006.

Using the minus operator is equivalent to multiplying a number by -1.

The unary plus operator (+) has no effect on a value. It is a superfluous addition to the C# language and was included for the sake of symmetry.

Arithmetic Binary Operators (+, , *, /, %)

Binary operators require two operands in order to process an equation: a left-hand side operand and a righthand side operand. Binary operators also require that the code assign the resulting value to avoid losing the resulting value.

Language Contrast: C++Operator-Only Statements

Binary operators in C# require an assignment or call; they always return a new result. Neither operand in a binary operator expression can be modified. In contrast, C++ will allow a single statement, such as 4+5, to compile even without an assignment. In C#, only assignment, call, increment, decrement, and new object expressions are allowed for operatoronly statements.


The subtraction example in Listing 3.3 is an example of a binary operatormore specifically, an arithmetic binary operator. The other arithmetic binary operators are addition (+), division (/), multiplication (*), and remainder (%; sometimes called the mod operator).

Listing 3.3. Using Binary Operators

class Division {  static void Main()  {     int numerator;     int denominator;     int quotient;     int remainder;     System.Console.Write("Enter the numerator: ");     numerator = int.Parse(System.Console.ReadLine());    System.Console.Write("Enter the denominator: ");    denominator = int.Parse(System.Console.ReadLine());    quotient = numerator / denominator;                                     remainder = numerator % denominator;                                    System.Console.WriteLine(        "{0} / {1} = {2} with remainder {3}",        numerator, denominator, quotient, remainder);  } }

Output 3.1 shows the results of Listing 3.3.

Output 3.1.

Enter the numerator: 23 Enter the denominator: 3 23 / 3 = 7 with remainder 2

Note the order of associativity when using binary operators. The binary operator order is from left to right. In contrast, the assignment operator order is from right to left. On its own, however, associativity does not specify whether the division will occur before or after the assignment. The order of precedence defines this. The precedence for the operators used so far is as follows:

1) *, /, and %, 2) + and , and 3) =


Therefore, you can assume that the statement behaves as expected, with the division and remainder operators occurring before the assignment.

If you forget to assign the result of one of these binary operators, you will receive the compile error shown in Output 3.2.

Output 3.2.

... error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement

Beginner Topic: Associativity and Order of Precedence

As with mathematics, programming languages support the concept of associativity. Associativity refers to how operands are grouped and, therefore, the order in which operators are evaluated. Given a single operator that appears more than once in an expression, associative operators will produce the same result regardless of the order in which they are evaluated. Binary operators such as + and are associative because the order in which the operators are applied is not significant; a+b+c has the same result whether a+b is performed first or b+c is performed first.

Associativity applies only when all the operators are the same. When different operators appear within a statement, the order of precedence for those operators dictates which operators are evaluated first. Order of precedence, for example, indicates that the multiplication operator be evaluated before the plus operator in the expression a+b*c.


Using the Plus Operator with Strings

Operators can also work with types that are not numeric. For example, it is possible to use the plus operator to concatenate two or more strings, as shown in Listing 3.4.

Listing 3.4. Using Binary Operators with NonNumeric Types

classFortyTwo {    static void Main()     {            short windSpeed = 42;             System.Console.WriteLine(                  "The original Tacoma Bridge in Washington\nwas "                  + "brought down by a "                  + windSpeed + " mile/hour wind.");    } }

Output 3.3 shows the results of Listing 3.4.

Output 3.3.

The original Tacoma Bridge in Washington was brought down by a 42 mile/hour wind.

Because sentence structure varies among languages in different cultures, developers should be careful not to use the plus operator with strings that require localization. Composite formatting is preferred (refer to Chapter 1).

Using Characters in Arithmetic Operations

When introducing the char type in the last chapter, I mentioned that even though it stores characters and not numbers, the char type is an integer type. It can participate in arithmetic operations with other integer types. However, interpretation of the value of the char type is not based on the character stored within it, but rather, on its underlying value. The digit 3, for example, contains a Unicode value of 0x33 (hexadecimal), which in base 10 is 51. The digit 4, on the other hand, contains a Unicode value of 0x34, or 52 in base 10. Adding 3 and 4 in Listing 3.5 results in a hexadecimal value of 0x167, or 103 in base 10, which is equivalent to the letter g.

Listing 3.5. Using the Plus Operator with the char Data Type

int n = '3' + '4'; char c = (char)n; System.Console.WriteLine(c); // Writes out g.

Output 3.4 shows the results of Listing 3.5.

Output 3.4.

g

You can use this trait of character types to determine how far two characters are from one another. For example, the letter f is three characters away from the letter c. You can determine this value by subtracting the letter c from the letter f, as Listing 3.6 demonstrates.

Listing 3.6. Determining the Character Difference between Two Characters

int distance = 'f'  'c'; System.Console.WriteLine(distance);

Output 3.5 shows the results of Listing 3.6.

Output 3.5.

3

Special FloatingPoint Characteristics

The floatingpoint types, float and double, have some special characteristics, such as the way they handle precision. This section looks at some specific examples, as well as some unique floatingpoint type characteristics.

A float, with seven digits of precision, can hold the value 1,234,567 and the value 0.1234567. However, if you add these two floats together, the result will be rounded to 1234567, because the decimal portion of the number is past the seven significant digits that a float can hold. This type of rounding can become significant, especially with repeated calculations or checks for equality (see the upcoming Expert Topic, Unexpected Inequality with FloatingPoint Types).

Note that inaccuracies can occur with a simple assignment, such as double number = 4.2F. Since the double can hold a more accurate value than the float can store, the C# compiler will actually evaluate this expression to double number = 4.1999998092651367;. 4.1999998092651367 is 4.2 as a float, but not quite 4.2 when represented as a double.

Advanced Topic: Unexpected Inequality with FloatingPoint Types

The inaccuracies of floats can be very disconcerting when comparing values for equality, since they can unexpectedly be unequal. Consider Listing 3.7.

Listing 3.7. Unexpected Inequality Due to FloatingPoint Inaccuracies

decimal decimalNumber = 4.2M; double doubleNumber1 = 0.1F * 42F; double doubleNumber2 = 0.1D * 42D; float floatNumber = 0.1F * 42F; Trace.Assert(decimalNumber != (decimal)doubleNumber1); // Displays: 4.2 != 4.20000006258488 System.Console.WriteLine(      "{0} != {1}", decimalNumber, (decimal)doubleNumber1); Trace.Assert((double)decimalNumber != doubleNumber1); // Displays: 4.2 != 4.20000006258488 System.Console.WriteLine(      "{0} != {1}", (double)decimalNumber, doubleNumber1); Trace.Assert((float)decimalNumber != floatNumber); // Displays: (float)4.2M != 4.2F System.Console.WriteLine(     "(float){0}M != {1}F",     (float)decimalNumber, floatNumber); Trace.Assert(doubleNumber1 != (double)floatNumber); // Displays: 4.20000006258488 != 4.20000028610229 System.Console.WriteLine(     "{0} != {1}", doubleNumber1, (double)floatNumber); Trace.Assert(doubleNumber1 != doubleNumber2); // Displays: 4.20000006258488 != 4.2 System.Console.WriteLine(      "{0} != {1}", doubleNumber1, doubleNumber2); Trace.Assert(floatNumber != doubleNumber2); // Displays: 4.2F != 4.2D System.Console.WriteLine(      "{0}F != {1}D", floatNumber, doubleNumber2); Trace.Assert((double)4.2F != 4.2D); // Display: 4.19999980926514 != 4.2 System.Console.WriteLine(     "{0} != {1}", (double)4.2F, 4.2D); Trace.Assert(4.2F != 4.2D); // Display: 4.2F != 4.2D System.Console.WriteLine(     "{0}F != {1}D", 4.2F, 4.2D);

Output 3.6 shows the results of Listing 3.7.

Output 3.6.

4.2 != 4.20000006258488 4.2 != 4.20000006258488 (float)4.2M != 4.2F 4.20000006258488 != 4.20000028610229 4.20000006258488 != 4.2 4.2F != 4.2D 4.19999980926514 != 4.2 4.2F != 4.2D

The Assert() methods are designed to display a dialog whenever the parameter evaluates for false. However, all of the Assert() statements in this code listing will evaluate to true. Therefore, in spite of the apparent equality of the values in the code listing, they are in fact not equivalent due to the inaccuracies of a float. Furthermore, there is not some compounding rounding error. The C# compiler performs the calculations instead of the runtime. Even if you simply assign 4.2F rather than a calculation, the comparisons will remain unequal.

To avoid unexpected results caused by the inaccuracies of floatingpoint types, developers should avoid using equality conditionals with these types. Rather, equality evaluations should include a tolerance. One easy way to achieve this is to subtract one value (operand) from the other and then evaluate whether the result is less than the maximum tolerance. Even better is to use the decimal type in place of the float type.


You should be aware of some additional unique floatingpoint characteristics as well. For instance, you would expect that dividing an integer by zero would result in an error, and it does with precision data types such as int and decimal. float and double, however, allow for certain special values. Consider Listing 3.8, and its resulting output, Output 3.7.

Listing 3.8. Dividing a Float by Zero, Displaying NaN

float n=0f; // Displays: NaN System.Console.WriteLine(n / 0);

Output 3.7.

NaN

In mathematics, certain mathematical operations are undefined. In C#, the result of dividing 0F by the value 0 results in "Not a Number," and all attempts to print the output of such a number will result in NaN. Similarly, taking the square root of a negative number (System.Math.Sqrt(-1)) will result in NaN.

A floatingpoint number could overflow its bounds as well. For example, the upper bound of a float type is 3.4E38. Should the number overflow that bound, the result would be stored as "positive infinity" and the output of printing the number would be Infinity. Similarly, the lower bound of a float type is 3.4E38, and assigning a value below that bound would result in "negative infinity," which would be represented by the string -Infinity. Listing 3.9 produces negative and positive infinity, respectively, and Output 3.8 shows the results.

Listing 3.9. Overflowing the Bounds of a float

// Displays: -Infinity System.Console.WriteLine(-1f / 0); // Displays: Infinity System.Console.WriteLine(3.402823E+38f * 2f);

Output 3.8.

-Infinity Infinity

Further examination of the floatingpoint number reveals that it can contain a value very close to zero, without actually containing zero. If the value exceeds the threshold for the float or double type, then the value of the number can be represented as "negative zero" or "positive zero," depending on whether the number is negative or positive, and is represented in output as 0 or 0.

Parenthesis Operator

The parenthesis operator allows you to group operands and operators so that they are evaluated together. This is important because it provides a means of overriding the default order of precedence. For example, the following two expressions evaluate to something completely different:

(60 / 10) * 2 60 / (10 * 2)


The first expression is equal to 12; the second expression is equal to 3. In both cases, the parentheses affect the final value of the expression.

Sometimes the parenthesis operator does not actually change the result, because the orderofprecedence rules apply appropriately. However, it is often still a good practice to use parentheses to make the code more readable. This expression, for example:

fahrenheit = (celsius * 9 / 5) + 32;


is easier to interpret confidently at a glance than this one is:

fahrenheit = celsius * 9 / 5 + 32;


Developers should use parentheses to make code more readable, disambiguating expressions explicitly instead of relying on operator precedence.

Assignment Operators (+=, =, *=, /=, %=)

Chapter 1 discussed the simple assignment operator, which places the value of the righthand side of the operator into the variable on the lefthand side. Other assignment operators combine common binary operator calculations with the assignment operator. Take Listing 3.10, for example.

Listing 3.10. Common Increment Calculation

int x; x = x + 2;

In this assignment, you first calculate the value of x + 2 and then you assign the calculated value back to x. Since this type of operation is relatively frequent, an assignment operator exists to handle both the calculation and the assignment with one operator. The += operator increments the variable on the lefthand side of the operator with the value on the righthand side of the operator, as shown in Listing 3.11.

Listing 3.11. Using the += Operator

int x; x += 2;

This code, therefore, is equivalent to Listing 3.10.

Numerous other combination assignment operators exist to provide similar functionality. You can use the assignment operator in conjunction with not only addition, but also subtraction, multiplication, division, and the remainder operators, as Listing 3.12 demonstrates.

Listing 3.12. Other Assignment Operator Examples

x -= 2; x /= 2; x *= 2; x %= 2;

Increment and Decrement Operators (++, )

C# includes special operators for incrementing and decrementing counters. The increment operator, ++, increments a variable by one each time it is used. In other words, each code line shown in Listing 3.13 is equivalent.

Listing 3.13. Increment Operator

spaceCount = spaceCount + 1; spaceCount += 1; spaceCount++;

Similarly, you can also decrement a variable by one using the decrement operator, . Therefore, all the code lines shown in Listing 3.14 are also equivalent.

Listing 3.14. Decrement Operator

lines = lines - 1; lines -= 1; lines--;

Beginner Topic: A Decrement Example in a Loop

The increment and decrement operators are especially prevalent in loops, such as the while loop. For example, Listing 3.15 uses the decrement operator in order to iterate backward through each letter in the alphabet.

Listing 3.15. Displaying Each Character's ASCII Value in Descending Order

char current; int asciiValue; // Set the initial value of current. current='z'; do {  // Retrieve the ASCII value of current.   asciiValue = current;  System.Console.Write("{0}={1}\t", current, asciiValue);  // Proceed to the previous letter in the alphabet;  current;                                                       } while(current>='a');

Output 3.9 shows the results of Listing 3.15.

Output 3.9.

z=122 y=121 x=120  w=119  v=118  u=117  t=116  s=115 r=114 q=113 p=112 o=111  n=110  m=109  l=108  k=107  j=106 i=105 h=104 g=103 f=102  e=101  d=100  c=99  b=98  a=97

The increment and decrement operators are used to count how many times to perform a particular operation. Notice also that in this example, the increment operator is used on a character (char) data type. You can use increment and decrement operators on various data types as long as some meaning is assigned to the concept of "next" or "previous" for that data type.


Just as with the assignment operator, the increment operator also returns a value. In other words, it is possible to use the assignment operator simultaneously with the increment or decrement operator (see Listing 3.16 and Output 3.10).

Listing 3.16. Using the PostDecrement Operator

int count; int result; count = 0; result = count++;                                                      System.Console.WriteLine("result = {0} and count = {1}", result, count);

Output 3.10.

result = 0 and count = 1

You might be surprised that count is assigned to result before it is incremented. This is why result ends up with a value of 0 even though count ends up with a value of 1.

If you want the increment or decrement operator to take precedence over the assignment operator and to execute before assigning the value, you need to place the operator before the variable being incremented, as shown in Listing 3.17.

Listing 3.17. Using the PreIncrement Operator

int count; int result; count = 0; result = ++count;                                                       System.Console.WriteLine("result = {0} and count = {1}",  result, count);

Output 3.11 shows the results of Listing 3.17.

Output 3.11.

result = 1 and count = 1

Where you place the increment or decrement operator determines the order of operations, which affects how the code functions. If the increment or decrement operator appears before the operand, then the value returned will be the new value. If x is 1, then ++x will return 2. However, if a postfix operator is used, x++, the value returned by the expression will still be 1. Regardless of whether the operator is postfix or prefix, the resulting value of x will be different. The difference between prefix and postfix behavior appears in Listing 3.18. The resulting output is shown in Output 3.12.

Listing 3.18. Comparing the Prefix and Postfix Increment Operators

class IncrementExample {  public static void Main()  {     int x;     x = 1;     // Display 1, 2.     Console.WriteLine("{0}, {1}", x++, x++);     // x now contains the value 3.     // Display 4, 5.     Console.WriteLine("{0}, {1}", ++x, ++x);     // x now contains the value 5.     // ...   } }

Output 3.12.

1, 2 4, 5

As Listing 3.18 demonstrates, where the increment and decrement operators appear relative to the operand can affect the result returned from the operator. Preincrement/decrement operators return the result after incrementing/decrementing the operand. Postincrement/decrement operators return the result before changing the operand. Developers should use caution when embedding these operators in the middle of a statement. When in doubt as to what will happen, use these operators independently, placing them within their own statements. This way, the code is also more readable and there is no mistaking the intention.

Advanced Topic: ThreadSafe Incrementing and Decrementing

In spite of the brevity of the increment and decrement operators, these operators are not atomic. A thread context switch can occur during the execution of the operator and can cause a race condition. Instead of using a lock statement to prevent the race condition, the System.Threading.Interlocked class includes the threadsafe methods Increment() and Decrement(). These methods rely on processor functions for performing fast threadsafe increments and decrements.


Constant Expressions (const)

The previous chapter discussed literal values, or values embedded directly into the code. It is possible to combine multiple literal values in a constant expression using operators. By definition, a constant expression is one that the C# compiler can evaluate at compile time (instead of calculating it when the program runs). For example, the number of seconds in a day can be assigned as a constant expression whose result can then be used in other expressions.

The const keyword in Listing 3.19 locks the value at compile time. Any attempt to modify the value later in the code results in a compile error.

Listing 3.19. Declaring a Constant

Note that even the value assigned to secondsPerWeek is a constant expression, because the operands in the expression are also constants, so the compiler can determine the result.




Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net