7 Byte Hex to Dec Conversion - vba

I need to convert 7 byte hexadecimal values in an Excel column to the decimal equivalents in an adjacent column.
I have over 2000 unique values to convert from hexadecimal to decimal.
I got as far as using Excel's hex2bin and then bin2dec formulas.
I found that Excel is rounding up the least significant 4 decimal places.
Example:
7Byte Hex: 0x803277323A8904
Excel Output: 36084284544158000
Correct Decimal Number: 36084284544157956

This is a small variation of Rick Rothstein's code
Function HexToDecs(ByVal HexString As String) As String
Dim X As Integer
Dim BinStr As String
Const BinValues = "0000000100100011010001010110011110001001101010111100110111101111"
If Left$(HexString, 2) Like "&[hH]" Then
HexString = Mid$(HexString, 3)
End If
If Len(HexString) <= 23 Then
For X = 1 To Len(HexString)
BinStr = BinStr & Mid$(BinValues, 4 * Val("&h" & Mid$(HexString, X, 1)) + 1, 4)
Next
HexToDecd = CDec(0)
For X = 0 To Len(BinStr) - 1
HexToDecd = HexToDecd + Val(Mid(BinStr, Len(BinStr) - X, 1)) * 2 ^ X
Next
Else
' Number is too big, handle error here
End If
HexToDecs = CStr(HexToDecd)
End Function
NOTE:
This UDF() returns a String representation of the integer to avoid the 15 digit limitation to true numeric values.
I have elected not to start my input string with 0x

Excel maximum number of digits. In Excel spreadsheet, there is a limit for storing a number in a Cell, which is 15 digits (15 numbers) regardless of whether the numbers are decimal places. Excel call this “15 significant digits of precision” which adheres to “IEEE 754”.Feb 24, 2015
In order to have 36084284544157956, which has 17 digits, save the cell as a Text.
Even VBA does not like displaying such big numbers:
Public Sub TestMe()
Dim inputString As String: inputString = "123456789012345678"
Dim someValue As Double
someValue = inputString
Debug.Print someValue + 1
End Sub
gets: 1,23456789012346E+17
To present the text value in Excel cell, make sure that you format the cell before putting the text in it:
Option Explicit
Public Sub TestMe()
With Range("D2")
.NumberFormat = "#"
.Value2 = "123456789012345678"
End With
End Sub

Related

signed result of Val function in VBA

I use vba in ms access,and found that ,if my parameter greater than 0x8000
less than 0x10000, the result is minus number
eg. Val("&H8000") = -32768 Val("&HFFFF")= -1
how can i get the unsigned number?
thanks!
There's a problem right here:
?TypeName(&HFFFF)
Integer
The Integer type is 16-bit, and 65,535 overflows it. This is probably overkill, but it was fun to write:
Function ConvertHex(ByVal value As String) As Double
If Left(value, 2) = "&H" Then
value = Right(value, Len(value) - 2)
End If
Dim result As Double
Dim i As Integer, j As Integer
For i = Len(value) To 1 Step -1
Dim digit As String
digit = Mid$(value, i, 1)
result = result + (16 ^ j) * Val("&H" & digit)
j = j + 1
Next
ConvertHex = result
End Function
This function iterates each digit starting from the right, computing its value and adding it to the result as it moves to the leftmost digit.
?ConvertHex("&H10")
16
?ConvertHex("&HFF")
255
?ConvertHex("&HFFFF")
65535
?ConvertHex("&HFFFFFFFFFFFFF")
281474976710655
UPDATE
As I suspected, there's a much better way to do this. Credits to #Jonbot for this one:
Function ConvertHex(ByVal value As String) As Currency
Dim result As Currency
result = CCur(value)
If result < 0 Then
'Add two times Int32.MaxValue and another 2 for the overflow
'Because the hex value is apparently parsed as a signed Int64/Int32
result = result + &H7FFFFFFF + &H7FFFFFFF + 2
End If
ConvertHex = result
End Function
Append an ampersand to the hex literal to force conversion to a 32bit integer:
Val("&HFFFF" & "&") == 65535
Val("&H8000&") == +32768
Don't use Val. Use one of the built-in conversion functions instead:
?CCur("&HFFFF")
65535
?CDbl("&HFFFF")
65535
Prefer Currency over Double in this case, to avoid floating-point issues.

Get Index of last active columns per Row in Excel using Open XML

