さくらのジャンク箱ロゴ

Sakura87がほぼ月刊でお届けするPCや電子工作と写真の備忘録てきなブログ @なんと20周年

ExcelのVBAにてdouble型16進数の浮動小数点を10進数へ変換する

この記事は8年ほど前に投稿されました。内容が古くなっている可能性がありますので更新日時にご注意ください。

realex

某所で利用していた製品のパラメータファイルが、整数および実数型の16進数で記録されていまして。

それを専用ツールにて10進数実数値に戻していましたが。専用ツールが老朽化しており、また、エクセルのマクロに貼り付けるのに手間がかかっていたため、エクセルマクロを作れと言われて作ったのですが。作る過程で製品のパラメータデータを10進数で取り込めるコマンドがあることに気づいたので実際にはそちらが採用されました。

しかし、私が個人で個人の家でIEEE754形式の実数値に関する資料をまとめ、コードを作り、某所に持って行く前に不採用となりましたので。せっかく作ったので誰か拾ってください。

なお、マクロが使用できない環境のために、マクロを排してすべてエクセル関数で作ったバージョンはこちらです。

仕組み

まずこのIEEE754形式というのは、一般的なコンピュータで利用されている浮動小数点演算の標準規格です。

殆どの言語でdouble ○○ と定義すれば扱えるアレです。

double型は8バイトの変数ですが。これの中身は

  • 先頭1bitが符号ビット(値が正なのか負なのか)
  • 指数部11bit
  • 仮数部52bit

となっています。

この格納データをもとに指数部と仮数部を計算し、フォーマット通りに計算してやることで16進数のデータから実数値が取得できます。

hex2bin

作成環境

このマクロはエクセル2003で作成しており、2000・2003・2013で動作確認を行いました。
また、検算については、某所で頂いたパラメータファイルの他、eimei’s laboratoryさんの作った「浮動小数点値 ○進表示 Version 1.2」にて数値及び小数点桁数の異なる数値データを利用して小数点第3位まで相違ない事を確認しています。

コード

以下に全体のコードを貼り付けておきます。

Sub Main()

    '変数を定義
        Dim BinConvCnt, FracDataCnvCnt, ExpDataDecConvCnt, SpaceDeleteCnt As Integer 'ループ用カウンター
        Dim FracDataCnvBit, ExpDataDecConvBit As Integer 'ビット単位演算用変数
        
        'チェックシートに反映されるデータ
        Dim Binary, InputData As String
        Dim RealHexToDec As Double
        
        '内部変数 ○○Temp=テンポラリ ○○Cnv=変換系
        Dim SpaceDelete, SpaceDeleteTemp, BigToLittle, BigToLittleTmp As String '入力値整形用
        Dim HexToBin, BinTemp, NegaPosiFlag, ExpData, FracData As String
        Dim FracDataCnv, FracDataCnvTemp, ExpDataDecConv, ExpDataDecConvTemp As Double
        
    'シート内のデータ読み込み
        InputData = ThisWorkbook.Sheets(1).Cells.Range("C4").Value '16進数の値が入っているセルを指定
            If InputData = "" Then: MsgBox ("数値を入力してください。"): End
        
        
    '入力値を大文字に変換し、半角スペースを削除
        InputData = StrConv(InputData, vbUpperCase)
            For SpaceDeleteCnt = 0 To Len(InputData) - 1
                SpaceDeleteTemp = Mid(InputData, SpaceDeleteCnt + 1, 1)
                If SpaceDeleteTemp <> " " Then SpaceDelete = SpaceDelete + SpaceDeleteTemp
            Next SpaceDeleteCnt
        InputData = SpaceDelete
    
    
    '※ビッグエンディアンの数値を変換する場合は以下のコメントを外す
    '--------以下から
    '    Dim BigToLittleCnt As Integer
    '    For BigToLittleCnt = 0 To (Len(InputData) / 2) - 1
    '        BigToLittleTmp = Mid(InputData, 1 + (BigToLittleCnt * 2), 2)
    '       BigToLittle = BigToLittleTmp + BigToLittle
    '    Next BigToLittleCnt
    '    InputData = BigToLittle
    '-------以上まで
    
    
    '16進数の入力値を2進数に変換する
    'エクセルには上記の変換命令が無いので1文字ずつ切り出して置き換え
    'つなぎ合わせる
        For BinConvCnt = 0 To Len(InputData) - 1
            HexToBin = Mid(InputData, BinConvCnt + 1, 1)
                If HexToBin = "0" Then BinTemp = "0000"
                If HexToBin = "1" Then BinTemp = "0001"
                If HexToBin = "2" Then BinTemp = "0010"
                If HexToBin = "3" Then BinTemp = "0011"
                If HexToBin = "4" Then BinTemp = "0100"
                If HexToBin = "5" Then BinTemp = "0101"
                If HexToBin = "6" Then BinTemp = "0110"
                If HexToBin = "7" Then BinTemp = "0111"
                If HexToBin = "8" Then BinTemp = "1000"
                If HexToBin = "9" Then BinTemp = "1001"
                If HexToBin = "A" Then BinTemp = "1010"
                If HexToBin = "B" Then BinTemp = "1011"
                If HexToBin = "C" Then BinTemp = "1100"
                If HexToBin = "D" Then BinTemp = "1101"
                If HexToBin = "E" Then BinTemp = "1110"
                If HexToBin = "F" Then BinTemp = "1111"
            Binary = Binary + BinTemp
        Next BinConvCnt
    '8桁の16進数=整数値として認識させる。
        If Len(InputData) > 9 Then
            '符号(プラス=0 マイナス=1)
            '指数(11bit値) 仮数部(52bit値)に分割
                NegaPosiFlag = Mid(Binary, 1, 1)
                ExpData = Mid(Binary, 2, 11)
                FracData = Mid(Binary, 13)
                
            '指数(2進数)を10進数に変換する
                For ExpDataDecConvCnt = 0 To 10
                    ExpDataDecConvBit = Mid(ExpData, 1 + ExpDataDecConvCnt, 1)
                    ExpDataDecConvTemp = ExpDataDecConvBit * (2 ^ (10 - (ExpDataDecConvCnt)))
                    ExpDataDecConv = ExpDataDecConv + ExpDataDecConvTemp
                Next ExpDataDecConvCnt
                
            '仮数部を小数点に変換する。
                For FracDataCnvCnt = 0 To (Len(FracData) - 1)
                    FracDataCnvBit = Mid(FracData, 1 + FracDataCnvCnt, 1)
                    FracDataCnvTemp = FracDataCnvBit * (1 / (2 ^ (FracDataCnvCnt + 1)))
                    FracDataCnv = FracDataCnv + FracDataCnvTemp
                Next FracDataCnvCnt
                
            '最終的な計算
                RealHexToDec = ((-1) ^ Int(NegaPosiFlag) * (2 ^ (ExpDataDecConv - 1023)) * (1 + FracDataCnv))
                
            '実数用変換命令終わり
                
        Else
            '整数の場合は標準命令で対応できるので
            '標準命令に丸投げする。
            RealHexToDec = Int("&h" & InputData)
        End If
        
    '書き出し
        ThisWorkbook.Sheets(1).Cells.Range("C5").Value = Binary '演算したバイナリ値の書き出し
        ThisWorkbook.Sheets(1).Cells.Range("C6").Value = RealHexToDec '演算した10進数の値を書き出し
    
    
