The Essential .NET Data Types
- 8/15/2011
In Chapter 1, “Beginners All-Purpose Symbolic Instruction Code,” you learned about variables, including what the different types of variables are, and you saw a few examples of declaring and using variables of various data types. However, there’s still a lot to learn about the base data types, which are part of the Microsoft .NET Framework. These are the data types that you’ll be using over and over as a developer.
The base data types are mainly primitive data types, such as Integer, Double, Date, String, and so on, with which you’re already familiar. They are an integral part of the C# and Microsoft Visual Basic.NET languages; you can recognize them easily because the Microsoft Visual Studio code editor colors them blue as soon as you declare them.
Base data types include all the types that are part of any .NET programming language, and they expose the following characteristics:
-
They can be used as variable values. You can set the value of each base data type directly. For example, specifying a value of 123.324D identifies a variable of type Decimal with a specific magnitude.
-
They can be used as constant values. It is possible to declare a base data type as a constant. When a certain expression is exclusively defined as a constant (such as the expression 123.32D*2+100.23D), it can be evaluated during compilation.
-
They are recognized by the processor. Many operations and functions of certain base data types can be delegated by the .NET Framework directly to the processor for execution. This means that no program logic is necessary to calculate an arithmetic expression (for example, a floating-point division). The processor can do this by itself, so such operations are therefore very fast. Most of the operations of the data types Byte, Short, Integer, Long, Single, Double, and Boolean fall into this category.
Numeric Data Types
For processing numbers, Visual Basic provides the data types Byte, Short, Integer, Long, Single, Double, and Decimal. The data types SByte, UShort, Uinteger, and ULong were introduced with Visual Basic 2005. They differ in the range, the precision, or scale, of the values that they can represent (for instance, the number of decimal points), and their memory requirements.
Defining and Declaring Numeric Data Types
All numeric data types (as with all value types) are declared without the keyword New. Constant values can be directly assigned to numeric types in the program’s code. There are certain keywords that define the type of a constant value. A variable of the type Double can, for example, be declared with the following statement:
Dim aDouble As Double
You can then use aDouble immediately in the code. Numeric variables can be assigned values, which are strings made up of digits followed (if necessary) by a type literal. In the following example, the type literal is the D following the actual value:
aDouble = 123.3D
Just as with other base data types, declaration and assignment can take place in a single statement. Therefore, you can replace the two preceding statements with the following single statement:
Dim aDouble As Double = 123.3D
If you use local type inference (refer to Chapter 1 for more information), you don’t even need to specify the type or procedure level (for example, in a Sub, a Function or a Property, but not for module or class variables); you can let the compiler infer the correct type from the type of the expression or the constant from which the variable is assigned:
Dim aDouble = 123.3D
The example applies to all other numeric data types equally—the type literal can of course differ from type to type.
Table 6-1 Type Literals and Variable Type Declaration Characters of the Base Data Types in Visual Basic 2010
Type name |
Type declaration character |
Type literal |
Example |
Byte |
– |
– |
Dim var As Byte = 128 |
SByte |
– |
– |
Dim var As SByte = -5 |
Short |
– |
S |
Dim var As Short = -32700S |
UShort |
– |
US |
Dim var As UShort = 65000US |
Integer |
% |
I |
Dim var% = -123I or Dim var As Integer = -123I |
UInteger |
– |
UI |
Dim var As UInteger = 123UI |
Long |
& |
L |
Dim var& = -123123L or Dim var As Long = -123123L |
ULong |
– |
UL |
Dim var As ULong = 123123UL |
Single |
! |
F |
Dim var! = 123.4F or Dim var As Single = 123.4F |
Double |
# |
R |
Dim var# = 123.456789R or Dim var As Double = 123.456789R |
Decimal |
@ |
D |
Dim var@ = 123.456789123D or Dim var As Decimal = 123.456789123D |
Boolean |
– |
– |
Dim var As Boolean = True |
Char |
– |
C |
Dim var As Char = "A"c |
Date |
– |
#MM/dd/yyyy HH:mm:ss# or #MM/dd/yyyy hh:mm:ss am/pm# |
Dim var As Date = #12/24/2008 04:30:15 PM# |
Object |
– |
– |
In a variable of the type Object, any type can be boxed or referenced by it |
String |
$ |
“String” |
Dim var$ = "String" or Dim var As String = "String" |
Delegating Numeric Calculations to the Processor
The example that follows shows how to leave some mathematical operations to the processor. To do this, you need to know that, due to its computational accuracy, the Decimal type is calculated not by the floating-point unit of the processor, but by the corresponding program code of the Base Class Library (unlike Double or Single).
Before you run the following sample code, press F9 to set a breakpoint in the highlighted line.
Public Class Primitives Public Shared Sub main() Dim locDouble1, locDouble2 As Double Dim locDec1, locDec2 As Decimal locDouble1 = 123.434D locDouble2 = 321.121D locDouble2 += 1 locDouble1 += locDouble2 Console.WriteLine("Result of the Double calculation: {0}", locDouble1) locDec1 = 123.434D locDec2 = 321.121D locDec2 += 1 locDec1 += locDec2 Console.WriteLine("Result of the Decimal calculation: {0}", locDec1) End Sub End Class
When you start the program, it will stop at the line with the breakpoint. On the Debug/Window menu, select Disassembly. This window will display what the Just-in-Time (JIT) compiler has done with the program, which is first compiled in the IML, as shown in the code that follows.
Dim locDouble1, locDouble2 As Double Dim locDec1, locDec2 As Decimal locDouble1 = 123.434D 00000055 movsd xmm0,mmword ptr [000002E8h] 0000005d movsd mmword ptr [rsp+50h],xmm0 locDouble2 = 321.121D 00000063 movsd xmm0,mmword ptr [000002F0h] 0000006b movsd mmword ptr [rsp+58h],xmm0 locDouble2 += 1 00000071 movsd xmm0,mmword ptr [000002F8h] 00000079 addsd xmm0,mmword ptr [rsp+58h] 0000007f movsd mmword ptr [rsp+58h],xmm0
The numbered lines correspond to assembly language statements and show the operations required by the processor to execute the preceding Visual Basic statement. These are the statements that the processor understands, and no matter what language you’re using to write your applications, at the end of the day, your code must be translated into a series of assembly statements. That’s what compilers do for you. The listings in this section demonstrate (if nothing else) what a “high-level” language is all about.
Unlike what you might have expected, no special methods of the Double structure were called. Instead, the addition happens via the floating-point functionality of the processor itself (addsd,2 marked in bold). It’s quite different further down in the disassembly, where the same operations are carried out by using the Decimal data type:
locDec2 += 1 0000017e mov rcx,129F1180h 00000188 mov rcx,qword ptr [rcx] 0000018b add rcx,8 0000018f mov rax,qword ptr [rcx] 00000192 mov qword ptr [rsp+000000A8h],rax 0000019a mov rax,qword ptr [rcx+8] 0000019e mov qword ptr [rsp+000000B0h],rax 000001a6 lea rcx,[rsp+000000A8h] 000001ae mov rax,qword ptr [rcx] 000001b1 mov qword ptr [rsp+000000E0h],rax 000001b9 mov rax,qword ptr [rcx+8] 000001bd mov qword ptr [rsp+000000E8h],rax 000001c5 lea rcx,[rsp+40h] 000001ca mov rax,qword ptr [rcx] 000001cd mov qword ptr [rsp+000000D0h],rax 000001d5 mov rax,qword ptr [rcx+8] 000001d9 mov qword ptr [rsp+000000D8h],rax 000001e1 lea r8,[rsp+000000E0h] 000001e9 lea rdx,[rsp+000000D0h] 000001f1 lea rcx,[rsp+000000B8h] 000001f9 call FFFFFFFFEF381460 // Here the addition routine of ... 000001fe mov qword ptr [rsp+00000128h],rax 00000206 lea rcx,[rsp+000000B8h] 0000020e mov rax,qword ptr [rcx] 00000211 mov qword ptr [rsp+40h],rax 00000216 mov rax,qword ptr [rcx+8] 0000021a mov qword ptr [rsp+48h],rax locDec1 += locDec2 0000021f lea rcx,[rsp+40h] 00000224 mov rax,qword ptr [rcx] 00000227 mov qword ptr [rsp+00000110h],rax 0000022f mov rax,qword ptr [rcx+8] 00000233 mov qword ptr [rsp+00000118h],rax 0000023b lea rcx,[rsp+30h] 00000240 mov rax,qword ptr [rcx] 00000243 mov qword ptr [rsp+00000100h],rax 0000024b mov rax,qword ptr [rcx+8] 0000024f mov qword ptr [rsp+00000108h],rax 00000257 lea r8,[rsp+00000110h] 0000025f lea rdx,[rsp+00000100h] 00000267 lea rcx,[rsp+000000F0h] 0000026f call FFFFFFFFEF381460 // ... Decimal is called. Here also. . . .
The preceding code demonstrates that the addition requires many more preparations. This is because the values to be added must first be copied to the stack. The actual addition isn’t performed by the processor itself, but by the corresponding routines of the Base Class Library (BCL), which is called by using the Call statement, shown in the disassembly (highlighted in bold).
Numeric Data Types at a Glance
The following short sections describe the use of numeric data types and the range of values that you can represent with each numeric type.
Byte
.NET data type: System.Byte
Represents: Integer values (numbers without decimal points) in the specified range
Range: 0 to 255
Type literal: Not available
Memory requirements: 1 byte
Declaration and example assignment:
Dim aByte As Byte aByte = 123
Description: This data type stores only unsigned positive numbers in the specified numeric range.
CLS-compliant: Yes
Conversion of other numeric types: CByte(objVar) or Convert.ToByte(objVar)
aByte = CByte(123.45D) aByte = Convert.ToByte(123.45D)
SByte
.NET data type: System.SByte
Represents: Integer values (numbers without decimal points) in the specified range
Range: –128 to 127
Type literal: Not available
Memory requirements: 1 byte
Declaration and example assignment:
Dim aByte As SByte aByte = 123
Description: This data type saves negative and positive numbers in the specified numeric range.
CLS-compliant: No
Conversion of other numeric types: CSByte(objVar) or Convert.ToSByte(objVar)
aByte = CSByte(123.45D) aByte = Convert.ToSByte(123.45D)
Short
.NET data type: System.Int16
Represents: Integer values (numbers without decimal points) in the specified range
Range: –32,768 to 32,767
Type literal: S
Memory requirements: 2 bytes
Declaration and example assignment:
Dim aShort As Short aShort = 123S
Description: This data type stores signed numbers (both negative and positive) in the specified range. Conversion to the Byte data type can cause an OutOfRangeException, due to the larger scope of Short.
CLS-compliant: Yes
Conversion of other numeric types: CShort(objVar) or Convert.ToInt16(objVar)
'Decimal points are truncated aShort = CShort(123.45D) aShort = Convert.ToInt16(123.45D)
UShort
.NET data type: System.UInt16
Represents: Positive integer values (numbers without decimal points) in the specified range
Range: 0 to 65,535
Type literal: US
Memory requirements: 2 bytes
Declaration and example assignment:
Dim aUShort As UShort aUShort = 123US
Description: This data type stores unsigned numbers (positive only) in the specified numeric range. Conversion to the Byte or Short data types can cause an OutOfRangeException, due to the (partially) larger scope of Byte or Short.
CLS-compliant: No
Conversion of other numeric types: CUShort(objVar) or Convert.ToUInt16(objVar)
'Decimal points are truncated aUShort = CUShort(123.45D) aUShort = Convert.ToUInt16(123.45D)
Integer
.NET data type: System.Int32
Represents: Integer values (numbers without decimal points) in the specified range
Range: –2,147,483,648 to 2,147,483,647
Type literal: I
Memory requirements: 4 bytes
Declaration and example assignment:
Dim anInteger As Integer Dim anDifferentInteger% ' also declared a integer anInteger = 123I
Description: This data type stores signed numbers (both negative and positive) in the specified range. Conversion to the Byte, Short, and UShort data types can cause an OutOfRangeException, due to the larger scope of Integer. By appending the “%” (percent) character to a variable, the Integer type for the variable can be forced. However, in the interest of better programming style, you should avoid this technique.
CLS-compliant: Yes
Conversion of other numeric types: CInt(objVar) or Convert.ToInt32(objVar)
anInteger = CInt(123.45D) anInteger = Convert.ToInt32(123.45D)
UInteger
.NET data type: System.UInt32
Represents: Positive integer values (numbers without decimal points) in the specified range
Range: 0 to 4,294,967,295
Type literal: UI
Memory requirements: 4 bytes
Declaration and example assignment:
Dim aUInteger As UInteger aUInteger = 123UI
Description: This data type stores unsigned numbers (positive only) in the specified range. Conversion to the data types Byte, Short, Ushort, and Integer can cause an OutOfRangeException, due to the (partially) larger scope of UInteger.
CLS-compliant: No
Conversion of other numeric types: CUInt(objVar) or Convert.ToUInt32(objVar)
aUInteger = CUInt(123.45D) aUInteger = Convert.ToUInt32(123.45D)
Long
.NET data type: System.Int64
Represents: Integer values (numbers without decimal points) in the specified range.
Range: –9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
Type literal: L
Memory requirements: 8 bytes
Declaration and example assignment:
Dim aLong As Long Dim aDifferentLong& ' also defined as long aLong = 123L
Description: This data type stores signed numbers (both negative and positive) in the specified range. Conversion to all other integer data types can cause an OutOfRangeException, due to the larger scope of Long. You can force a variable to a Long by appending the “&” (ampersand) character to a variable. However, in the interest of better programming style, you should avoid this technique.
CLS-compliant: Yes
Conversion of other numeric types: CLng(objVar) or Convert.ToInt64(objVar)
aLong = CLng(123.45D) aLong = Convert.ToInt64(123.45D)
ULong
.NET data type: System.UInt64
Represents: Positive integer values (numbers without decimal points) in the specified range
Range: 0 to 18.446.744.073.709.551.615
Type literal: UL
Memory requirements: 8 bytes
Declaration and example assignment:
Dim aULong As ULong aULong = 123L
Description: This data type stores unsigned numbers (positive only) in the specified numeric range. Conversion to all other integer data types can cause an OutOfRangeException, due to the larger scope of ULong.
CLS-compliant: No
Conversion of other numeric types: CULng(objVar) or Convert.ToUInt64(objVar)
aULong = CULng(123.45D) aULong = Convert.ToUInt64(123.45D)
Single
.NET data type: System.Single
Represents: Floating-point values (numbers with decimal points whose scale becomes smaller with the increasing value) in the specified range
Range: –3.4028235*1038 to –1.401298*10–45 for negative values; 1.401298*10–45 to 3.4028235*1038 for positive values
Type literal: F
Memory requirements: 4 bytes
Declaration and example assignment:
Dim aSingle As Single Dim aDifferentSingle! ' also defined as Single aSingle = 123.0F
Description: This data type stores signed numbers (both negative and positive) in the specified range. By appending the “!” (exclamation) character to a variable, you can foce the variable to the Single type. However, in the interest of better programming style, you should avoid this technique.
CLS-compliant: Yes
Conversion of other numeric types: CSng(objVar) or Convert.ToSingle(objVar)
aSingle = CSng(123.45D) aSingle = Convert.ToSingle(123.45D)
Double
.NET data type: System.Double
Represents: Floating-point values (numbers with decimal points whose scale becomes smaller with the increasing value) in the specified range
Range: –1.79769313486231570*10308 to –4.94065645841246544*10–324 for negative values; 4.94065645841246544*10–324 to 1.79769313486231570308 for positive values
Type literal: R
Memory requirements: 8 bytes
Declaration and example assignment:
Dim aDouble As Double Dim aDifferentDouble# ' also defined as Double aDouble = 123.0R
Description: This data type stores numbers (both negative and positive) in the specified range. By appending the “#” (hash) character to a variable, you can force it to the Double type. However, in the interest of better programming style, you should avoid this technique.
CLS-compliant: Yes
Conversion of other numeric types: CDbl(objVar) or Convert.ToDouble(objVar)
aDouble = CDbl(123.45D) aDouble = Convert.ToDouble(123.45D)
Decimal
.NET data type: System.Decimal
Represents: Floating-point values (numbers with decimal points whose scale becomes smaller with the increasing value) in the specified range
Range: Depends on the number of used decimal places. If no decimal places are used (called a scale of 0) the max/min values are between ±79,228,162,514,264,337,593,543,950,335. When using a maximal scale (28 places behind the period; only values between –1 and 1 can be stored at this scale) the max/min values are between ±0.9999999999999999999999999999.
Type literal: D
Memory requirements: 16 bytes
Declaration and example assignment:
Dim aDecimal As Decimal Dim aDifferentDouble@ ' also defined as Decimal aDecimal = 123.23D
Description: This data type stores signed numbers (both negative and positive) in the specified range. By appending the “@” (ampersand) character to a variable you can force the Decimal type. However, in the interest of better programming style, you should avoid this technique.
CLS-compliant: Yes
Conversion of other numeric types: CDec(objVar) or Convert.ToDecimal(objVar)
aDecimal = CDec(123.45F) aDecimal = Convert.ToDecimal(123.45F)
The Numeric Data Types at a Glance
Table 6-2 presents a list of the numeric data types, along with a brief description.
Table 6-2 The Numeric Base Data Types in .NET 2.0/3.5/4.0 and Visual Basic 2010
Type name |
.NET type name |
Task |
Scope |
Byte |
System.Byte |
Stores unsigned integer values with a width of 8 bits (1 byte) |
–0 to 255 |
SByte |
System.SByte |
Stores signed integer values with a width of 8 bits (1 byte) |
–127 to 128 |
Short |
System.Int16 |
Stores signed integer values with a width of 16 bits (2 bytes) |
–32,768 to 32,767 |
UShort |
System.UInt16 |
Stores unsigned integer values with a width of 16 bits (2 bytes) |
0 to 65,535 |
Integer |
System.Int32 |
Stores signed integer values with a width of 32 bits (4 bytes) Note: On 32-bit systems, this integer data type is processed most quickly |
–2,147,483,648 to 2,147,483,647 |
UInteger |
System.UInt32 |
Stores unsigned integer values with a width of 32 bit (4 bytes) |
0 to 4,294,967,295 |
Long |
System.Int64 |
Stores signed integer values with a width of 64 bits (8 bytes) |
–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
ULong |
System.UInt64 |
Stores unsigned integer values with a width of 64 bits (8 bytes) |
0 to 18,446,744,073,709,551,615 |
Single |
System.Single |
Stores floating-point numbers with single precision; requires 4 bytes for display |
–3.4028235E+38 to –1.401298E-45 for negative values; 1.401298E-45 to 3.4028235E+38 for positive values |
Double |
System.Double |
Stores floating-point numbers with double precision; requires 8 bytes for display. Note: This is the fastest data type for floating-point number calculations because it is delegated directly to the math unit of the processor for calculation. |
1.79769313486231570E+308 to -4.94065645841246544E-324 for negative values; 4.94065645841246544E-324 to 1.79769313486231570E+308 for positive values |
Decimal |
System.Decimal |
Stores floating-point numbers in binary-coded decimal format. Note: This is the lowest data type for floating-point number calculations, but its special form of representing values excludes typical computer rounding errors. |
0 to ±79,228,162,514.264,337.593,543,950,335 (±7.9...E+28) without decimal character; 0 to ±7.9228162514264337593543950335 with 28 places to the right of the decimal character; smallest number not equal 0 (zero) is ±0.0000000000000000000000000001 (±1E-28) |
Avoiding Single and Double Rounding Errors
It’s not unusual that some numeric systems are unable to display exact values for fractions. However, programmers repeatedly believe that they have found an error in a programming language or claim that the computer can’t calculate correctly. You have already experienced rounding and conversion errors from one numeric system into another in your daily life with the base-10 system. For example, dividing the number 1 by 3 results in a number with infinite decimal points (0.333333333333...). Representing the fraction one-third using a base-3 system requires considerably fewer numbers; it’s simply 0.1.
It doesn’t really matter how many numbers you use to display a fraction, but as long as you use a finite number of digits in numeric systems to display a fraction, there will be situations in which rounding errors are unavoidable.
For example, 3*1/3 in a base-3 system leads to the following calculation:
0.1 +0.1 +0.1 ============================================ +1.0
In the decimal system, this also corresponds to 1.0. But performing this same addition in the decimal system is imprecise, because even if you use 60 decimal places to represent the number, you never reach the value 1 in the addition, as shown here:
0,333333333333333333333333333333333333333333333333333333333333333 +0.333333333333333333333333333333333333333333333333333333333333333 +0.333333333333333333333333333333333333333333333333333333333333333 ================================================================== 0.999999999999999999999999999999999999999999999999999999999999999
The total value in the preceding calculation is very close to 1—but it’s not quite 1. If you have multiple intermediate results during the course of a calculation, such representation errors can quickly lead to bigger mistakes that will become relevant at some point.
The computer has the same problem with certain numbers when it calculates in the binary system. Even though it can display the number 69.82 in the decimal system correctly, it runs into problems with the binary system. Converting 69 works without issues, but it becomes difficult with 0.82.
Once you know that decimal places are represented by negative powers of the base number, you can try to approximate the fractional part (0.82) by using the following calculations:
0.5 1*2^-1 intermediate result: 0.5 0.25 1*2^-2 intermediate result: 0.75 0.125 1*2^-3 intermediate result: 0.8125 0.0625 0*2^-4 intermediate result: 0.8125 0.03125 0*2^-5 intermediate result: 0.8125 0.015625 0*2^-6 intermediate result: 0.8125 0.0078125 0*2^-7 intermediate result: 0.8125 0.00390625 1*2^-8 intermediate result: 0.81640625 0.001953125 1*2^-9 intermediate result: 0.818359375 0.0009765625 1*2^-10 intermediate result: 0.8193359375 0.00048828125 1*2^-11 intermediate result: 0.81982421875
At this point, the computer has generated the binary digits 0.11100001111, but we have not reached the desired goal. The truth is that you can play this game for all eternity, but you will never be able to represent the number 0.82 in the decimal system with a finite number of digits in the binary system.
Public Class Primitives Public Shared Sub main() Dim locDouble1, locDouble2 As Double Dim locDec1, locDec2 As Decimal locDouble1 = 69.82 locDouble2 = 69.2 locDouble2 += 0.62 Console.WriteLine("The statement locDouble1=locDouble2 is {0}", locDouble1 = locDouble2) Console.WriteLine("but locDouble1 is {0} and locDouble2 is {1}", _ locDouble1, locDouble2) locDec1 = 69.82D locDec2 = 69.2D locDec2 += 0.62D Console.WriteLine("The statement locDec1=locDec2 is {0}", locDec1 = locDec2) Console.WriteLine() Console.WriteLine("Press key to exit!") Console.ReadKey() ... End Sub End Class
At first glance, you’d think that both WriteLine methods return the same text. You don’t need to use a calculator to see that the value (and thus the first variable) within the program represents the addition of the second and third value; therefore, both variable values should be the same. Unfortunately that’s not the case. Although the second part of the program achieves the correct result using the Decimal data type, the Double type fails in the first part of the program.
The second WriteLine method is even more confusing, because both variables appear to contain the same value to the last decimal place. Here’s the output from the preceding code:
The statement locDouble1=locDouble2 is False but locDouble1 is 69.82 and locDouble2 is 69.82 The statement locDec1=locDec2 is True Press key to exit!
So what happened here? During the conversion from the internal binary number system to the decimal number system, a rounding error takes place that conceals the true result. Based on this experiment, the following remarks can be made. If at all possible, try to avoid using fractioned Double or Single values as counters or conditions within a loop; otherwise, you run the risk that your program becomes bogged down in endless loops as a result of the inaccuracies just mentioned. Therefore, follow these rules:
-
Use Single and Double data types only where the umpteenth number behind the comma is not important. For example, when calculating graphics, where rounding errors are irrelevant due to a smaller screen resolution, you should always choose the faster processor-calculated data types, Single and Double, over the manually calculated Decimal data type.
-
When working with finance applications you should always use the Decimal data type. It’s the only data type that ensures that numeric calculations that cannot be represented exactly will not result in major errors.
-
If possible, never use the Decimal data type in loops, and do not use it as a counter variable. The type is not directly supported by the processor, so it degrades your program’s performance. Try to get by with one of the many integer variable types.
-
When you need to compare Double and Single variables to one another, you should query their deltas rather than comparing the values directly, as in the following code:
If Math.Abs(locDouble1 – locDouble2) < 0.0001 then 'Values are nearly the same, i.e. the same. End If
Methods Common to all Numeric Types
All numeric data types have methods that are used the same way for all types. They convert a string into the corresponding numeric value or a numeric value into a string. Other methods serve to determine the largest or smallest value a data type can represent.
Converting Strings into Values and Avoiding Culture-Dependant Errors
The static functions Parse and TryParse are available to all numeric data types to convert a string into a value. For example, to convert the numeric string “123” into the integer value 123, you can write:
Dim locInteger As Integer locInteger = Integer.Parse("123")
You can also try to convert the string into a numeric value, as shown here:
Dim locInteger As Integer If Integer.TryParse("123", locInteger) Then 'Conversion successful Else 'Conversion not successful End If
If the conversion is successful, the converted number is displayed in the output variable—locInteger in this example. The .NET Framework equivalent of Integer also permits conversions via this code:
locInteger = System.Int32.Parse("123") 'This would work, too.
And of course, it’s also possible to make the conversion via the Convert class in .NET Framework style by using the following:
locInteger = Convert.ToInt32("123") 'And this would work.
Finally, there’s an old-fashioned way in Visual Basic:
locInteger = CInt("123") 'Last option.
But watch out: you might find differences when running programs on a non–English-language system because of the default cultural setting. For example, if you run the following program on a German system, it will not act the way you might expect:
Dim locString As String = "123.23" Dim locdouble As Double = Double.Parse(locString) Console.WriteLine(locdouble.ToString)
You might expect the string to be converted correctly into the value 123.23. Instead the program returns the following:
12323
This is definitely not the expected result. However, if you run the program on an English system, the result will be correct, as expected:
123.23
Well, maybe not quite. Germans are used to separating the decimal places from integer places by a comma. English speaking countries use a period, and the preceding output uses the correct English formatting. What’s the impact of this behavior on your programs? To begin, you should avoid saving numeric constants as text in the program code itself if you want to convert them to a numeric type later on (as shown in the example). When you define numeric data types within your programs, make those definitions directly in code. Do not use strings (text in quotes) and the corresponding conversion functions. You have probably already noticed that numeric strings placed in code (without quotes) for assigning a value must always adhere to the English formatting.
As long as you don’t need to exchange files with information saved as text across cultural borders for which your program has to generate values, you have nothing to worry about: if your application is run on an English-language system, your numbers are written into the file with a period as separator; in the German-speaking areas, a comma is used. Because cultural settings are taken into account when reading a file, your application should be able to generate the correct values back from the text file.
It becomes a bit more problematic when the files containing the text are exchanged across cultural borders. This can happen pretty easily; for example, you might access a database server in a company with a .NET Windows Forms application from a German Windows 7 system, because many IT departments exclusively run English-language versions on their servers for a variety of reasons. Therefore, a platform in the United States would export the file with a period separator, and in Germany, the Parse function would recognize the period as a thousands-separator, and thus erroneously treat the fractional digits as significant integer digits. In this case, you need to ensure that any export of a text file is culturally neutral, which you can achieve as follows:
You can use the Parse function and the ToString function of all numeric types to control the conversion by a format provider. For numeric types, .NET offers many different format providers: some help you control the format depending on the application type (financial, scientific, and so on), others control it depending on the culture, namely the classes NumberFormatInfo and CultureInfo. You can pass either to the ToString or the Parse function (assuming they have been properly initialized).
The static property InvariantCulture returns an instance of a CultureInfo class that represents the current system locale.
Performance and Rounding Issues
If you are using type-safe programming in Visual Basic .NET (which you should always do by using Option Strict On in the project properties), it is customary to convert a floating-point number into a value of the type Integer by using the conversion operator CInt. But a lot of programmers don’t know that the Visual Basic compiler behaves completely differently than the casting operator in C#. CInt in Visual Basic uses commercial rounding, so the compiler turns
Dim anInt = CInt(123.54R)
into:
Dim anInt = CInt(Math.Round(123.54R))
It is not possible to implement a simple CInt (as used by the Visual Basic compiler itself, and as is the default in C#) in Visual Basic itself. When converting a floating-point in C#, the decimal places after the integer are simply truncated—they are not rounded. To simulate this, you need to use the following construct:
Dim anInt = CInt(Math.Truncate(123.54R))
The problem is that the compiler generates the following completely redundant code from it:
Dim anInt = CInt(Math.Round(Math.Truncate(123.54R)))
When it comes to processing graphics, for example, this means a huge performance compromise, of course, because two functions are called from the Math Library. C# is noticeably faster because it provides a CInt directly.
Determining the Minimum and Maximum Values of a Numeric Type
The numeric data types recognize two specific static properties that you can use to determine the largest and smallest representable value. These properties are called MinValue and MaxValue—and just like any static function, you can call them through the type name as shown in the following example:
Dim locDecimal As System.Decimal Console.WriteLine(Integer.MaxValue) Console.WriteLine(Double.MinValue) Console.WriteLine(locDecimal.MaxValue) ' Compiler gives a warning – use type name instead.
Special Functions for all Floating-Point Types
Floating-point types have certain special properties that simplify processing of abnormal results during calculations (such as the Infinity and the NaN properties). To check for nonnumeric results in your calculations, use the following members of the floating-point data types.
Infinity
When a floating-point value type is divided by 0, the .NET Framework does not generate an exception; instead, the result is infinity. Both Single and Double can represent this result, as shown in the following example:
Dim locdouble As Double locdouble = 20 locdouble /= 0 Console.WriteLine(locdouble) Console.WriteLine("The statement locDouble is +infinite is {0}.", locdouble = Double.PositiveInfinity) ' I suggest using the IsPositiveInfinity method and replacing the last statement ' with an If statement: If locdouble.IsPositiveInfinity Then ... Else ... End If
When you run this example, no exception occurs, but the program displays the following on the screen:
+infinite The statement locDouble is +infinite is True.
Instead of performing the comparison to infinity by using the comparison operator, you can also use the static function IsInfinity, as follows:
Console.WriteLine("The statement locDouble is +infinity is {0}.", locdouble. IsInfinity(locdouble))
You can also use the IsPositiveInfinity and IsNegativeInfinity methods to determine whether a value is infinitely large or infinitely small (a very large negative value).
To assign the value infinite to a variable, use the static functions PositiveInfinity and NegativeInfinity, which return appropriate constants.
Not a Number: NaN
The base floating-point types cover another special case: the division of 0 by 0, which is not mathematically defined and does not return a valid number:
'Special case: 0/0 is not mathematically defined and returns "Not a Number" aDouble = 0 aDouble = aDouble / 0 If Double.IsNaN(aDouble) Then Debug.Print("aDouble is not a number!") End If
If you run this code, the output window will display the result of the If query.
Dim aDouble As Double 'Special case: 0/0 is not mathematically defined and returns "Not a Number" aDouble = 0 aDouble = aDouble / 0 'The text should be returned as expected, 'but isn't! If aDouble = Double.NaN Then Debug.Print("Test 1:aDouble is not a number!") End If 'Now the test can be performed! If Double.IsNaN(aDouble) Then Debug.Print("Test 2:aDouble is not a number!") End If
The preceding example displays only the second message in the output window.
Converting with TryParse
All numeric data types expose the static method TryParse, which attempts to convert a string into a value. Unlike Parse, the TryParse method doesn’t generate an exception when the conversion fails. Instead, you pass a variable name as a reference argument to the method, and the method returns a result, indicating whether the conversion was successful (True) or not (False), as shown here:
Dim locdouble As Double Dim locString As String = "Onehundredandtwentythree" 'locdouble = Double.Parse(locString) ' Exception 'Not working, either, but at least no exception: Console.WriteLine("Conversion successful? {0}", _ Double.TryParse(locString, NumberStyles.Any, New CultureInfo("en-En"), locdouble))
Special Functions for the Decimal Type
The value type Decimal also has special methods, many of which aren’t of any use in Visual Basic (you can use them, but it doesn’t make much sense—they were added for other languages that don’t support operator overloading). Take, for example, the static Add function, which adds two numbers of type Decimal and returns a Decimal. You can use the + operator of Visual Basic instead, which can also add two numbers of the type Decimal—and does so in much more easily readable code. Therefore, it makes sense to use the functions presented in Table 6-3.
Table 6-3 The Most Important Functions of the Decimal Type
Function name |
Task |
Remainder(Dec1, Dec2) |
Determines the remainder of the division of both decimal Decimal values. |
Round(Dec, Integer) |
Rounds a Decimal value to the specified number of decimal places. |
Truncate(Dec) |
Returns the integer part of the specified Decimal value. |
Floor(Dec) |
Rounds the Decimal value to the next smaller number. |
Negate(Decimal) |
Multiplies the Decimal value by –1. |