こんにちは、Ryoです。
16進数の値から10進数で符号有無それぞれの値を計算する処理を考える機会があったのですが、この辺りの計算方法などは使いたい時には忘れてしまってることも多いので、今後の為にも16進数⇒2進数⇒10進数変換について小数点対応も含めてVBAで製作してみましたので、その内容について書いていきたいと思います。
1.サンプル概要
元値は16進数で入力しますので、「0-9」「A-F」を使用して文字入力を行います。この時に小数点は対応可なので問題ありませんが、上述以外の文字が入力された場合は対象文字のみの修正を要求する形にしています。
≪入力文字が対象外の場合≫
これは例として「F3BZ」と入力して実行したので、4文字目の「Z」が対象外により再入力を求めています。この文字のみ修正すれば良いので例えば「Z」を「5」に修正するなら5と入力すればOKです。
実行結果はVBAエディタ上のイミディエイトウィンドウに出力していますので、このような形で結果出力を行います。
≪小数点なし≫
≪小数点あり≫
このように16進数の値から2進数と符号有無での10進数値を計算して表示するものになりますが、桁合わせ処理も実施しており、入力値から8bit/16bit/24bit/32bit/64bitを自動判定し処理する形としています。
2.サンプルコード
Sub Sample1()はメイン処理側として入力データの2進数置換、及び2の補数計算処理などを実行します。
◆メイン処理側:Sample1
Sub Sample1() Dim i As Long, n As Long, num As Long Dim Pos_D As Integer, cnt As Integer, dig As Integer Dim zcnt As Integer Dim tmp As String, strD As String Dim strHex As String, strA As String Dim b As String, c As String, dnum As String Dim B_arr(0 To 15) As String Dim Dtb() As Variant, e_Dat As Variant Do 'Application.InputBoxはキャンセルボタン=Falseを取得する目的 strHex = Application.InputBox _ (Prompt:="0~9、A~F(a~f)を使用して文字入力してください" & _ vbLf & "(符号有無、小数点対応可)") 'キャンセルボタンが押されたら終了 If strHex = "False" Then Exit Sub 'InputBoxに入力された場合は処理を実行する dig = 0 '入力値のカンマ有無を確認 If InStr(strHex, ".") <> 0 Then dig = 1 If Len(strHex) <> 0 And Len(strHex) - dig < 17 Then Exit Do Else '未入力(文字数0)や文字数過多の場合は再表示 Debug.Print "Error: 未入力、または入力文字数が多すぎます" End If Loop '入力値をイミディエイトウィンドウへ出力(表示) Debug.Print "入力値: " & strHex '予め16進数/2進数対応表を配列に準備しておく B_arr(0) = "0000" '0 B_arr(1) = "0001" '1 B_arr(2) = "0010" '2 B_arr(3) = "0011" '3 B_arr(4) = "0100" '4 B_arr(5) = "0101" '5 B_arr(6) = "0110" '6 B_arr(7) = "0111" '7 B_arr(8) = "1000" '8 B_arr(9) = "1001" '9 B_arr(10) = "1010" 'A B_arr(11) = "1011" 'B B_arr(12) = "1100" 'C B_arr(13) = "1101" 'D B_arr(14) = "1110" 'E B_arr(15) = "1111" 'F '***16進数の文字を2進数(4bit)に置換する*** '桁合わせ処理(8bit/16bit/24bit/32bit/64bitに合わせる) If Len(strHex) - dig < 9 And _ (Len(strHex) - dig) Mod 2 <> 0 Then strHex = "0" & strHex ElseIf Len(strHex) - dig > 8 And _ 16 > (Len(strHex) - dig) Then zcnt = 16 - (Len(strHex) - dig) For i = 1 To zcnt strHex = "0" & strHex Next i End If For i = 1 To Len(strHex) '入力されたアルファベットを半角/大文字に変換 '1文字ずつ切り出して処理を実行する tmp = UCase(StrConv(Mid(strHex, i, 1), vbNarrow)) strAdd: Select Case tmp Case "A" To "F" 'ASCII文字コードを利用して対応表を判定させる 'A~F:ASCIIコードで65~70 '例:Bの場合、66-65+10=11⇒B_arr(11) '「Space(1)」は半角スペースを追加(視認性向上) strA = strA + B_arr(Asc(tmp) - Asc("A") + 10) _ + Space(1) Case 0 To 9 strA = strA + B_arr(tmp) + Space(1) Case "." strA = strA + tmp + Space(1) Case Else '入力された文字が指定範囲外の場合の処理 'その対象となる文字のみ入力すれば可 tmp = Application.InputBox(i & "文字目の" & _ tmp & "は対象外の文字です。" & vbCrLf & _ "0~9、A~Fの範囲でこの文字のみ再入力してください。") 'キャンセルボタンが押されたら終了 If tmp = "False" Then Exit Sub '入力された文字を半角/大文字化 tmp = UCase(StrConv(tmp, vbNarrow)) GoTo strAdd: End Select Next 'イミディエイトウィンドウへ2進数変換値出力 Debug.Print "2進数: " & strA '視認性の為に使用した半角スペースを除去 strA = Replace(strA, " ", "") '***2進数から10進数に変換する(符号なし)*** Call binCalc1(strA, b) '2進数⇒10進数変換処理実行 Debug.Print "符号なし10進数: " & b '10進数表示・・・符号なし '※処理実行のbinCalc1()自体は符号有無に関わらず共通 '***2進数から10進数に変換する(符号あり)*** '2進数の最初の文字列が「1」であれば符号(-)なので、 '符号付きでの10進数変換処理を行う If Mid(strA, 1, 1) = 1 Then For n = 1 To Len(strA) dnum = Mid(strA, n, 1) 'コンマはそのまま文字列として結合 If dnum = "." Then strD = strD + dnum Else '2の補数で変換を行う為、先ずは反転させる '反転=0⇒1、1⇒0とする為、排他的論理和 'Xor1にて処理 '1 xor 1 = 0 ,0 xor 1 = 1 strD = strD + CStr(dnum Xor 1) End If Next n '最終ビットを10進数に変換 For i = 1 To 4 e_Dat = e_Dat + 2 ^ (4 - i) * _ CInt(Mid(Mid(strD, Len(strD) - 3), i, 1)) Next i '変換した最終ビットの値が15未満か否かを確認 If e_Dat < 15 Then '15未満であれば最終ビットの値に+1し、 '配列B_arrから対応する2進数を選定して '最終ビットを置換 e_Dat = B_arr(e_Dat + 1) strD = Left(strD, Len(strD) - 4) + e_Dat Else '変換した値が14を超えている場合の処理 '14を超える=15="1111"なので、最終ビットは '"0000"を割り当て、1つ前の4ビットに対して '順次処理を実行していく e_Dat = B_arr(0) 'コンマの位置を取得する Pos_D = InStr(strD, ".") '2進数のデータからコンマと”0000”と、 '置換した最終ビットを一旦削除 strD = Replace(Left(strD, Len(strD) - 4), ".", "") '繰り返し処理回数把握の為、4bit単位での数を取得 num = Len(strD) \ 4 '以後に実行する演算結果格納用の配列を必要数宣言 ReDim Dtb(1 To num) '4bit単位での繰り返し処理実行 For i = 1 To num '現時点での最終bitに対し10進数への演算処理実行 For n = 1 To 4 Dtb(i) = Dtb(i) + 2 ^ (4 - n) * _ CInt(Mid(Mid(strD, Len(strD) - 3), n, 1)) Next n '演算結果が15未満か否かを確認 If Dtb(i) < 15 Then '15未満であれば現最終ビットの値に+1し、 '配列B_arrから対応する2進数を選定して '最終ビットを置換、For文を抜ける Dtb(i) = B_arr(Dtb(i) + 1) strD = Left(strD, Len(strD) - 4) + Dtb(i) cnt = i - 1 Exit For Else '15以上であれば現最終bitに"0000"を割り当て、 'その割り当てた最終bitを削除する Dtb(i) = B_arr(0) strD = Left(strD, Len(strD) - 4) End If '演算完了まで繰り返し実行する Next i '演算完了後、置換した配列データを結合する For i = cnt To 1 Step -1 strD = strD + Dtb(i) Next i '元データの最終bitデータを結合、 'その後にコンマを元の位置に戻す為、"."を挿入する strD = strD + e_Dat If Pos_D <> 0 Then strD = Left(strD, Pos_D - 1) & _ "." & Mid(strD, Pos_D) End If 'ここまでで2の補数計算が完了しているので、そのデータを '引渡して2進数⇒10進数変換処理(符号付き)を実行する Call binCalc1(strD, c) Debug.Print "符号付き10進数: " & -c '10進数表示・・・符号付 Else Debug.Print "符号付きではありません" End If End Sub
◆サブ処理側:10進数変換
Sub binCalc1(bin_a As String, dec_b As String)は受け渡された2進数データを10進数に変換してメイン側に値を戻す処理を行います。
Sub binCalc1(bin_a As String, dec_b As String) Dim i As Integer, D_Pos As Integer Dim strLen As Integer Dim strP As String Dim Dec_n1 As Double, Dec_n2 As Double '引数の2進数文字列が空の場合は終了 If (bin_a = "") Then Exit Sub End If '10進数値を初期化 Dec_n1 = 0 '2進数文字列数を取得 strLen = Len(bin_a) '2進数値のコンマ有無により処理分岐 If InStr(bin_a, ".") Then 'コンマの位置を取得 D_Pos = InStr(bin_a, ".") '2進数文字列を1文字ずつループ For i = 1 To strLen '2進数文字列を1文字毎に切り出して処理を実行 'D_Pos=i⇒コンマなので除外する If i <> D_Pos Then strP = Mid(bin_a, i, 1) Select Case i '整数側の処理(2進数⇒10進数) Case Is < D_Pos Dec_n1 = Dec_n1 + 2 ^ (D_Pos - 1 - i) _ * CInt(strP) '小数側の処理(2進数⇒10進数) Case Is > D_Pos n = n + 1 Dec_n2 = Dec_n2 + 2 ^ -n * CInt(strP) End Select Next i Else 'コンマなしでの処理 For i = 1 To strLen '2進数文字列を1文字毎切り出し strP = Mid(bin_a, i, 1) '整数側の処理(2進数⇒10進数) Dec_n1 = Dec_n1 + 2 ^ (strLen - i) * CInt(strP) Next End If '整数側と小数側の総和=10進数 Dec_n1 = Dec_n1 + Dec_n2 '算出した10進数値を文字列に変換 dec_b = CStr(Dec_n1) End Sub
このサンプルコードを標準モジュール上に記述し実行すれば、概要に示す通り16進数データを変換し表示します。
それぞれの変換の計算内容など詳細については割愛しますが、サンプルコードの主な部分について以下に解説していきます。
3.解説
変換や計算における解説というよりは、処理する上で知っておくと便利そうな部分に焦点をあてて書いていきます。
サンプルコード全体の流れとしては、「データ入力要求」⇒「入力された値をウィンドウ出力」⇒「1文字毎切り出して2進数変換/誤入力文字修正要求」⇒「2進数データをウィンドウ出力」⇒「符号なし10進数変換/ウィンドウ表示⇒符号付き10進数変換前処理(2の補数計算)」⇒「符号付き10進数変換/ウィンドウ出力」となっています。
◆メイン処理側:データ入力要求~16進数⇒2進数変換
最初にInputBoxで変換したい元データの入力を要求します。入力されるまでDo~Loopで延々繰り返しますが、キャンセル処理を記述することでLoopから抜けるようにしています。
Application.InputBoxはキャンセルボタンでFalseが戻ってきますので、それで判定します。
If strHex = “False” Then Exit Sub
このサンプルコードでは最大64bitとしていますので、入力文字数を確認し過多(16文字超)であれば再入力を促します。
dig = 0
If InStr(strHex, “.”) <> 0 Then dig = 1
If Len(strHex) <> 0 And Len(strHex) – dig < 17 Then
Exit Do
Else
Debug.Print “Error: 未入力、または入力文字数が多すぎます”
End If
データが入力されたらDebug.Print “入力値: ” & strHexとしてイミディエイトウィンドウに表示しておきます。これは後で何を入力元データにしたのかわからなくなることがあるので私には結構大事です。
入力元データに対して桁合わせを行うので元データの文字列数からカンマを引き、Mod2(2で割り余り有無を判定)で奇数/偶数判定しています。奇数且つ文字列数が9未満ならデータの先頭に”0”を付加し、文字列数が9以上16未満なら必要数の”0”を付加して桁合わせを自動判定しています。
16進数/2進数変換については対応表を予め配列で準備しておくと処理が楽なので0-FまでB_arr(n)として書いておき、その対応表を用いて入力データから切り出された文字に対応する2進数データに置換していきます。
◆参考:文字変換(半角/大文字)、半角スペース追加と削除方法について
この時に入力された文字列が不揃い(大文字/小文字/全角/半角の混在)の場合があるので、UCase関数とStrConv関数を利用して半角/大文字に統一します。
tmp = UCase(StrConv(Mid(strHex, i, 1), vbNarrow))
UCase/StrConv関数などは以前に記事でまとめていますので、ご参考までに。
切り出された文字はSelect Case文で条件ごとに処理していきますが、A-Fの文字列についてはコメントに記述した通りASCII文字コード(ASC)を利用して判定しています。
strA = strA + B_arr(Asc(tmp) – Asc(“A”) + 10) + Space(1)
ここで使用しているSpace(1)は「半角スペースを追加」するものです。これは使用しなくても良いのですが、ウィンドウ出力した際に2進数文字列が0000111100101110のように大変見難いことになるので視認性を向上させる目的で利用しています。
後は1文字ずつ処理している状況を利用して対象外の文字を検出したら再入力を促し、その文字のみを取得して同様の置換を行います。この場合対象文字が修正されるまで処理を停滞させる必要があるので、Goto文を利用してループさせています。
16進数⇒2進数への変換が完了したらイミディエイトウィンドウに出力して表示させます。表示出力後は2進数データ内の半角スペースは必要ありませんのでReplace関数を利用して除去します。
strA = Replace(strA, ” “, “”)
Replace関数についても以前に記事でまとめていますので、ご参考までに。
ここまでで16進数⇒2進数の変換は完了しているので、2進数から10進数への変換を符号付き/符号なしでそれぞれ実行します。
◆メイン処理側:2の補数計算
符号なし10進数については計算するのみなので後述するSub binCalc1(bin_a As String, dec_b As String)で説明します。
符号付き10進数については、先ず最初の文字列が「1」であれば符号ありの処理を行います。先ずは2の補数で変換する必要があるので排他的論理和(1 Xor 1 = 0, 0 Xor 1 = 1)を利用して反転させていますが、1-n(nが0 or 1)でも反転させるだけなら問題ないので良いかと思います。
strD = strD + CStr(dnum Xor 1)
2進数データを反転させた後に最終ビットに+1とする必要があるので、最終ビット(4bit)を10進数に変換します。あえて変換しなくても計算する手段は多々あろうかと思いますので、この方法はご参考までに。
当然ながら計算する際には文字列から数値に変換する必要があるのでCInt関数を使用します。
For i = 1 To 4
e_Dat = e_Dat + 2 ^ (4 – i) * CInt(Mid(Mid(strD, Len(strD) – 3), i, 1))
Next i
変換した10進数データに1を加算して15以下であれば、再度配列の対応表を利用して2進数値を取得し、最終bitを置換します。
e_Dat = B_arr(e_Dat + 1)
strD = Left(strD, Len(strD) – 4) + e_Dat
変換した最終ビットが15(F)であれば1を加算することで繰り上がりとなるので、”0000″として配列格納し次の4bitを参照/10進数変換して1加算し判定・・・をサンプルコード内のコメントに記述の通り処理を順次繰り返していきます。
演算完了後、それぞれ配列に格納した2進数データを結合し小数点(コンマ)を元の位置に戻します。
◆参考:文字列の途中に文字を挿入する
If Pos_D <> 0 Then strD = Left(strD, Pos_D – 1) & “.” & Mid(strD, Pos_D)
文字列の途中に文字を挿入する場合、このようにLeft関数とMid関数を利用すれば対処できます。この場合ではLeft関数で挿入したい位置までの文字列を抜取り、そこに”.”コンマを&で追加してMid関数でコンマ以降の文字列を抜取り、それぞれを&で結合するという形です。
例えば11110000の場合、Left関数で4文字までの1111を抜取り、そこに&で”.”を追加、次にMid関数で5文字目からの0000を抜き取っておいて結合するので1111 . 0000となるイメージです。記述するとLeft(対象文字列,4)& “.” & Mid(対象文字列,5)のようになります。
ここまでで2の補数計算は完了しているので、サブ処理のSub binCalc1(bin_a As String, dec_b As String)に値を引き渡して10進数変換を行います。
◆サブ処理側:2進数→符号有無の10進数変換処理
符号なしはメイン処理側のCall binCalc1(strA, b)で値を引き渡し、符号ありは Call binCalc1(strD, c)の記述でサブ処理側へ渡しています。
こちらの処理は渡された2進数文字列に対して単純に計算を実行して値をメイン側に返すものになりますが、小数点に対応する為にコンマ有無を確認して計算処理を分けています。
'2進数値のコンマ有無により処理分岐 If InStr(bin_a, ".") Then 'コンマの位置を取得 D_Pos = InStr(bin_a, ".") '2進数文字列を1文字ずつループ For i = 1 To strLen '2進数文字列を1文字毎に切り出して処理を実行 'D_Pos=i⇒コンマなので除外する If i <> D_Pos Then strP = Mid(bin_a, i, 1) Select Case i '整数側の処理(2進数⇒10進数) Case Is < D_Pos Dec_n1 = Dec_n1 + 2 ^ (D_Pos - 1 - i) _ * CInt(strP) '小数側の処理(2進数⇒10進数) Case Is > D_Pos n = n + 1 Dec_n2 = Dec_n2 + 2 ^ -n * CInt(strP) End Select Next i Else 'コンマなしでの処理 For i = 1 To strLen '2進数文字列を1文字毎切り出し strP = Mid(bin_a, i, 1) '整数側の処理(2進数⇒10進数) Dec_n1 = Dec_n1 + 2 ^ (strLen - i) * CInt(strP) Next End If
計算方法などの詳細は割愛しますが小数部有無によって計算処理が異なるので、コンマ位置や有無を把握してからコンマありなら整数部と小数部でそれぞれ計算し最終的に合算することで変換後の値を求め、コンマ無しであれば全て整数側の処理として計算します。
dec_b = CStr(Dec_n1)で10進数値を文字列に変換してメイン処理側に返し、イミディエイトウィンドウに出力して処理完了となります。
4.まとめ
以前に必要があって16進数の文字列を2進数に置き換えるだけの単純なものを作っていたので、備忘録的に投稿しようと思ったのですが、それなら小数点対応や桁合わせまで出来たほうが。。。などと考えて思いつくままにザーッと書いてみたサンプルコードになります。なので動作は大丈夫かと思いますが記述や構成がかなり粗いのでいずれ時間があるときにでも見直します。
この手の計算はこうして実際にやってる間は頭に入ってますがすぐに忘れてしまうものなので、そんな時に見返して思い出してみたり、ちょっとした計算などにつかっていけたらと思います。
ちなみに10進数から2進数/16進数に変換(符号なし/小数点対応)する方法は以下になりますので、ご参考までに。
以上、【VBA】16進数⇒2進数/10進数(符号有無/小数点対応)変換を行う方法についてでした!今回の記事が何かの参考になれば幸いです。
Ryo