End Sub

コードの説明

    '変数を定義
        Dim BinConvCnt, FracDataCnvCnt, ExpDataDecConvCnt, SpaceDeleteCnt As Integer 'ループ用カウンター
        Dim FracDataCnvBit, ExpDataDecConvBit As Integer 'ビット単位演算用変数
        
        'チェックシートに反映されるデータ
        Dim Binary, InputData As String
        Dim RealHexToDec As Double
        
        '内部変数 ○○Temp=テンポラリ ○○Cnv=変換系
        Dim SpaceDelete, SpaceDeleteTemp, BigToLittle, BigToLittleTmp As String '入力値整形用
        Dim HexToBin, BinTemp, NegaPosiFlag, ExpData, FracData As String
        Dim FracDataCnv, FracDataCnvTemp, ExpDataDecConv, ExpDataDecConvTemp As Double
        
    'シート内のデータ読み込み
        InputData = ThisWorkbook.Sheets(1).Cells.Range("C4").Value '16進数の値が入っているセルを指定
            If InputData = "" Then: MsgBox ("数値を入力してください。"): End
        
        
    '入力値を大文字に変換し、半角スペースを削除
        InputData = StrConv(InputData, vbUpperCase)
            For SpaceDeleteCnt = 0 To Len(InputData) - 1
                SpaceDeleteTemp = Mid(InputData, SpaceDeleteCnt + 1, 1)
                If SpaceDeleteTemp <> " " Then SpaceDelete = SpaceDelete + SpaceDeleteTemp
            Next SpaceDeleteCnt
        InputData = SpaceDelete

まず前準備です。必要な変数の定義、変換前データの読み込み、整形。

まず読み込みに関しては、16進数の値をInputData変数に入れ込んでやります。
その後前後のスペースを削除し、大文字に変換していますが。変数に代入される値が大文字の16進数で固定されている場合は最後の6行は不要です。

また、今回のコードはもともと搭載する予定だった対象製品パラメータの仕様に合わせリトルエンディアンを前提に作成しています。
よってビッグエンディアンのコードで使用する場合は上記全体コードにある以下の行のコメントをはずしてください。

    '    Dim BigToLittleCnt As Integer
    '    For BigToLittleCnt = 0 To (Len(InputData) / 2) - 1
    '        BigToLittleTmp = Mid(InputData, 1 + (BigToLittleCnt * 2), 2)
    '       BigToLittle = BigToLittleTmp + BigToLittle
    '    Next BigToLittleCnt
    '    InputData = BigToLittle

