Items in this chapter from C# Cookbook (O'Reilly Media, ISBN: 0-596-00339-0, 2004) include: Testing for even or odd vlaue, safely performing a narrowing numeric cast and converting radians to degrees. Get a complete, documented code sample to solve a specific problem, and review a discussion of how the underlying technology works, alternatives and limitations.
Contributed by O'Reilly Media Rating: / 22 June 29, 2004
Simple types are value types that are a subset of the built-in types in Visual C# .NET, although, in fact, the types are defined as part of the .NET Framework Class Library (.NET FCL).
Simple types are made up of several numeric types and a bool type. These numeric types consist of a decimal type (decimal), nine integral types (byte, char, int, long, sbyte, short, uint, ulong, ushort), and two floating-point types (float, double).
Table 1-1 lists the simple types and their fully qualified names in the .NET Framework.
Table 1-1. The simple data types
Fully qualified name
Reserved C# keyword
Value Range
System.Boolean
bool
true or false
System.Byte
byte
0 to 255
System.BSByte
sbyte
-128 to 127
System.Char
char
0 to 65535
System.Decimal
decimal
–79,228,162,514,264,337,593,543,950,335 to 79,228,162,514,264,337,593,543,950,335
System.Double
double
–1.79769313486232e308 to 1.79769313486232e308
System.Single
float
–3.402823e38 to 3.402823e38
System.Int16
short
–32768 to 32767
System.Uint16
ushort
0 to 65535
System.Int32
int
–2,147,483,648 to 2,147,483,647
System.Uint32
uint
0 to 4,294,967,295
System.Int64
long
–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
System.Uint64
ulong
0 to 18,446,744,073,709,551,615
The C# reserved words for the various data types are simply aliases for the fully qualified type name. Therefore, it does not matter whether you use the type name or the reserved word: the C# compiler will generate identical code.
It should be noted that the following types are not CLS-compliant: sbyte, ushort, uint, and ulong. These types do not conform to the rules governing CLS types and therefore, they might not be supported by other .NET languages. This lack of support might limit or impede the interaction between your C# code and code written in another CLS-compliant language, such as Visual Basic .NET.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
You need to compare a fraction with a value of type double or float to determine whether they are within a close approximation to each other. Take, for example, the result of comparing the expression 1/6 and the value 0.16666667. These seem to be equivalent, except that 0.16666666 is precise to only 8 places to the right of the decimal point, and 1/6 is precise to the maximum number of digits to the right of the decimal point that the data type will hold.
Solution
Verify that the difference between the two values is within an acceptable tolerance:
// This is NOT a good approximation return (false);
}
}
Replacing the type double with float allows you to determine whether a fraction and a float value are approximately equal.
Discussion
Fractions can be expressed as a numerator over a denominator; however, storing them as a floating-point value might be necessary. Storing fractions as floating-point values introduces rounding errors that make it difficult to perform comparisons. Expressing the value as a fraction (e.g., 1/6) allows the maximum precision. Expressing the value as a floating-point value (e.g., 0.16667) can limit the precision of the value. In this case, the precision depends on the number of digits that the developer decides to use to the right of the decimal point.
You might need a way to determine whether two values are approximately equal to each other. This comparison is achieved by defining a value (epsilon) that is the smallest positive value, greater than zero, in which the absolute value of the difference between two values (numerator/denominator -dblValue) must be less than. In other words, by taking the absolute value of the difference between the fraction and the floating-point value and comparing it to a predetermined value passed to the epsilon argument, we can determine whether the floating-point value is a good approximation of the fraction.
Consider a comparison between the fraction 1/7 and its floating-point value, 0.14285714285714285. The following call to the IsApproximatelyEqualTo method indicates that there are not enough digits to the right of the decimal point in the floating-point value to be a good approximation of the fraction (there are 6 digits, although 7 are required):
Adding another digit of precision to the third parameter of this method nowindicates that this more precise number is what we require for a good approximation of the fraction 1/7:
When using the trigonometric functions of the Math class, all units are in radians. You have one or more angles measured in degrees and want to convert these to radians in order to use them with the members of the Math class.
Solution
To convert a value in degrees to radians, multiply it by π/180 (pi/180):
using System;
public static double ConvertDegreesToRadians (double degrees)
{
double radians = (Math.PI / 180) * degrees;
return (radians);
}
Discussion
All of the static trigonometric methods in the Math class use radians as their unit of measure for angles. It is very handy to have conversion routines to convert between radians and degrees, especially when a user is required to enter data in degrees rather than radians.
The equation for converting degrees to radians is shown here:
radians = (Math.PI / 180) * degrees
The static field Math.PI contains the constant π (pi).
1.3 Converting Radians to Degrees
Problem
When using the trigonometric functions of the Math class, all units are in radians; instead, you require a result in degrees.
Solution
To convert a value in radians to degrees, multiply it by 180/π (180/pi):
using System;
public static double ConvertRadiansToDegrees(double radians)
All of the static trigonometric methods in the Math class use radians as their unit of measure for angles. It is very handy to have conversion routines to convert between radians and degrees, especially when displaying degrees to a user is more informative than displaying radians.
The equation for converting radians to degrees is shown here:
degrees = (180 / Math.PI) * radians
The static field Math.PI contains the constant π (pi).
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
1.4 Using the Bitwise Complement Operator with Various Data Types
Problem >
The bitwise complement operator (~) is overloaded to work directly with int, uint, long, ulong, and enumeration data types consisting of the underlying types int, uint, long, and ulong. However, you need to perform a different bitwise complement operation on a data type.
Solution >
You must cast the resultant value of the bitwise operation to the type you wish to work. The following code demonstrates this technique with the byte data type:
byte y = 1; byte result = (byte)~y;
The value assigned to result is 254.
Discussion >
The following code shows incorrect use of the bitwise complement operator on the byte data type:
byte y = 1; Console.WriteLine("~y = " + ~y);
This code outputs the following surprising value:
-2
Clearly, the result from performing the bitwise complement of the byte variable is incorrect; it should be 254. In fact, byte is an unsigned data type, so it cannot be equal to a negative number. If we rewrite the code as follows:
byte y = 1; byte result = ~y;
we get a compile-time error: “Cannot implicitly convert type ‘int’ to ‘byte.’” This error message gives some insight into why this operation does not work as expected. To fix this problem, we must explicitly cast this value to a byte before we assign it to the result variable, as shown here:
byte y = 1; byte result = (byte)~y;
This cast is required because the bitwise operators are only overloaded to operate on six specific data types: int, uint, long, ulong, bool, and enumeration data types. When one of the bitwise operators is used on another data type, that data type is converted to the next closest data type of the six supported data types. Therefore, a byte data type is converted to an int before the bitwise complement operator is evaluated:
0x01 // byte y = 1; 0xFFFFFFFE // The value 01h is converted to an int and its // bitwise complement is taken 0xFE // The resultant int value is cast to its original byte data type
Notice that the int data type is a signed data type, unlike the byte data type. This is why we receive –2 for a result instead of the expected value 254. This conversion of the byte data type to its nearest equivalent is called numeric promotion. Numeric promotion also comes into play when you use differing data types with binary operators, including the bitwise binary operators.
Note: Numeric promotion is discussed in detail in the C# Language Specification document in section 7.2.6 (this document is found in the directory \Microsoft Visual Studio .NET 2003\Vc7\1033 belowthe .NET 2003 installation directory). Understanding hownumeric promotion works is essential when using operators on differing data types and when using operators with a data type that it is not overloaded to handle. Knowing this can save you hours of debugging time.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
You need a simple method to test a numeric value to determine whether it is even or odd.
Solution >
The solution is actually implemented as two methods. To test for an even integer value, use the following method:
public static bool IsEven(int intValue) {
return ((intValue & 1) == 0); }
To test for an odd integer value, use the following method:
public static bool IsOdd(int intValue) {
return ((intValue & 1) == 1); }
Discussion >
Every odd number always has its least-significant bit set to 1. Therefore, by checking whether this bit is equal to 1, we can tell whether it is an odd number. Conversely, testing the least-significant bit to see whether it is 0 can tell you whether it is an even number.
To test whether a value is even we AND the value in question with 1 and then determine whether the result is equal to zero. If the result is zero, we know that the value is an even number; otherwise, the value is odd. This operation is part of the IsEven method.
On the other hand, we can determine whether a value is odd by ANDing the value with 1, similar to howthe even test operates, and then determine whether the result is 1.If the result is set to 1, we know that the value is an odd number; otherwise, the value is even. This operation is part of the IsOdd method.
Note that you do not have to implement both the IsEven and IsOdd methods in your application, although implementing both methods might improve the readability of your code.
The methods presented here accept only 32-bit integer values. To allowthis method to accept other numeric data types, you can simply overload it to accept any other data types that you require. For example, if you need to also determine whether a 64bit integer is even, you could modify the IsEven method as follows:
public static bool IsEven(long longValue) {
return ((longValue & 1) == 0); }
Only the data type in the parameter list needs to be modified.
1.6 Obtaining the Most- or Least-Significant Bits of a Number >
Problem >
You have a 32-bit integer value that contains information in both its lower and upper 16 bits. You need a method to get the 16 most-significant bits and/or the 16 least-significant bits of this value.
Solution >
To get the most-significant bits (MSB) of an integer value, perform a bitwise and between it and the value shown in the following method:
public static int GetMSB(int intValue) {
return (intValue & 0xFFFF0000); }
To get the least-significant bits (LSB) of a value, use the following method:
public static int GetLSB(int intValue) {
return (intValue & 0x0000FFFF); }
This technique can easily be modified to work with other sizes of integers (e.g., 8-bit, 16-bit, or 64-bit); this trick is shown in the Discussion section.
Discussion >
In order to determine the values of the MSB of a number, use the following bitwise AND operation:
uint intValue = Int32.MaxValue;
uint MSB = intValue & 0xFFFF0000;
// MSB == 0xFFFF0000
This method simply ANDs the number to another number with all of the MSB set to 1. This method will zero out all of the LSB, leaving the MSB intact.
In order to determine the values of the LSB of a number, use the following bitwise AND operation:
uint intValue = Int32.MaxValue;
uint LSB = intValue & 0x0000FFFF;
// LSB == 0x0000FFFF
This method simply ANDs the number to another number with all of the LSB set to 1, which zeroes out all of the MSB, leaving the LSB intact.
The methods presented here accept only 32-bit integer values. To allowthis method to accept other numeric data types, you can simply overload this method to accept any other data types that you require. For example, if you need to also acquire the least-significant byte or most-significant byte of a 16-bit integer, you could modify the GetMSB method as follows:
public static int GetMSB(short shortValue) {
return (shortValue & 0xFF00); }
The GetLSB method is modified as shown here:
public static int GetLSB(short shortValue) {
return (shortValue & 0x00FF); }
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
You have a string containing a number in base2 (binary), base8 (octal), base10 (decimal), or base16 (hexadecimal). You need to convert this string to its equivalent integer value and display it in base10.
Solution >
Use the overloaded static Convert.ToInt32 method on the Convert class:
The static Convert.ToInt32 method has an overload that takes a string containing a number and an integer defining the base of this number. This method then converts the numeric string into an integer and returns this number displayed as base10.
The other static methods of the Convert class, such as ToByte, ToInt64, and ToInt16, also have this same overload, which accepts a number as a string and a base type for this number. Unfortunately, these methods convert from base2, base8, base10, and base16 only to a value of base10. They do not convert a value to any other base types.
See Also: See the “Convert Class” and “Converting with System.Convert” topics in the MSDN documentation.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
1.8 Determining Whether a String Is a Valid Number >
Problem >
You have a string that possibly contains a numeric value. You need to know whether this string contains a valid number.
Solution >
Use the static Parse method of any of the numeric types. For example, to determine whether a string contains an integer, use the following method:
public static bool IsNumeric(string str)
{ try {
str = str.Trim(); int foo = int.Parse(str); return (true);
} catch (FormatException) {
// Not a numeric value return (false); } }
If you instead needed to test whether a string is a floating-point value, change the second line in the try block to the following:
int foo = float.Parse(str);
A more compact way of testing a string for a numeric value—and one that does not have the overhead of throwing an exception—is to use the double.TryParse method:
public static bool IsNumericFromTryParse(string str)
{ double result = 0; return (double.TryParse(str, System.Globalization.NumberStyles.Float,
System.Globalization.NumberFormatInfo.CurrentInfo, out result)); }
The following IsNumericRegEx method does not incur the overhead of throwing an exception and it allows more flexibility in determining what type of number to test for. The IsNumericRegEx method tests for a number that can be signed/unsigned, contain a decimal point, and be displayed in scientific notation. This method accepts a string, possibly containing only a number, and returns true or false, depending on whether this string conforms to a numeric value:
private static Regex r = new Regex(@"^[\+\-]?\d*\.?[Ee]?[\+\-]?\d*$", RegexOptions.Compiled);
public static bool IsNumericRegEx(string str)
{
str = str.Trim();
Match m = r.Match(str);
return (m.Value);
}
Discussion >
This recipe shows three ways of determining whether a string contains only a numeric value. The IsNumeric method uses the Parse method, which throws a FormatException if the string cannot be converted to the appropriate type. The second method, IsNumericFromTryParse, uses the built-in double. TryParse method; this method also returns a value of type double if the string contains a valid number. The third method, IsNumericRegEx, uses a regular expression to determine whether the value of a string conforms to the various formats of a numeric value, such as an integer, a floating-point value, or a number written in scientific notation.
The method you choose can have a performance impact on your application. It’s not just a question of whether it’s called many times, it’s also about whether a valid number exists within the string passed in to these methods. In some scenarios IsNumeric will be fastest, even if you call it many times. In others, the IsNumericFromTryParse or IsNumericRegEx will be fastest. It all depends on how often the string will not be a valid number. If you expect the string to contain non-numeric data most of the time (or even half the time), you should consider using the IsNumericFromTryParse or IsNumericRegEx methods. Otherwise, the IsNumeric method will give you the best performance.
The IsNumericRegEx method uses the static IsMatch method on the Regex class to attempt to match a numeric value contained in the string str. This static method returns true if the match succeeds and false if it does not. Notice also that the regular expression starts with the ^ character and ends with the $ character. This forces the regular expression to match everything within the string, not just part of the string. If these characters were not included, the IsMatch method would return true for the following string "111 West Ave".
The IsNumericRegEx method has a drawback: it cannot determine the data type of the number contained within the string. For example, the int.Parse method will accept only strings that contain a valid integer value; likewise, the float.Parse method will accept only strings containing valid float values. The regular expression will return true for any type of numeric value matched. To enhance the regular expression, use the following method to determine whether a value is a non-floating-point number:
public static bool IsIntegerRegEx(string str)
{
str = str.Trim();
return (Regex.IsMatch(str, @"^[\+\-]?\d+$"));
}
We could also use the following method to determine whether the string contains an unsigned number:
public static bool IsUnsignedIntegerRegEx(string str)
{
str = str.Trim();
return (Regex.IsMatch(str, @"^\+?\d+$"));
}
Note also that the Trim method can be excluded if you want to find numbers within strings that contain no beginning or ending whitespace.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
You need to round a number to a whole number or to a specific number of decimal places.
Solution >
To round any number to its nearest whole number, use the overloaded static Math. Round method, which takes only a single arguments:
int x = (int)Math.Round(2.5555); // x == 3
If you need to round a floating-point value to a specific number of decimal places, use the overloaded static Math.Round method, which takes two arguments:
decimal x = Math.Round(2.5555, 2); // x == 2.56
Discussion >
The Round method is easy to use; however, you need to be aware of how the rounding operation works. The Round method follows the IEEE Standard 754, section 4 standard. This means that if the number being rounded is halfway between two numbers, the Round operation will always round to the even number. An example will show what this means to you:
decimal x = Math.Round(1.5); // x == 2 decimal y = Math.Round(2.5); // y == 2
Notice that 1.5 is rounded up to the nearest even whole number and 2.5 is rounded down to the nearest even whole number. Keep this in mind when using the Round method.
See Also: See Recipe 1.1; see the “Math Class” topic in the MSDN documentation.
1.10 Different Rounding Algorithms Problem >
The Math.Round method will round the value 1.5 to 2; however, the value 2.5 will also be rounded to 2 using this method. Always round to the greater number in this type of situation (e.g., round 2.5 to 3). Conversely, you might want to always round to the lesser number (e.g., round 1.5 to 1).
Solution >
Use the static Math.Floor method to always round up when a value is halfway between two whole numbers:
The static Static.Round method rounds to the nearest even number (see Recipe 1.9 for more information). However, there are some times that you do not want to round a number in this manner. The static Math.Floor method can be used to allowfor different manners of rounding.
Note that the methods used to round numbers in this recipe do not round to a specific number of decimal points; rather, they round to the nearest whole number.
See Also: See Recipe 1.9; see the “Math Class” topic in the MSDN documentation.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
You need to cast a value from a larger value to a smaller one, while gracefully handling conditions that result in a loss of information. For example, casting a long to an int results only in a loss of information if the long data type is greater than int. MaxSize.
Solution >
The simplest way to do this check is to use the checked keyword. The following method accepts two long data types and attempts to add them together. The result is stuffed into an int data type. If an overflowcondition exists, the OverflowException is thrown:
using System;
public void UseChecked(long lhs, long rhs) { int result = 0; try { result = checked((int)(lhs + rhs)); } catch (OverflowException e) { // Handle overflow exception here.
} }
This is the simplest method. However, if you do not want the overhead of throwing an exception and having to wrap a lot of code in try/catch blocks to handle the overflowcondition, you could use the MaxValue and MinValue fields of each type. A check using these fields can be done prior to the conversion to insure that no loss of information occurs. If this does occur, the code can inform the application that this cast will cause a loss of information. You can use the following conditional statement to determine whether sourceValue can be cast to a short without losing any information:
// Our two variables are declared and initialized int sourceValue = 34000; short destinationValue = 0;
// Determine if sourceValue will lose information in a cast to a short
if (sourceValue <= short.MaxValue && sourceValue >= short.MinValue) {
destinationValue = (short)sourceValue; } else {
// Inform the application that a loss of information will occur }
Instead of placing this conditional throughout your code, you can use the following overloaded methods to determine whether an integral type will lose data in a cast:
// Overloaded methods to check conversions from unsigned integral types // to any other type public static bool IsSafeToConvert(byte valueToConvert,
// Overloaded methods to check conversions from a float type // to any other type public bool IsSafeToConvert(float valueToConvert, string typeToConvertTo) {
A narrowing conversion occurs when a larger type is cast down to a smaller type. For instance, consider casting a value of type Int32 to a value of type Int16. If the Int32 value is smaller or equal to the Int16.MaxValue field and the Int32 value is higher or equal to the Int16.MinValue field, the cast will occur without error or loss of information. Loss of information occurs when the Int32 value is larger than the Int16. MaxValue field or the Int32 value is lower than the Int16.MinValue field. In either of these cases, the most-significant bits of the Int32 value would be truncated and discarded, changing the value after the cast.
If a loss of information occurs in an unchecked context, it will occur silently without the application noticing. This problem can cause some very insidious bugs that are hard to track down. To prevent this, check the value to be converted to determine whether it is within the lower and upper bounds of the type that it will be cast to. If the value is outside these bounds, then code can be written to handle this situation. This code could force the cast not to occur and/or possibly to inform the application of the casting problem. This solution can aid in the prevention of hard-to-find arithmetic bugs from appearing in your applications.
You should understand that both techniques shown in the Solution section are valid. However, the technique you use will depend on whether you expect to hit the overflowcase on a regular basis or only occasionally. If you expect to hit the overflow case quite often, you might want to choose the second technique of manually testing the numeric value. Otherwise, it might be easier to use the checked keyword, as in the first technique.
Note: In C#, code can run in either a checked or unchecked context; by default, the code runs in an unchecked context. In a checked context, any arithmetic and conversions involving integral types are examined to determine whether an overflow condition exists. If so, an OverflowException is thrown. In an unchecked context, no OverflowException will be thrown when an overflow condition exists.
A checked context can be set up by using the /checked{+} compiler switch, by setting the Check for Arithmetic Overflow/Underflow project property to true, or by using the checked keyword. An unchecked context can be set up using the /checked-compiler switch, by setting the Check for Arithmetic Overflow/Underflow project property to false, or by using the unchecked keyword.
Notice that floating-point and decimal types are not included in the code that handles the conversions to integral types in this recipe. The reason is that a conversion from any integral type to a float, double,or decimal will not lose any information; therefore, it is redundant to check these conversions.
In addition, you should be aware of the following when performing a conversion:
Casting from a float, double, or decimal type to an integral type results in the truncation of the fractional portion of this number.
Casting from a float or double to a decimal results in the float or double being rounded to 28 decimal places.
Casting from a double to a float results in the double being rounded to the nearest float value.
Casting from a decimal to a float or double results in the decimal being rounded to the resulting type (float or double).
Casting from int, uint, or long to a float could result in the loss of precision, but never magnitude.
Casting from long to a double could result in the loss of precision, but never magnitude.
See Also: See the “checked” keyword and “Checked and Unchecked” topics in the MSDN documentation.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!
1.14 Finding the Length of Any Three Sides of a Right Triangle
Problem >
You need to calculate the length of one side of a triangle when either the lengths of two sides are known or one angle and the length of a side are known.
Solution >
Use the Math.Sin, Math.Cos, and Math.Tan methods of the Math class to find the length of one side. The equations for these methods are as follows:
where theta is the known angle, and the oppositeSide variable is equal to the length of the side opposite to the angle theta, and the adjacentSide variable is equal to the length of the side adjacent to the angle theta. The hypotenuse variable is equal to the length of the hypotenuse of the triangle. See Figure 1-1.
In addition to these three static methods, the length of the hypotenuse of a right triangle can be calculated using the Pythagorean theorem. This theorem states that the hypotenuse of a right triangle is equal to the square root of the sum of the squares of the other two sides. This equation can be realized through the use of the Math.Pow and Math.Sqrt static methods of the Math class, as follows:
where xSide and ySide are the lengths of the two sides that are not the hypotenuse of the triangle.
Adjacent
Figure 1-1. A right triangle
Discussion >
Finding the length of a side of a right triangle is easy when an angle and the length of one of the sides are known. Using the trigonometric functions sine, cosine, and tangent, we can derive the lengths of either of the two unknown sides. The equations for sine, cosine, and tangent are defined here:
These equations give us two methods to find the length of each side of the triangle.
In the case where none of the angles are known, but the lengths of two of the sides are known, use the Pythagorean theorem to determine the length of the hypotenuse. This theorem is defined as follows:
where hypotenuse is equal to the length of the hypotenuse, and xSide and ySide are the lengths of the other two sides.
See Also: See the “Math Class” topic in the MSDN documentation.
1.15 Finding the Angles of a Right Triangle Problem >
You need to calculate an angle of a triangle when the lengths of two sides are known.
Solution >
Use the Math.Atan, Math.Acos, or Math.Asin static methods of the Math class. The following code calculates the angle theta and returns the value in radian measure:
where theta is the known angle value, the oppositeSide is equal to the length of the side opposite to the angle, and adjacentSide is equal to the length of the side adjacent to the angle. The hypotenuse is the length of the hypotenuse of the triangle. See Figure 1-1 in Recipe 1.14 for a graphical representation of these sides of a right triangle.
Discussion >
In some cases, we need to determine an angle of a right triangle when only the lengths of two sides are known. The three trigonometric functions arcsine, arccosine, and arctangent allowus to find any angle of a right triangle, given this information. The static methods Math.Atan, Math.Acos, and Math.Asin on the Math class provide the functionality to implement these trigonometric operations.
See Also: See Recipe 1.14; see the “Math Class” topic in the MSDN documentation.
If you've enjoyed what you've seen here, or to get more information, click on the "Buy the book!" graphic. Pick up a copy today!