This FAQ will explain the difference between signed and unsigned integers, the binary explanation of a number and how to convert and work with unsigned integers in Visual Basic .NET. No knowledge of binary numbers, integers or datatypes in general is required. The FAQ is meant for beginners through experienced users, though the more experienced user may skip many basic explanations throughout the FAQ.
When working with databases, CIM/WMI, COM classes, dynamic link libraries, API's and other objects usually created in another language then VB .NET you may encounter a returned value of an unsigned integer type.
VB .NET has very little support for these datatypes, even though there are some structures defined for it: Uint16, Uin32 and Uint64. These are not types, but structures and there are no operators defined for them. In other words, you can't perform calculations on unsigned integers in VB .NET like you would in C/C++ for example. The only unsigned integer truly supported and CLS compliant in VB .NET is the System.Byte structure, which represents an 8-bit unsigned integer.
In VB .NET you are used to working with types like Short, Integer, Long, Decimal among others. The Short datatype is a 16-bit signed integer, an Integer is as 32-bit signed integer and Long is as 64-bit signed integer. The decimal is a somewhat special type which I will explain later. The difference between a signed and an unsigned integer is exactly what the name implies: a signed integer uses the left most bit to indicate wether the number represents a positive or negative number (it's sign), where an usigned integer does not.
This in fact means that a 16-bit signed integer can use 15 bits to store it's value and 1 bit to indicate it's sign. An unsigned 16-bit integer can use all 16 bits to store it's value. The range of these both is the same, but the minimum and maximum values are very different. To understand the implications you should know about the binary format of a number.
The binary number 10000001 may both represent a signed and an unsigned 8-bit integer. The value here is different though. To comprehend the difference I will first explain how a binary number is interpreted in both cases.
A binary number represents powers of 2, from right to left:
As you can see the result from the same binary number differs greatly depending on which datatype is used. As a rule, you use -2^n for the left most bit if it is set (it's value is 1) when working with signed integers. For positive numbers the result will be the same:
Obviously, an unsigned integer can hold a greater positive value than a signed integer, because the left most bit must be set to 0 to indicate it's a positive number.
For negative numbers, the minimum value for an unsigned integer is always 0 since it cannot represent a negative number. So the range of an 8-bit unsigned integer is 0 to 255, which are 256 (2^8) different values. Thus, a 16-bit unsigned integer can hold 2^16 = 65536 different values, it's range being 0 to 65535.
The range of an 8-bit signed integer is -128 to 127, which also represents 256 different values. From the above we may conclude that wether the integer is signed or unsigned, it can always hold only 2^n different values, where n represents it's bit length.
Knowing this, you can see the problem when converting from unsigned to signed integers arousing; the unsigned integer may very well have a larger positive value than can be stored in the equivalent signed integer. With equivalent I mean they both use the same number of bits. The problem is solved easily theoritically: use one more bit for the signed datatype so it uses the same number of bits to store it's value. Now, practically, a 9-bit signed integer doesn't exist in VB .NET and we have to resort to other options. When converting from unsigned to signed integers you should use the following VB .NET datatypes:
Uint8 -> Byte (which already is a predefined, usable 8-bit unsigned integer) Uint16 -> Integer (32-bit signed integer) Uint32 -> Long (64-bit signed integer) Uint64 -> Decimal (96-bit signed 'integer', with some special properties)
As a rule, you take the next larger signed datatype in bit length, to ensure the value will 'fit' in the signed target datatype.
Keeping this in mind, you could use the following function in VB .NET to convert a Uint16 to Integer:
Friend Function Uint16ToInteger(ByVal value As UInt16) As Integer Return Integer.Parse(value.ToString) End Function
The Integer.Parse method provided by VB .NET parses a string representation of a value and converts it to an integer value. Since the only way to get the numeric value for a Uint16 is using the ToString method, the function first gets this value as a string, and then converts that string to an integer. Since an unsigned 16-bit integer can never hold a larger value than an 32-bit signed integer can, this is safe to use.
The same applies to all other conversions:
Friend Function Uint32ToLong(ByVal value As UInt32) As Long Return Long.Parse(value.ToString) End Function
Friend Function Uint64ToDecimal(ByVal value As UInt64) As Decimal Return Decimal.Parse(value.ToString) End Function
The Decimal structure represents a number that is a signed, fixed-point value consisting of an integral part and an optional fractional part. The integral part is interesting to us. The Decimal datatype consists of 128-bits, of which 96 may be used to store an integer value. Thus we can use it to convert an 64-bit unsigned integer, ignoring the fractional part of the decimal (which will be .0 any way).
Using these functions you are left with Integers, Longs and Decimals to perform further calculations. When it comes to converting these values back to unsigned integers, there are some tricky issues to deal with.
Most importantly, converting a negative integer to an unsigned integer may leave you dazzled with the results, although it is very logical. Let's look at an example:
Now this is certainly not what we want, and when you even try this in the above functions, the system will throw an exception telling you the number is too small. Therefore, you should always check to see wether the signed integer you wish to convert is greater than or equal to 0.
I will explain another issue by a practical example:
Let's say you got a value of Uint16 from a database field and converted it to an Integer (32-bit signed). After some operations on the Integer value, you want to store it back into the database. You need to convert it back to Uint16 again. The problem here is that the Integer value can actually be larger than what a Uint16 can hold. There's no way around this and you will have to check wether the Integer value isn't too large now to convert it back.
The maximum value of Uint16 is 2^16 - 1 = 65535. (-1 because of zero; 2^16 is the number of different value it can hold). Thus the maximum value of Uint32 is 2^32 - 1 = 4294967295. The same applies for Uint64.
When you have checked this, you can convert the values to unsigned integers thus:
Friend Function IntegerToUint16(ByVal value As Integer) As Uint16 If value < 2 ^ 16 Then Return Uint16.Parse(value.ToString) Else Throw New OverflowException(value.ToString & " is too large to convert to a 16-bit unsigned integer! It must be less than " & (2 ^ 16).ToString & "!") End If End Function
You can now easily write similar functions for the other datatypes:
Friend Function LongToUint32(ByVal value As Long) As UInt32 If value < 2 ^ 32 Then Return UInt32.Parse(value.ToString) Else Throw New OverflowException(value.ToString & " is too large to convert to a 32-bit unsigned integer! It must be less than " & (2 ^ 32).ToString & "!") End If End Function
Friend Function DecimalToUint64(ByVal value As Decimal) As UInt64 If value < 2 ^ 64 Then Return UInt64.Parse(value.ToString) Else Throw New OverflowException(value.ToString & " is too large to convert to a 64-bit unsigned integer! It must be less than " & (2 ^ 64).ToString & "!") End If End Function
An option is to convert the large integer to a bigger unsigned integer, but you will most likely encounter the conversion problem further on (in your database for example, when you try to store a Uint32 in a field defined as Uint16).
That's it! Thank you for reading this FAQ and I hope you learned something from it. If you have any comments or corrections, feel free to drop me a message. There's always new things to discover!