Numeric Variable Types
C# provides several different types of numeric variables. You need different types of variables, because different numeric values have varying memory storage requirements and differ in the ease with which certain mathematical operations can be performed on them. Small integers (for example, 1, 199, and 8) require less memory to store, and your computer can perform mathematical operations (addition, multiplication, and so on) with such numbers very quickly. In contrast, large integers and values with decimal points require more storage space and more time for mathematical operations. By using the appropriate variable types, you ensure that your program runs as efficiently as possible.
The following sections break the different numeric data types into four categories:
- Integral
- Floating-point
- Decimal
- Boolean
Earlier in today's lesson, you learned that variables are stored in memory. Additionally, you learned that different types of information required different amounts of memory. The amount of memory used to store a variable is based on its data type. Listing 3.4 is a program that contains code beyond what you know right now; however, it provides you with the amount of information needed to store some of the different C# data types.
You must include extra information for the compiler when you compile this listing. This extra information, referred to as a flag to the compiler, can be included on the command line. Specifically, you need to add the /unsafe flag as shown:
csc /unsafe sizes.cs
If you are using an integrated development environment, you need to set the unsafe option as instructed by its documentation.
Listing 3.4 sizes.csMemory Requirements for Data Types
1: // sizes.cs--Program to tell the size of the C# variable types 2: //---------------------------------------------------------------- 3: 4: using System; 5: 6: class sizes 7: { 8: unsafe public static void Main() 9: { 10: Console.WriteLine( "\nA byte is {0} byte(s)", sizeof( byte )); 11: Console.WriteLine( "An sbyte is {0} byte(s)", sizeof( sbyte )); 12: Console.WriteLine( "A char is {0} byte(s)", sizeof( char )); 13: Console.WriteLine( "\nA short is {0} byte(s)", sizeof( short )); 14: Console.WriteLine( "An ushort is {0} byte(s)", sizeof( ushort )); 15: Console.WriteLine( "\nAn int is {0} byte(s)", sizeof( int )); 16: Console.WriteLine( "An uint is {0} byte(s)", sizeof( uint )); 17: Console.WriteLine( "\nA long is {0} byte(s)", sizeof( long )); 18: Console.WriteLine( "An ulong is {0} byte(s)", sizeof( ulong )); 19: Console.WriteLine( "\nA float is {0} byte(s)", sizeof( float )); 20: Console.WriteLine( "A double is {0} byte(s)", sizeof( double )); 21: Console.WriteLine( "\nA decimal is {0} byte(s)", sizeof( decimal )); 22: Console.WriteLine( "\nA boolean is {0} byte(s)", sizeof( bool )); 23: } 24: }
Output
A byte is 1 byte(s) An sbyte is 1 byte(s) A char is 2 byte(s) A short is 2 byte(s) An ushort is 2 byte(s) An int is 4 byte(s) An uint is 4 byte(s) A long is 8 byte(s) An ulong is 8 byte(s) A float is 4 byte(s) A double is 8 byte(s) A decimal is 16 byte(s) A boolean is 1 byte(s)
Analysis
Although you haven't learned all the data types yet, I believed it valuable to present this listing here. As you go through the following sections, refer to this listing and its output.
This listing uses a C# keyword called sizeof. The sizeof keyword tells you the size of a variable. In this listing, sizeof is used to show the size of the different data types. For example, to determine the size of an int, you can use:
sizeof(int)
If you had declared a variable called x, you could determine its sizewhich would actually be the size of its data typeby using the following code:
sizeof(x)
Looking at the output of Listing 3.4, you see that you have been given the number of bytes that are required to store each of the C# data types. For an int, you need 4 bytes of storage. For a short you need 2. The amount of memory used determines how big or small a number can be that is stored. You'll learn more about this in the following sections.
The sizeof keyword is not one that you will use very often; however, it is useful for illustrating the points in today's lesson. The sizeof keyword taps into memory to determine the size of the variable or data type. With C#, you avoid tapping directly into memory. In line 8, an extra keyword is addedunsafe. If you don't include the unsafe keyword, you get an error when you compile this program. For now, understand that the reason unsafe is added is because the sizeof keyword works directly with memory.
CAUTION
The C# keyword sizeof can be used; however, you should generally avoid it. The sizeof keyword sometimes accesses memory directly to find out the size. Accessing memory directly is something to be avoided in pure C# programs.
Integral Data Types
Until this point, you have been using one of the integral data typesint. Integral data types store integers. Recall that an integer is basically any numeric value that does not include a decimal or a fractional value. The numbers 1, 1,000, 56 trillion, and 534 are integral values.
C# provides nine integral data types, including the following:
- Integers (int and uint)
- Shorts (short and ushort)
- Longs (long and ulong)
- Bytes (byte and sbyte)
- Characters (char)
Integers
As you saw in Listing 3.4, an integer is stored in 4 bytes of memory. This includes both the int and uint data types. The int data type has been used in many of the programs you have seen so far. Although you might not have known it, this data type cannot store just any number. Rather, it can store any signed whole number that can be represented in 4 bytes or 32 bitsany number between 2,147,483,648 and 2,147,483,647.
A variable of type int is signed, which means it can be positive or negative. Technically, 4 bytes can hold a number as big as 4,294,967,295; however, when you take away one of the 32 bits to keep track of positive or negative, you can go only to 2,147,483,647. You can, however, also go to 2,147,483,648.
NOTE
As you learned earlier, information is stored in units called bytes. A byte is actually composed of 8 bits. A bit is the most basic unit of storage in a computer. A bit can have one of two values0 or 1. Using bits and the binary math system, you can store numbers in multiple bits. In Appendix C, "Working with Number Systems," you can learn the details of binary math.
If you want to use a type int to go higher, you can make it unsigned. An unsigned number can only be positive. The benefit should be obvious. The uint data type declares an unsigned integer. The net result is that a uint can store a value from 0 to 4,294,967,295.
What happens if you try to store a number that is too big? What about storing a number with a decimal point into an int or uint? What happens if you try to store a negative number into an uint? Listing 3.5 answers all three questions.
Listing 3.5 int_conv.csDoing Bad Things
1: // int_conv.cs 2: // storing bad values. Program generates errors and won't compile. 3: //---------------------------------------------------------------- 4: 5: using System; 6: 7: class int_conv 8: { 9: public static void Main() 10: { 11: int val1, val2; // declare two integers 12: uint pos_val; // declare an unsigned int 13: 14: val1 = 1.5; 15: val2 = 9876543210; 16: pos_val = -123; 17: 18: Console.WriteLine( "val1 is {0}", val1); 19: Console.WriteLine( "val2 is {0}", val2); 20: Console.WriteLine( "pos_val is {0}", pos_val); 21: } 22: }
Output
int_conv.cs(14,15): error CS0029: Cannot implicitly convert type 'double' to 'int' int_conv.cs(15,15): error CS0029: Cannot implicitly convert type 'long' to 'int' int_conv.cs(16,18): error CS0031: Constant value '-123' cannot be converted to a 'uint'
WARNING
This program gives compiler errors.
Analysis
This program will not compile. As you can see, the compiler catches all three problems that were questioned. In line 14 you try to put a number with a decimal point into an integer. In line 15 you try to put a number that is too big into an integer. Remember, the highest number that can go into an int is 2,147,483,647. Finally, in line 16, you try to put a negative number into an unsigned integer (uint). As the output shows, the compiler catches each of these errors and prevents the program from being created.
Shorts
The int and uint data types used 4 bytes of memory for each variable declared. There are a number of times when you don't need to store numbers that are that big. For example, you don't need big numbers to keep track of the day of the week (numbers 1 to 7), to store a person's age, or to track the temperature to bake a cake.
When you want to store a whole number and you want to save some memory, you can use short and ushort. A short, like an int, stores a whole number. Unlike an int, it is only 2 bytes instead of 4. If you look at the output from Listing 3.4, you see that sizeof returned 2 bytes for both short and ushort. If you are storing both positive and negative numbers, you'll want to use short. If you are storing only positive and you want to use the extra room, you'll want to use ushort. The values that can be stored in a short are from 32,768 to 32,767. If you use a ushort, you can store whole numbers from 0 to 65535.
Longs
If int and uint are not big enough for what you want to store, there is another data type to uselong. As with short and int, there is also an unsigned version of the long data type called ulong. Looking at the output from Listing 3.4, you can see that long and ulong each use 8 bytes of memory. This gives them the capability of storing very large numbers. A long can store numbers from 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. An ulong can store a number from 0 to 18,446,744,073,709,551,615.
Bytes
As you have seen, you can store whole numbers in data types that take 2, 4, or 8 bytes of memory. For those times when your needs are very small, you can store a whole number in a single byte. To keep things simple, the data type that uses a single byte of memory for storage is called a byte! As with the previous integers, there is both a signed version, sbyte, and an unsigned version, byte. A sbyte can store a number from 128 to 127. An unsigned byte can store a number from 0 to 255.
Characters
In addition to numbers, you will often want to store characters. Characters are letters, such as A, B, or C, or even extended characters such as the smiley face. Additional characters that you might want to store are characters such as $, %, or *. You might even want to store foreign characters.
A computer does not recognize characters. It can recognize only numbers. To get around this, all characters are stored as numeric values. To make sure that everyone uses the same values, a standard was created called Unicode. Within Unicode, each character and symbol is represented by a single whole number. This is why the character data type is considered an integral type.
To know that numbers should be used as characters, you use the data type char. A char is a number stored in 2 bytes of memory that is interpreted as a character. Listing 3.6 presents a program that uses char values.
Listing 3.6 chars.csWorking with Characters
1: // chars.cs 2: // A listing to print out a number of characters and their numbers 3: //---------------------------------------------------------------- 4: 5: using System; 6: 7: class chars 8: { 9: public static void Main() 10: { 11: int ctr; 12: char ch; 13: 14: Console.WriteLine("\nNumber Value\n"); 15: 16: for( ctr = 60; ctr <= 95; ctr = ctr + 1) 17: { 18: ch = (char) ctr; 19: Console.WriteLine( "{0} is {1}", ctr, ch); 20: } 21: } 22: }
Output
Number Value 60 is < 61 is = 62 is > 63 is ? 64 is @ 65 is A 66 is B 67 is C 68 is D 69 is E 70 is F 71 is G 72 is H 73 is I 74 is J 75 is K 76 is L 77 is M 78 is N 79 is O 80 is P 81 is Q 82 is R 83 is S 84 is T 85 is U 86 is V 87 is W 88 is X 89 is Y 90 is Z 91 is [ 92 is \ 93 is ] 94 is ^ 95 is _
Analysis
This listing displays a range of numeric values and their character equivalents. In line 11 an integer is declared called ctr. This variable is used to cycle through a number of integers. Line 12 declares a character variable called ch. Line 14 prints headings for the information that will be displayed.
Line 16 contains something new. For now, don't worry about fully understanding this line of code. On Day 5, "Control Statements," you learn all the glorious details. For now know that this line sets the value of ctr to 60. It then runs lines 18 and 19 before adding 1 to the value of ctr. It keeps doing this until ctr is no longer less than or equal to 95. The end result is that lines 18 and 19 are run using the ctr with the value of 60, then 61, then 62, and on and on until ctr is 95.
Line 18 sets the value of ctr (first 60) and places it into the character variable, ch. Because ctr is an integer, you have to tell the computer to convert the integer to a character, which the (char) statement does. You'll learn more about this later.
Line 19 prints the values stored in ctr and ch. As you can see, the integer ctr prints as a number. The value of ch, however, does not print as a number; it prints as a character. As you can see from the output of this listing, the character A is represented by the value 65. The value of 66 is the same as the character B.
NOTE
A computer actually recognizes only 1s and 0s (within bits). It recognizes these as on or off values (or positive charges versus negative charges). A binary number system is one that uses 1s and 0s to represent its numbers. Appendix C explains the binary number system.
Character Literals
How can you assign a character to a char variable? You place the character between single quotes. For example, to assign the letter a to the variable my_char, you use the following:
my_char = 'a';
In addition to assigning regular characters, there are also several extended characters you will most likely want to use. You have actually been using one extended character in a number of your listings. The \n that you've been using in your listings is an extended character. This prints a newline character. Table 3.2 contains some of the most common characters you might want to use. Listing 3.7 shows some of these special characters in action.
Table 3.2 Extended Characters
Characters |
Meaning |
\b |
Backspace |
\n |
Newline |
\t |
Horizontal tab |
\\ |
Backslash |
\' |
Single quote |
\" |
Double quote |
NOTE
The extended characters in Table 3.2 are often called escape characters because the slash "escapes" from the regular text and indicates that the following character is special (or extended).
Listing 3.7 chars_table.csThe Special Characters
1: // chars_table.cs 2: //---------------------------------------------------------------- 3: 4: using System; 5: 6: class chars_table 7: { 8: public static void Main() 9: { 10: char ch1 = 'Z'; 11: char ch2 = 'x'; 12: 13: Console.WriteLine("This is the first line of text"); 14: Console.WriteLine("\n\n\nSkipped three lines"); 15: Console.WriteLine("one\ttwo\tthree <-tabbed"); 16: Console.WriteLine(" A quote: \' \ndouble quote: \""); 17: Console.WriteLine("\n ch1 = {0} ch2 = {1}", ch1, ch2); 18: } 19: }
Output
This is the first line of text Skipped three lines one two three <-tabbed A quote: ' double quote: " ch1 = Z ch2 = x
Analysis
This listing illustrates two concepts. First, in line 10 and 11 you see how a character can be assigned to a variable of type char. It is as simple as including the character in single quotes. In lines 13 to 17, you see how to use the extended characters. There is nothing special about line 13. Line 14 prints three newlines followed by some text. Line 15 prints one, two, and three, separated by tabs. Line 16 displays a single quote and a double quote. Notice that there are two double quotes in a row at the end of this line. Finally, line 17 prints the values of ch1 and ch2.
Floating Point
Not all numbers are whole numbers. For those times when you need to use numbers that might have decimals, you need to use different data types. As with storing whole numbers, there are different data types you can use, depending on the size of the numbers you are using and the amount of memory you want to use. The two primary types are float and double.
float
A float is a data type for storing numbers with decimal places. For example, in calculating the circumference or area of a circle, you often end up with a result that is not a whole number. Any time you need to store a number such as 1.23 or 3.1459, you need a nonintegral data type.
The float data type stores numbers in 4 bytes of memory. As such, it can store a number from approximately 1.5_1045 to 3.4_1038.
NOTE
1038 is equivalent to 10_10, 37 times. The result is 1 followed by 38 zeros, or 100,000,000,000,000,000,000,000,000,000,000,000,000. 1045 is 10÷10, 44 times. The result is 44 zeros between a decimal point and a 1, or .000000000000000000000000000000000000000000001.
WARNING
A float can retain only about 7 digits of precision, which means it is not uncommon for a float to be off by a fraction. For example, subtracting 9.90 from 10.00 might result in a number different from .10. It might result in a number closer to .099999999. Generally such rounding errors are not noticeable.
double
Variables of type double are stored in 8 bytes of memory. This means they can be much bigger than a float. A double can generally be from 5.0_10324 to 1.7_10308. The precision of a double is generally from 15 to 16 digits.
NOTE
C# supports the 4-byte precision (32 bits) and 8-byte precision (64 bits) of the IEEE 754 format, so certain mathematical functions return specific values. If you divide a number by 0, the result is infinity (either positive or negative). If you divide 0 by 0, you get a Not-a-Number value. Finally, 0 can be both positive and negative. For more on this, check your C# documentation.
Decimal
C# provides another data type that can be used to store special decimal numbers. This is the decimal data type. This data type was created for storing numbers with greater precision. When you store numbers in a float or double, you can get rounding errors. For example, storing the result of subtracting 9.90 from 10.00 in a double could result in the string 0.099999999999999645 instead of .10. If this math is done with decimal values, the .10 is stored.
TIP
If you are calculating monetary values or doing financial calculations where precision is important, you should use a decimal instead of a float or a double.
A decimal number uses 16 bytes to store numbers. Unlike the other data types, there is not an unsigned version of decimal. A decimal variable can store a number from 1.0_10-28 to approximately 7.9_1028. It can do this while maintaining precision to 28 places.
Boolean
The last of the simple data types is the Boolean. Sometimes you need to know whether something is on or off, true or false, yes or no. Boolean numbers are generally set to one of two values: 0 or 1.
C# has a Boolean data type called a bool. As you can see in Listing 3.4, a bool is stored in 1 byte of memory. The value of a bool is either true or false, which are C# keywords. This means you can actually store true and false in a data type of bool.
"Yes," "no," "on," and "off" are not keywords in C#. This means you cannot set a Boolean variable to these values. Instead, you must use true or false.
Checking Versus Unchecking
Earlier in today's lesson you learned that if you put a number that is too big into a variable, an error is produced. There are times when you might not want an error produced. In those cases, you can have the compiler avoid checking the code. This is done with the unchecked keyword. Listing 3.8 illustrates this.
Listing 3.8 unchecked.csMarking Code as Unchecked
1: // unchecked.cs 2: //---------------------------------------------------------------- 3: 4: using System; 5: 6: class sizes 7: { 8: public static void Main() 9: { 10: int val1 = 2147483647; 11: int val2; 12: 13: unchecked 14: { 15: val2 = val1 + 1; 16: } 17: 18: Console.WriteLine( "val1 is {0}", val1); 19: Console.WriteLine( "val2 is {0}", val2); 20: } 21: }
Output
val1 is 2147483647 val2 is -2147483648
Analysis
This listing uses unchecked in line 13. The brackets on line 14 and 16 enclose the area to be unchecked. When you compile this listing, you do not get any errors. When you run the listing, you get what might seem like a weird result. The number 2,147,483,647 is the largest number that a signed int variable can hold. As you see in line 10, this maximum value has been assigned to var1. In line 15, the unchecked line, 1 is added to what is already the largest value var1 can hold. Because this line is unchecked, the program continues to operate. The result is that the value stored in var1 rolls to the most negative number.
This operation is similar to the way an odometer works in a car. When the mileage gets to the maximum, such as 999,999, adding 1 more mile (or kilometer) sets the odometer to 000,000. It isn't a new car with no miles, it is simply a car that no longer has a valid value on its odometer. Rather than rolling to 0, a variable is going to roll to the lowest value it can store. In this listing, that value is 2,147,483,648.
Change line 13 to the following and recompile and run the listing:
13: checked
The program compiled, but will it run? Executing the program causes an error. If you are asked to run your debugger, you'll want to say no. The error you get will be similar to the following:
Exception occurred: System.OverflowException: An exception of type System.OverflowException was thrown. at sizes.Main()
On later days, you'll see how to deal with this error in your program. For now, you should keep in mind that if you believe there is a chance to put an invalid value into a variable, you should force checking to occur.
Data Types Simpler than .NET
The C# data types covered so far are considered simple data types. The simple data types are sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, bool, and decimal. On Day 1 and Day 2, "Understanding C# Programs," you learned that C# programs execute on the Common Language Runtime (CLR). Each of these data types corresponds directly to a data type that the CLR uses. Each of these types is considered simple because there is a direct relationship to the types available in the CLR and thus in the .NET Framework. Table 3.3 presents the .NET equivalent of the C# data types.
Table 3.3 C# and .NET Data Types
C# Data Type |
.NET Data Type |
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
If you want to declare an integer using the .NET equivalent declarationeven though there is no good reason to do soyou use the following:
System.Int32 my_variable = 5;
As you can see, System.Int32 is much more complicated than simply using int. Listing 3.9 shows the use of the .NET data types.
Listing 3.9 net_vars.csUsing the .NET Data Types
1: // net_vars 2: // Using a .NET data declaration 3: //----------------------------------------------- 4: 5: using System; 6: 7: class net_vars 8: { 9: public static void Main() 10: { 11: 12: System.Int32 my_variable = 4; 13: System.Double PI = 3.1459; 14: 15: Console.WriteLine("\nmy_variable is {0}", my_variable ); 16: Console.WriteLine("\nPI is {0}", PI ); 17: } 18: }
Output
my_variable is 4 PI is 3.1459
Analysis
Lines 12 and 13 declare an int and a double. Lines 15 and 16 print these values. This listing operates like those you've seen earlier, except it uses the .NET data types.
In your C# programs, you should use the simple data types rather than the .NET types. All the functionality that the .NET types have is available to you in the simpler commands that C# provides. You should, however, understand that the simple C# data types translate to .NET equivalents. You'll find that all other programming languages that work with the Microsoft .NET types also have data types that translate to these .NET types.
NOTE
The Common Type System (CTS) is a set of rules that data types within the CLR must adhere to. The simple data types within C# adhere to these rules, as do the .NET data types. If a language follows the CTS in creating its data types, the data created and stored should be compatible with other programming languages that also follow the CTS.