The basic numeric types in C# have keywords associated with them. These types include integer types, floating-point types, and a decimal type to store large numbers with a high degree of accuracy.
There are eight C# integer types. This variety allows you to select a data type large enough to hold its intended range of values without wasting resources. Table 2.1 lists each integer type.
Included in Table 2.1 (and in Tables 2.2 and 2.3) is a column for the full name of each type. All the fundamental types in C# have a short name and a full name. The full name corresponds to the type as it is named in the Base Class Library (BCL). This name is the same across all languages and it uniquely identifies the type within an assembly. Because of the fundamental nature of primitive types, C# also supplies keywords as short names or abbreviations to the full names of fundamental types. From the compiler's perspective, both names are exactly the same, producing exactly the same code. In fact, an examination of the resulting CIL code would provide no indication of which name was used.
Floating-Point Types (float, double)
Floating-point numbers have varying degrees of precision. If you were to read the value of a floating-point number to be 0.1, it could very easily be 0.099999999999999999 or 0.1000000000000000001 or some other number very close to 0.1. Alternatively, a large number such as Avagadro's number, 6.02E23, could be off by 9.9E9, which is something also exceptionally close to 6.02E23, considering its size. By definition, the accuracy of a floating-point number is in proportion to the size of the number it contains. Accuracy, therefore, is determined by the number of significant digits, not by a fixed value such as ±0.01.
C# supports the two floating-point number types listed in Table 2.2.
Binary numbers appear as base 10 (denary) numbers for human readability. The number of bits (binary digits) converts to 15 decimal digits, with a remainder that contributes to a sixteenth decimal digit as expressed in Table 2.2. Specifically, numbers between 1.7 * 10307 and less than 1 * 10308 have only 15 significant digits. However, numbers ranging from 1 * 10308 to 1.7 * 10308 will have 16 significant digits. A similar range of significant digits occurs with the decimal type as well.
C# contains a numeric type with 128-bit precision (see Table 2.3). This is suitable for large and precise calculations, frequently financial calculations.
Unlike floating-point numbers, the decimal type maintains exact precision for all denary numbers within its range. With the decimal type, therefore, a value of 0.1 is exactly 0.1. However, while the decimal type has greater precision than the floating-point types, it has a smaller range. Thus, conversions from floating-point types to the decimal type may result in overflow errors. Also, calculations with decimal are slightly slower.
A literal value is a representation of a constant value within source code. For example, if you want to have System.Console.WriteLine() print out the integer value 42 and the double value 1.618034 (Phi), you could use the code shown in Listing 2.1.
Listing 2.1. Specifying Literal Values
Output 2.1 shows the results of Listing 2.1.
By default, when you specify a literal number with a decimal point, the compiler interprets it as a double type. Conversely, an integer value (with no decimal point) generally defaults to an int, assuming the value is not too large to be stored in an integer. If the value is too large, then the compiler will interpret it as a long. Furthermore, the C# compiler allows assignment to a numeric type other than an int, assuming the literal value is appropriate for the target data type. For example, short s = 42 and byte b = 77 are allowed. However, this is appropriate only for literal values; b = s is not appropriate without additional syntax, as discussed in the section Conversions between Data Types, later in this chapter.
As previously discussed in the section Fundamental Numeric Types, there are many different numeric types in C#. In Listing 2.2, a literal value is placed within C# code. Since numbers with a decimal point will default to the double data type, the output, shown in Output 2.2, is 1.61803398874989 (the last digit, 5, is missing), corresponding to the expected accuracy of a double.
Listing 2.2. Specifying a Literal double
To view the intended number with its full accuracy, you must declare explicitly the literal value as a decimal type by appending an m (or M) (see Listing 2.3 and Output 2.3).
Listing 2.3. Specifying a Literal decimal
Now the output of Listing 2.3 is as expected: 1.618033988749895. Note that d is for double. The m used to identify a decimal corresponds to its frequent use in monetary calculations.
You can also add a suffix to explicitly declare a literal as float or double by using the f and d suffixes, respectively. For integer data types, the suffixes are u, l, lu, and ul. The type of an integer literal can be determined as follows.
Note that suffixes for literals are case insensitive. However, for long, uppercase is generally preferred because of the similarity between the lowercase letter l and the digit 1.
In some situations, you may wish to use exponential notation instead of writing out several zeroes before or after the decimal point. To use exponential notation, supply the e or E infix, follow the infix character with a positive or negative integer number, and complete the literal with the appropriate data type suffix. For example, you could print out Avagadro's number as a float, as shown in Listing 2.4 and Output 2.4.
Listing 2.4. Exponential Notation
In all discussions of literal numeric values so far, I have covered only decimal type values. C# also supports the ability to specify hexadecimal values. To specify a hexadecimal value, prefix the value with 0x and then use any hexadecimal digit, as shown in Listing 2.5.
Listing 2.5. Hexadecimal Literal Value
Output 2.5 shows the results of Listing 2.5.
Note that this code still displays 42, not 0x002A.