How do i get the Index of the last active column in a row using Open Xml
i have this for row 1.
Dim activeCells As IEnumerable(Of DocumentFormat.OpenXml.Spreadsheet.Cell) = row.Descendants(Of DocumentFormat.OpenXml.Spreadsheet.Cell)().Where(Function(c) Not String.IsNullOrEmpty(c.InnerText))
Dim cell As DocumentFormat.OpenXml.Spreadsheet.Cell = activeCells.LastOrDefault()
Dim CellRef As String = cell.CellReference
This gives D1", but what i want is the index in this case "4". how do i go about this?
To convert the cell reference to a column index you could use something like the following (I've converted the code from the answer here which you've inspired me to write :)).
Private Shared Function GetColumnIndex(cellReference As String) As System.Nullable(Of Integer)
If String.IsNullOrEmpty(cellReference) Then
Return Nothing
End If
'remove digits
Dim columnReference As String = Regex.Replace(cellReference.ToUpper(), "[\d]", String.Empty)
Dim columnNumber As Integer = -1
Dim mulitplier As Integer = 1
'working from the end of the letters take the ASCII code less 64 (so A = 1, B =2...etc)
'then multiply that number by our multiplier (which starts at 1)
'multiply our multiplier by 26 as there are 26 letters
For Each c As Char In columnReference.ToCharArray().Reverse()
columnNumber += mulitplier * (CInt(c) - 64)
mulitplier = mulitplier * 26
Next
'the result is zero based so return columnnumber + 1 for a 1 based answer
'this will match Excel's COLUMN function
Return columnNumber + 1
End Function
Note: the VB might not be idiomatic as I used the Telerik Converter to convert it from C# to VB.

VB6 comparing only numeric characters in srings

I need to compare phone numbers from a CSV file to phone numbers in an SSMS database in VB6 without using the .Net Library. One may have a number as 555-555-5555 and the other may have the same number as (555) 555-5555 which obviously kicks back as different when strings are compared.
I know I can use for loops and a buffer to pull out only numeric characters like:
Public Function PhoneNumberNumeric(PhoneNumberCSV As String) As String
Dim CharNdx As Integer
Dim buffer As String
For CharNdx = 1 To Len(PhoneNumberCSV) Step 1
If IsNumeric(Mid(PhoneNumberCSV, CharNdx, 1)) Then
buffer = buffer + Mid(PhoneNumberCSV, CharNdx, 1)
End If
Next
PhoneNumberNumeric = buffer
End Function
but this is expensive. Is there a less expensive way to do this?
This should be a bit quicker:
Private Function Clean(ByRef Original As String) As String
Dim I As Long
Dim J As Long
Dim Char As Long
Clean = Space$(10)
For I = 1 To Len(Original)
Char = AscW(Mid$(Original, I, 1))
If 48 <= Char And Char <= 57 Then
J = J + 1
If J > 10 Then Exit For 'Or raise an exception.
Mid$(Clean, J, 1) = ChrW$(Char)
End If
Next
End Function
It avoids string concatenation, ANSI conversions, and VBScript-form "pigeon VB" (use of slow Variant functions).

How do I get the corresponding Hex value of an RGB color in Excel/VBA?

I'm trying to set a public const of a color in my VBA code. Normally, I can use:
Dim BLUE As Long
BLUE = RGB(183, 222, 232)
However, there's no way to public const that because of the RGB function. I converted this RGB value to Hex using an online converter, and I got back B7DEE8
Using:
BLUE = &HB7DEE8
results in a completely different color. I think this may actually be an RGBA color, and I've tried B7DEE8__ and got the color pretty close (with the last digit being B8), but I'd like to know how to actually find the correct value.
Note: I don't really need code to convert this to hex, I just need to know how to find it, because I have five constant colors I use on my Excel sheet, and I'd like to set them up.
You'll have to reverse the bytes into order
BLUE = &HE8DEB7
to get the correct color value.
The reason for the apparent reversal is that the RGB() function actually creates a BGR value.
More specifically, the red byte is the low order byte and the blue byte is the high order byte (or third of four at least).
Try this example in the Immediate window:
x = RGB(255, 0, 128) ' full red, half blue
? hex(x)
8000FF
x = RGB(128, 0, 255) ' half red, full blue
? hex(x)
FF0080
Note that the "full" byte (255 or FF) and the "half-full" byte (128 or 80) end up on the opposite sides in each result. That's why you need to specify the hex constant in the reverse order from what you'd expect to get the same value.
Also, no need to use an online converter. The Hex() function provides the hex value of the number given to it, and Int will take a string in hex format and return the decimal value:
? Int("&hff0000")
16711680
Update:
So to use this information to create your hex constants, you just run your RGB() and Hex() statements in the Immediate window as above (type Ctrl+G to open and close it), then use the resulting Hex value as your constant. If the value is less than 6 digits long, you can pad it on the left with zeros, but that's technically not necessary:
x = RGB(183, 222, 232)
? "Public Const MyBlue = &h" & hex(x)
Public Const MyBlue = &hE8DEB7
then copy that last line into your code.
OK, the following will take the color of a cell in Excel 2010 and provide a valid Hexcode:
Public Function getHexCol(a As Range)
' In excel type in for example getHexCol(A1) to get the hexcode of the color on A1.
Dim strColour As String
Dim hexColour As String
Dim nColour As Long
Dim nR As Long, nB As Long, nG As Long
strColour = a.Interior.Color
If Len(strColour) = 0 Then Exit Function
nColour = Val(strColour) ' convert string to decimal number
hexColour = Hex(nColour) ' convert decimal number to hex string
While Len(hexColour) < 6 ' pad on left to 6 hex digits
hexColour = "0" & hexColour
Wend
nB = CLng("&H" & Mid(hexColour, 1, 2))
nG = CLng("&H" & Mid(hexColour, 3, 2))
nR = CLng("&H" & Mid(hexColour, 5, 2))
getHexCol = Hex(RGB(nB, nG, nR))
End Function
Function GetRGB(ByVal cell As Range) As String
Dim R As String, G As String
Dim b As String, hexColor As String
hexCode = Hex(cell.Interior.Color)
'Note the order excel uses for hex is BGR.
b = Val("&H" & Mid(hexCode, 1, 2))
G = Val("&H" & Mid(hexCode, 3, 2))
R = Val("&H" & Mid(hexCode, 5, 2))
GetRGB = R & ":" & G & ":" & b
End Function
note that excel RGB values are backwards (BGR)
Here is another function that also works in MS Access and accounts for the reverse RGB order:
Function VBA_RGB_To_HEX(iRed As Integer, iGreen As Integer, iBlue As Integer) As String
Dim sHex As String
sHex = "#" & VBA.Right$("00" & VBA.Hex(iBlue), 2) & VBA.Right$("00" & VBA.Hex(iGreen), 2) & VBA.Right$("00" & VBA.Hex(iRed), 2)
VBA_RGB_To_HEX = sHex
End Function
I tested this code, cant realy follow Howard's answer
Dim rd, gr, bl As Integer
rd = 183
gr = 222
bl = 232
BLUE = RGB(rd, gr, bl)
hexclr = Format(CStr(Hex(rd)), "00") +
Format(CStr(Hex(gr)), "00") +
Format(CStr(Hex(bl)), "00")
MsgBox hexclr 'B7DEE8

