Troubleshooting Data Types (Visual Basic)

This page lists some common problems that can occur when you perform operations on intrinsic data types.

Floating-Point Expressions Do Not Compare as Equal

When you work with floating-point numbers (Single Data Type and Double Data Type), remember that they are stored as binary fractions. This means they cannot hold an exact representation of any quantity that is not a binary fraction (of the form k / (2 ^ n) where k and n are integers). For example, 0.5 (= 1/2) and 0.3125 (= 5/16) can be held as precise values, whereas 0.2 (= 1/5) and 0.3 (= 3/10) can be only approximations.

Because of this imprecision, you cannot rely on exact results when you operate on floating-point values. In particular, two values that are theoretically equal might have slightly different representations.

To compare floating-point quantities
1. Calculate the absolute value of their difference by using the Abs method of the Math class in the System namespace.
2. Determine an acceptable maximum difference, such that you can consider the two quantities to be equal for practical purposes if their difference is no larger.
3. Compare the absolute value of the difference to the acceptable difference.

The following example demonstrates both incorrect and correct comparison of two Double values.

Dim oneThird As Double = 1.0 / 3.0
Dim pointThrees As Double = 0.333333333333333

' The following comparison does not indicate equality.
Dim exactlyEqual As Boolean = (oneThird = pointThrees)

' The following comparison indicates equality.
Dim closeEnough As Double = 0.000000000000001
Dim absoluteDifference As Double = Math.Abs(oneThird - pointThrees)
Dim practicallyEqual As Boolean = (absoluteDifference < closeEnough)

MsgBox("1.0 / 3.0 is represented as " & oneThird.ToString("G17") &
    vbCrLf & "0.333333333333333 is represented as " &
    pointThrees.ToString("G17") &
    vbCrLf & "Exact comparison generates " & CStr(exactlyEqual) &
    vbCrLf & "Acceptable difference comparison generates " &
    CStr(practicallyEqual))

The previous example uses the ToString method of the Double structure so that it can specify better precision than the CStr keyword uses. The default is 15 digits, but the "G17" format extends it to 17 digits.

Mod Operator Does Not Return Accurate Result

Because of the imprecision of floating-point storage, the Mod Operator can return an unexpected result when at least one of the operands is floating-point.

The Decimal Data Type does not use floating-point representation. Many numbers that are inexact in Single and Double are exact in Decimal (for example 0.2 and 0.3). Although arithmetic is slower in Decimal than in floating-point, it might be worth the performance decrease to achieve better precision.

To find the integer remainder of floating-point quantities
1. Declare variables as Decimal.
2. Use the literal type character D to force literals to Decimal, in case their values are too large for the Long data type.

The following example demonstrates the potential imprecision of floating-point operands.

Dim two As Double = 2.0
Dim zeroPointTwo As Double = 0.2
Dim quotient As Double = two / zeroPointTwo
Dim doubleRemainder As Double = two Mod zeroPointTwo

MsgBox("2.0 is represented as " & two.ToString("G17") &
    vbCrLf & "0.2 is represented as " & zeroPointTwo.ToString("G17") &
    vbCrLf & "2.0 / 0.2 generates " & quotient.ToString("G17") &
    vbCrLf & "2.0 Mod 0.2 generates " &
    doubleRemainder.ToString("G17"))

Dim decimalRemainder As Decimal = 2D Mod 0.2D
MsgBox("2.0D Mod 0.2D generates " & CStr(decimalRemainder))

The previous example uses the ToString method of the Double structure so that it can specify better precision than the CStr keyword uses. The default is 15 digits, but the "G17" format extends it to 17 digits.

Because zeroPointTwo is Double, its value for 0.2 is an infinitely repeating binary fraction with a stored value of 0.20000000000000001. Dividing 2.0 by this quantity yields 9.9999999999999995 with a remainder of 0.19999999999999991.

In the expression for decimalRemainder, the literal type character D forces both operands to Decimal, and 0.2 has a precise representation. Therefore the Mod operator yields the expected remainder of 0.0.

