Convert hex strings to integers

sinDizzy picture sinDizzy · Sep 10, 2014 · Viewed 7.8k times · Source

Visual Basic 2010: Recently in one of my projects I have been tasked with reading several data fields in hex. Each field is three characters long. So this is what I have been doing:

'Read the hex value and convert it to decimal
Dim hexVal as string  = "38D"
Dim intVal as integer = Convert.ToInt32(hexVal, 16)

'The integer result gets multiplied by a scaling factor
'(this is set by the manufacturer for each field).
Dim fac as single  = 200
Dim final as single  = intVal * fac
Debug.Print (final)

This has been working great except in this case: hexVal="FFD" and fac=32 .NET gives me intVal=4093 and the final=130976. However the legacy system I am checking against gives a -96.

I'm a bit confused but theorize that it's in the hex notation. The only documentation I have for the raw data states: Characters are coded per ISO Alphabet No 5 using seven bits per character, that is, without the addition of a parity bit. Three such characters will comprise the field of each 32-bit word.

Am I converting this incorrectly?

Addendum: I have looked into the definitions of the fields and have discovered that almost all are expected to be positive (or unsigned). A few can be negative or positive (signed). Looking at the legacy code, for each hex field they compute a unsigned result and a signed result. If the field is expected to be always positive then they use the unsigned result. Now if the field is expected to be negative or positive then they take the unsigned result and if higher than a ceiling then they use the signed result, otherwise they use the unsigned result. Here is what I have so far with the snippets from below:

    Dim hexVal As String, res As Single, decCeiling As Single
    Dim paddedHex1 As String, intVal1 As Integer = 0, resVal1 As Single = 0
    Dim paddedHex2 As String, intVal2 As Integer = 0, resVal2 As Single = 0
    Dim IsUnsignedInput As Boolean

    hexVal = "FB1"   'Raw hex input (in this case represents temperature in deg C)
    res = 0.125      'A resolution factor. Used to scale the decimal to a real-world nummber
    decCeiling = 150 'The maximum temperature we can expect is 150 degree Celcius
    IsUnsignedInput = False 'Is field unsigned or signed (for temps we can expect negative and positive)
    If hexVal.Length > 8 Then
        Throw New Exception("Input '" & hexVal & "' exceeds the max length of a raw input. The max is 8 characters.")
    EndIf

    'This calcualtion assumes an unsigned value (that is, always a positive number)
    paddedHex1 = hexVal.ToString.PadLeft(8, CChar("0"))
    intVal1 = Convert.ToInt32(paddedHex1, 16)
    resVal1 = intVal1 * res

    'This assumes a signed value (that is, could be a negative OR positive number.
    'Use two's complement to arrive at the result.
    paddedHex2 = hexVal.PadLeft(8, CChar("F"))
    Dim sb As New StringBuilder(paddedHex2.Length)
    For i As Integer = 0 To paddedHex2.Length - 1
        Dim hexDigit As Integer = Convert.ToInt32(paddedHex2(i), 16)
        sb.Append((15 - hexDigit).ToString("X"))
    Next i

    Dim inverted As Integer = Convert.ToInt32(sb.ToString, 16)
    intVal2 = -(inverted + 1)
    resVal2 = intVal2 * res

    'Finally, which result do we use as the final decimal? For our example we get
    'resVal1 (unsigned)=+502.125
    'resVal2 (signed)  =  -9.875
    'Field is signed so is 502.125 > 150? Yes, so use the signed result of -9.875.
    If IsUnsignedInput Then
        'If unsigned then we always expect a positive value so use straight conversion.
        Debug.Print("Result=" & resVal1)
    Else
        'If signed then we expect a positive OR negative value
        If resVal1 > decCeiling Then
            'Standard conversion yields a higher number than expected so use two's complement to get number
            Debug.Print("Result=" & resVal2)
        Else
            'Standard conversion yields a number that is in the expected range so use straight conversion to get number
            Debug.Print("Result=" & resVal1)
        End If
    End If

Doing a back-to-back comparison with the legacy system it all matches up, but it is not working with hex too much in the past, and I am a little cautious. I would appreciate any further feedback on this approach.

Answer

Olivier Jacot-Descombes picture Olivier Jacot-Descombes · Sep 10, 2014

intVal=4093 is correct.
final=130976 is also correct.

FFD can also be interpreted as -3 represented in the two's complement (that's how computers store negative values). 32 * -3 = -96.

FFD               = 111111111101 (binary)

In the two's complement, when the first bit is a 1, it means that the number is negative. In order to negate the number, first invert all the bits and then add 1:

FFD inverted =      000000000010
+ 1          =      000000000011 = 3 (decimal).

Since 3 is the negated number, the real number must be -3.

You can do this in the Hex notation as well. If the first hex digit is >= 8, you have a negative number. So negate the number by doing the following replacements:

0 -> F
1 -> E
2 -> D
3 -> C
4 -> B
5 -> A
6 -> 9
7 -> 8
8 -> 7
9 -> 6
A -> 5
B -> 4
C -> 3
D -> 2
E -> 1
F -> 0

(The second column simply lists the hex digits in reverse order.)

So for FFD you get 002 (hex). You can convert this hex number into a decimal number and add 1 and you get 3. Because this represents a negative value, multiply it by -1. 3 * -1 = -3.

Function ConvertHex(ByVal hexVal As String) As Integer
    If hexVal(0) >= "8"c Then 'We have a negative value in the 2's complement
        Dim sb As New System.Text.StringBuilder(hexVal.Length)
        For i As Integer = 0 To hexVal.Length - 1
            'Convert one hex digit into an Integer
            Dim hexDigit As Integer = Convert.ToInt32(hexVal(i), 16)

            'Invert and append it as hex digit
            sb.Append((15 - hexDigit).ToString("X"))
        Next i

        'Get the inverted hex number as Integer again
        Dim inverted As Integer = Convert.ToInt32(sb.ToString(), 16)

        'Add 1 to the inverted number in order to get the 2's complement
        Return -(inverted + 1)
    Else
        Return Convert.ToInt32(hexVal, 16)
    End If
End Function

But if you always have 3 hex digits you can simply do this

Function ConvertHex3(ByVal hexVal As String) As Integer
    Dim number = Convert.ToInt32(hexVal, 16)
    If hexVal(0) >= "8"c Then
        Return number - &H1000
    Else
        Return number
    End If
End Function

That's much smarter!