How do I convert VB's Double to COBOL's COMP-3?

Does anyone here know how to convert a VB Double to Cobol S9(15)V99 Comp-3 data type?
If it doesn't need to be done in the same program, it seems to me it would be easier to find a common format that both VB and COBOL can understand.
That would be text. In other words, the simplest solution may be to write the number out to a file as text "3.14159" and have the COBOL code read it in in that format and MOVE it to the COMP-3 field?
If that's not possible, the COMP-3 is a fairly simple BCD type. I would convert the number to a string anyway then take it two characters at a time into a byte array.
The S9(15)V99 requires 18 nybbles (a nybble being 4 bits, or half an octet) to store:
the integer bit (fifteen nybbles).
the fractional bit (two nybbles).
the sign (one nybble).
No space is needed for the decimal point since a V is an implied decimal, not a real one.
So the number 3.14 would be represented as the bytes:
00 00 00 00 00 00 00 31 4C
The only tricky bit is that final sign nybble (C for positive and D for negative).
Here's a bit of code I whipped up in Excel VBA (I don't have VB installed on this machine unfortunately) that shows you how to do it. The makeComp3() function should be easily transferred into a real VB program.
The macro test program outputs the values 0, 49 and 76 which are hex 00, 31 and 4C respectively (00314C is +3.14).
The first step (after all the declarations) is to make the double an implied decimal by multiplying it by the relevant power of ten then turning it into an integer:
Option Explicit
' makeComp3. '
' inp is the double to convert. '
' sz is the minimum final size (with sign). '
' frac is the number of fractional places. '
Function makeComp3(inp As Double, sz As Integer, frac As Integer) As String
Dim inpshifted As Double
Dim outstr As String
Dim outbcd As String
Dim i As Integer
Dim outval As Integer
Dim zero As Integer
zero = Asc("0")
' Make implied decimal. '
inpshifted = Abs(inp)
While frac > 0
inpshifted = inpshifted * 10
frac = frac - 1
Wend
inpshifted = Int(inpshifted)
Next, we make it into a string of the correct size, to make processing easier:
' Get as string and expand to correct size. '
outstr = CStr(inpshifted)
While Len(outstr) < sz - 1
outstr = "0" & outstr
Wend
If Len(outstr) Mod 2 = 0 Then
outstr = "0" & outstr
End If
Then we process that string two digits at a time and each pair is combined into an output nybble. The final step is to process the last digit along with the sign:
' Process each nybble pair bar the last. '
outbcd = ""
For i = 1 To Len(outstr) - 2 Step 2
outval = (Asc(Mid(outstr, i)) - zero) * 16
outval = outval + Asc(Mid(outstr, i + 1)) - zero
outbcd = outbcd & Chr(outval)
Next i
' Process final nybble including the sign. '
outval = (Asc(Right(outstr, 1)) - zero) * 16 + 12
If inp < 0 Then
outval = outval + 1
End If
makeComp3 = outbcd & Chr(outval)
End Function
And this is just the test harness, though it could probably do with a few more test cases :-)
Sub Macro1()
Dim i As Integer
Dim cobol As String
cobol = makeComp3(3.14159, 6, 2)
For i = 1 To Len(cobol)
MsgBox CStr(Asc(Mid(cobol, i)))
Next i
End Sub