Note that it is not sufficient to declare decimalRemainder as Decimal. You must also force the literals to Decimal, or they use Double by default and decimalRemainder receives the same inaccurate value as doubleRemainder.

Boolean Type Does Not Convert to Numeric Type Accurately

Boolean Data Type values are not stored as numbers, and the stored values are not intended to be equivalent to numbers. For compatibility with earlier versions, Visual Basic provides conversion keywords (CType Function, CBool, CInt, and so on) to convert between Boolean and numeric types. However, other languages sometimes perform these conversions differently, as do the .NET Framework methods.

You should never write code that relies on equivalent numeric values for True and False. Whenever possible, you should restrict usage of Boolean variables to the logical values for which they are designed. If you must mix Boolean and numeric values, make sure that you understand the conversion method that you select.

Conversion in Visual Basic

When you use the CType or CBool conversion keywords to convert numeric data types to Boolean, 0 becomes False and all other values become True. When you convert Boolean values to numeric types by using the conversion keywords, False becomes 0 and True becomes -1.

Conversion in the Framework

The ToInt32 method of the Convert class in the System namespace converts True to +1.

If you must convert a Boolean value to a numeric data type, be careful about which conversion method you use.

Character Literal Generates Compiler Error

In the absence of any type characters, Visual Basic assumes default data types for literals. The default type for a character literal — enclosed in quotation marks (" ") — is String.

The String data type does not widen to the Char Data Type. This means that if you want to assign a literal to a Char variable, you must either make a narrowing conversion or force the literal to the Char type.

To create a Char literal to assign to a variable or constant
1. Declare the variable or constant as Char.
2. Enclose the character value in quotation marks (" ").
3. Follow the closing double quotation mark with the literal type character C to force the literal to Char. This is necessary if the type checking switch (Option Strict Statement) is On, and it is desirable in any case.

The following example demonstrates both unsuccessful and successful assignments of a literal to a Char variable.

Dim charVar As Char
' The following statement attempts to convert a String literal to Char.
' Because Option Strict is On, it generates a compiler error.
charVar = "Z"
' The following statement succeeds because it specifies a Char literal.
charVar = "Z"c
' The following statement succeeds because it converts String to Char.
charVar = CChar("Z")

There is always a risk in using narrowing conversions, because they can fail at run time. For example, a conversion from String to Char can fail if the String value contains more than one character. Therefore, it is better programming to use the C type character.

String Conversion Fails at Run Time

The String Data Type participates in very few widening conversions. String widens only to itself and Object, and only Char and Char() (a Char array) widen to String. This is because String variables and constants can contain values that other data types cannot contain.

When the type checking switch (Option Strict Statement) is On, the compiler disallows all implicit narrowing conversions. This includes those involving String. Your code can still use conversion keywords such as CStr and CType Function, which direct the .NET Framework to attempt the conversion.

Note

The narrowing-conversion error is suppressed for conversions from the elements in a For Each…Next collection to the loop control variable. For more information and examples, see the "Narrowing Conversions" section in For Each...Next Statement.

Narrowing Conversion Protection

The disadvantage of narrowing conversions is that they can fail at run time. For example, if a String variable contains anything other than "True" or "False," it cannot be converted to Boolean. If it contains punctuation characters, conversion to any numeric type fails. Unless you know that your String variable always holds values that the destination type can accept, you should not try a conversion.

If you must convert from String to another data type, the safest procedure is to enclose the attempted conversion in the Try...Catch...Finally Statement. This lets you deal with a run-time failure.

Character Arrays

A single Char and an array of Char elements both widen to String. However, String does not widen to Char(). To convert a String value to a Char array, you can use the ToCharArray method of the System.String class.

Meaningless Values

In general, String values are not meaningful in other data types, and conversion is highly artificial and dangerous. Whenever possible, you should restrict usage of String variables to the character sequences for which they are designed. You should never write code that relies on equivalent values in other types.

See also