次に16進数を2進数に変換するのですが。今回16進数のコードは導入予定だったマクロの仕様に合わせ文字列として格納していますので1文字ずつ抜き出してIF文で2進数に変換するコードを使います。どうせ型変換を2回以上使わないといけなくなるため文字列→整数値→2進数→10進数 とする所を 文字列→2進数(文字列)とすっ飛ばすことにします。

    
    '16進数の入力値を2進数に変換する
    'エクセルには上記の変換命令が無いので1文字ずつ切り出して置き換え
    'つなぎ合わせる
        For BinConvCnt = 0 To Len(InputData) - 1
            HexToBin = Mid(InputData, BinConvCnt + 1, 1)
                If HexToBin = "0" Then BinTemp = "0000"
                If HexToBin = "1" Then BinTemp = "0001"
                If HexToBin = "2" Then BinTemp = "0010"
                If HexToBin = "3" Then BinTemp = "0011"
                If HexToBin = "4" Then BinTemp = "0100"
                If HexToBin = "5" Then BinTemp = "0101"
                If HexToBin = "6" Then BinTemp = "0110"
                If HexToBin = "7" Then BinTemp = "0111"
                If HexToBin = "8" Then BinTemp = "1000"
                If HexToBin = "9" Then BinTemp = "1001"
                If HexToBin = "A" Then BinTemp = "1010"
                If HexToBin = "B" Then BinTemp = "1011"
                If HexToBin = "C" Then BinTemp = "1100"
                If HexToBin = "D" Then BinTemp = "1101"
                If HexToBin = "E" Then BinTemp = "1110"
                If HexToBin = "F" Then BinTemp = "1111"
            Binary = Binary + BinTemp
        Next BinConvCnt

なお、このコード直下にある

    '8桁の16進数=整数値として認識させる。
        If Len(InputData) > 9 Then

という部分ですが。これはこの製品のパラメータが整数値はint型(4バイト)で実数値がdouble(8バイト)で記録されている事を利用したもので。整数値も8バイトで記録されているものでは別の方法で整数と実数を判別する必要があります。

次に2進数に変換した文字列から符号・指数部・仮数部に分割します。

            '符号(プラス=0 マイナス=1)
            '指数(11bit値) 仮数部(52bit値)に分割
                NegaPosiFlag = Mid(Binary, 1, 1)
                ExpData = Mid(Binary, 2, 11)
                FracData = Mid(Binary, 13)

単純に文字列を抜き出しているだけです。

次に2進数の指数部を10進数に変換します。

            '指数(2進数)を10進数に変換する
                For ExpDataDecConvCnt = 0 To 10
                    ExpDataDecConvBit = Mid(ExpData, 1 + ExpDataDecConvCnt, 1)
                    ExpDataDecConvTemp = ExpDataDecConvBit * (2 ^ (10 - (ExpDataDecConvCnt)))
                    ExpDataDecConv = ExpDataDecConv + ExpDataDecConvTemp
                Next ExpDataDecConvCnt

11bit目をから0bit目までを2進数→10進数に直します。
解析ツールとか使えばいい気がしますが気にしないでください。

binmath

2進数データなので値×2桁数-1で計算できます。

Bit×210からBit×20までを計算し、すべてを足すことにより 2進数を10進数に計算しています。この部分のみを使用すると整数の2進数→10進数の変換が行えます。

次に仮数部を固定小数点に変換します。1.23456の形になるはずです。

            '仮数部を小数点に変換する。
                For FracDataCnvCnt = 0 To (Len(FracData) - 1)
                    FracDataCnvBit = Mid(FracData, 1 + FracDataCnvCnt, 1)
                    FracDataCnvTemp = FracDataCnvBit * (1 / (2 ^ (FracDataCnvCnt + 1)))
                    FracDataCnv = FracDataCnv + FracDataCnvTemp
                Next FracDataCnvCnt
                

上のビットからビット×(1÷2n)を行っていき、データを合計することで仮数部を計算しています。
この計算式を見たらわかると思いますが。これでは正確な値を保持することは出来ません。
これはもともとのdouble型の仕様であって、double型はあまり精度が良くありません。

このマクロ自体は最初に話したように小数点以下3桁までの同一性は確保しているはずです。

次に最終的な計算をします。

'最終的な計算
                RealHexToDec = ((-1) ^ Int(NegaPosiFlag) * (2 ^ (ExpDataDecConv - 1023)) * (1 + FracDataCnv))

ここでのいままで計算したデータを全て結合します。
この計算式は以下のとおりになっています。

  • 解=((-1)不等号×2指数-1023×(1+仮数))

この結果を最終的に希望の場所に出力して終了です。

サンプルプログラム

このプログラムデータの仕様に関しては特に使用許諾等は必要ありません。
商用・非商用問わず使用できます。商用マクロへの組み込みや、製品管理ツールとしての添付など、特に制限を設けておりません。

ただし、このプログラムデータをこのファイル単体そのままの形で転載や販売等をすることを禁止します。
エクセルマクロとして配布する場合は必ずオリジナルのマクロに組み込んだ形での使用をお願いします。

改版履歴

  • 2013/01/19 初出Rev1
  • 2013/01/20 計算式変更Rev2
  • 2013/02/23 エクセル関数版の案内追加。
  • 2016/03/10 サイト移行に伴い加筆修正(プログラム以外ほとんど書き直し)
総閲覧数:1,323 PV

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください