Eu descobri esta questão SO que estava perguntando sobre uma maneira para converter valores hexadecimais maiores em um valor numérico positivo:
?Val("&H8000") -32768 Val("&HFFFF") -1
Minha resposta envolveu iterar os dígitos da string um por um e calcular seus respectivos valores no resultado:
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
Funciona, mas eu consigo ” não deixe de pensar que fiz algo estupidamente complicado demais para algo que deveria ser bem simples.
Há uma maneira melhor, não é?
Comentários
Resposta
Pelo que posso dizer, uma string começando com &H
é um literal hexadecimal.
Existe um número de funções de conversão que podem converter uma expressão para o tipo desejado.
Então, deve ser simplesmente, dependendo do tipo desejado:
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
Moeda vs duplo
Valor inteiro positivo máximo representável com precisão:
-
&H0020000000000000
(9.007.199.254.740.992) para Duplo ( IEEE 754 binary64 ) -
&H000346DC5D638865
(922.337.203.685.477) para Moeda
Então, por que usar Currency
em vez de Double
quando o último funciona para um intervalo maior de inteiros?
Currency
é sempre preciso. Se estourarmos um valor Currency
, obteremos um erro. Se estourarmos o valor inteiro representável máximo de um double, obteremos um valor inteiro aproximado:
Dim doubleMax As Double Dim doubleAfter As Double doubleMax = CDbl("&H0020000000000000") doubleAfter = doubleMax + 1 MsgBox "Double before: " & Format(doubleMax, "#") & vbNewLine & "after: " & Format(doubleAfter, "#") Dim currencyMax As Currency Dim currencyAfter As Currency currencyMax = CCur("&H000346DC5D638865") currencyAfter = currencyMax + 1 MsgBox "Currency before: " & Format(currencyMax, "#") & vbNewLine & "after: " & Format(currencyAfter, "#")
A saída deste exemplo é:
Double before: 9007199254740990 after: 9007199254740990
E então um run-time error "6": Overflow
que é ótimo se você deseja evitar erros de arredondamento. Agora, o MSDN afirma que Double
é
armazenado como número de ponto flutuante IEEE de 64 bits (8 bytes)
mas se você leu qualquer coisa sobre o IEEE 754 binário64 , você deve ser um pouco surpreso com a saída do exemplo. O máximo real é &H00038D7EA4C68000
(1.000.000.000.000.000).
Comentários
- Incrível! Eu ' vinculei a esta resposta em uma edição da minha resposta SO 🙂
- Eu expandi um pouco sobre
Currency
vsDouble
um pouco e descobriu algo interessante. - que tal
doubleMax + doubleMax
jogar um erro de estouro? - @Meehow Acabei de testar e, aparentemente,
Double
no VBA transborda. Mas ' ainda está não é possível estourar se o resultado analisado for um número inteiro. I ' Fico feliz por não ' não preciso trabalhar com esse idioma. - A maneira idiomática correta de fazer isso é usar uma das funções de conversão integradas, como @Meehow comentou no OP – Eu não ' não sei o que brainfart me fez escrever esta função …
Short
,Integer
,Long
,UShort
,UInteger
,ULong
ouDecimal
.Cdbl("&HFFFF")
?CDbl
é errado para este cenário.