The Essential .NET Data Types

  • 8/15/2011
This chapter from Microsoft Visual Basic 2010 Developer's Handbook covers .NET Data Types, including char, string, boolean, and date.

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.