Delphi Programming / Object Pascal
[掲載 2018年08月17日] [更新 2019年10月19日] Delphi 一般・その他
バイトデータと数値型の値とエンディアン
動作確認等
Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
数値型の値とそのバイトデータに関する記事とサンプルです.後半は数値のバイトオーダ (並び順) であるエンディアンについての内容となっています.
01_ 数値のバイトデータ - Helper の Bytes プロパティ
数値型の変数の値はメモリに書き込まれます.Single 型の数値の場合,TSingleHelper (Single 型のヘルパ) の Bytes プロパティ を使用すると,数値のデータをバイト単位で操作できます.以下は 4 バイトの実数値 123456 (1.23456E5) のバイトデータを表示する例です.
Single 型の Helper (TSingleHelper) は Delphi XE3 以降で利用できます.
図1
Single 型の Bytes プロパティを使用して数値のバイトデータを表示
copy code
// =============================================================================
// 数値のバイトデータ表示
// 4バイト(32ビット)浮動小数点型の例
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LSingle : Single;
LIndex : Integer;
begin
Memo1.Lines.Clear;
// 対象の数値
LSingle := 1.23456E5 ;
// バイト値を表示
for LIndex := 0 to LSingle.Size - 1 do begin
Memo1.Lines.Add('$' + IntToHex(LSingle.Bytes[LIndex], 2 ));
end ;
end ;
上のサンプルで使用した数値のハイドデータは $00, $20, $F1, $47 でした.これらの値を Bytes プロパティに順番代入すれば元の数値となります.
図2
Single 型の Bytes プロパティにバイトデータを代入した結果
copy code
// =============================================================================
// バイトデータを数値に変換
// 4バイト(32ビット)浮動小数点型の例
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LSingle : Single;
begin
Memo1.Lines.Clear;
// 数値変数にバイナリデータを代入
LSingle.Bytes[0 ] := $00 ;
LSingle.Bytes[1 ] := $20 ;
LSingle.Bytes[2 ] := $F1 ;
LSingle.Bytes[3 ] := $47 ;
// 結果を表示
Memo1.Lines.Add(FloatToStr(LSingle));
Memo1.Lines.Add(FormatFloat('#.000E+00' , LSingle));
end ;
02_ メモリストリームのバイトデータと数値型変数の値
メモリに書き込まれているバイトデータをメモリストリームに書き込めば,メモリの数値をメモリストリームに書き込むのと同じです.
前項と同じバイトデータをメモリストリームに書き込んで,そのメモリストリームのデータをSingle 型の数値として取得した結果です.前項の値と同じであることが確認できます.
図3
上のサンプルと同じバイトデータをメモリストリームに書き込んで,Single 型の値として取得した毛塚
copy code
// =============================================================================
// メモリストリームにバイトデータを書き込む
// 書き込んだデータを Single 型の値として取得
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LByteStream : TBytesStream;
LSingle : Single;
begin
Memo1.Lines.Clear;
LByteStream := TBytesStream.Create;
try
// ストリームのサイズを設定
LByteStream.Size := 4 ;
// ストリームにバイトデータを書き込む
// これは 1.23456E5 のバイトデータ
LByteStream.Bytes[0 ] := $00 ;
LByteStream.Bytes[1 ] := $20 ;
LByteStream.Bytes[2 ] := $F1 ;
LByteStream.Bytes[3 ] := $47 ;
// ストリームに書き込んだデータを読み出す
// Single 型の値として読み出す
LByteStream.ReadData(LSingle);
// 読み出したデータを表示
Memo1.Lines.Add(FloatToStr(LSingle));
Memo1.Lines.Add(FormatFloat('#.000E+00' , Lsingle));
finally
FreeAndNil(LByteStream);
end ;
end ;
バイト配列のデータをメモリストリームに書き込む例です.
上のコードでは TBytesStream の Bytes プロパティでバイトデータをメモリストリームに書き込んでいますが,,以下のようにバイト配列からも書き込むことができます.
copy code
// =============================================================================
// メモリストリームにバイト配列のデータを書き込む
// 書き込んだデータを Single 型の値として取得
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LStream : TMemoryStream;
LBytes : TBytes;
LSingle : Single;
begin
Memo1.Lines.Clear;
LStream := TMemoryStream.Create;
try
// バイト配列のサイズを設定
SetLength(LBytes, 4 );
// ストリームにバイトデータを書き込む
// これは 1.23456E5 のバイトデータ
LBytes[0 ] := $00 ;
LBytes[1 ] := $20 ;
LBytes[2 ] := $F1 ;
LBytes[3 ] := $47 ;
// メモリストリームにバイト配列のデータを書き込む
LStream.Write (LBytes, SizeOf(LBytes));
// ストリームに書き込んだデータを読み出す
// Single 型の値として読み出す
LStream.Position := 0 ;
LStream.ReadData(LSingle);
// 読み出したデータを表示
Memo1.Lines.Add(FloatToStr(LSingle));
Memo1.Lines.Add(FormatFloat('#.000E+00' , Lsingle));
finally
FreeAndNil(LStream);
end ;
end ;
例えば 123456789 という 32 ビットの整数値を 16 進数で表すと $075BCD15 となります.
これを 32 ビットの整数値としてメモリストリームに格納すると,16 進数で 15 CD 5B 07 の順となります.つまり.メモリにはこの順番で書き込まれていることになります.このバイトオーダ (バイトの並び順) は数値本来のバイト並び $075BCD15 とは逆順です.下のコードはそのことを確認するためのサンプルです.
整数値本来の 16 進数表示と同じバイト順でメモリに書き込むシステムもあります.その形式をビックエンディアンと言います.その逆順のバイトオーダをリトルエンディアンと言います.Intel 系の CPU ではリトルエンディアンとなります.同じシステム上で処理する限りエンディアンを意識する必要は全くありません.
[備考]
浮動小数点型の値の場合は,その内部表現 (IEEE 754) (アイトリプルイー 754) に対して逆のバイトオーダがリトルエンディアンです.Intel 系の CPU ではリトルエンディアンですから,事実上その逆のバイトオーダがビックエンディアンということになります.
Unicode (UTF-16) の文字列の場合は,エレメント単位でバイトオーダが逆順になります.
図4
整数 123456789 のバイトオーダ
上の値は数値を 16 進数表示した値
次はメモリストリームのバイトオーダ
バイトの並び順が逆になっている
図5
整数 123456789 をディスクに保存した結果
数値の 16 進数表示とは逆順バイトオーダとなっている
copy code
// =============================================================================
// 整数値の 16 進表示とメモリストリームに格納した時のバイトデータ
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LValue : UInt32;
LByteStream : TBytesStream;
LHexText : string ;
LLoop : Integer;
begin
Memo1.Lines.Clear;
// 整数値とそのバイトデータを表示
LValue := 123456789 ;
Memo1.Lines.Add('$' + IntToHex(LValue, 8 ));
LByteStream := TBytesStream.Create;
try
// 整数値のデータをメモリストリームに格納
LByteStream.WriteData(LValue);
// ディスクに保存
LByteStream.SaveToFile('000.bin' );
// メモリストリームのバイトデータを取得して表示
LHexText := IntToHex(LByteStream.Bytes[0 ], 2 );
for LLoop := 1 to SizeOf(LValue) -1 do begin
LHexText := LHexText + ' ' + IntToHex(LByteStream.Bytes[LLoop], 2 );
end ;
Memo1.Lines.Add(LHexText);
finally
FreeAndNil(LByteStream);
end ;
end ;
もし,他のエンディアンのデータを扱わなければならない場合,このエンディアンの違いを考慮しなければなりません.エンディアンを変更するには,バイトオーダを逆順にします.
16 ビット (2 バイト) の整数値であれば System.Swap 関数で上位バイトと下位バイトを入れ換えることができます.数値のデータをバイト配列やメモリストリームに読み込んで処理するのであれば,本サンプルのような方法でバイトオーダを逆順にすることができます.
図6
バイトオーダを逆順にする
図7
バイトオーダを逆順にする
copy code
// -----------------------------------------------------------------------------
// バイトオーダを逆順にする関数
// ApByte : バイトデータの先頭アドレス
// ADigit : 逆順にするバイト数 (2, 4, 8 または 10)
// -----------------------------------------------------------------------------
function ByteSwapp(ApByte: Pointer; ADigit: Integer): Boolean;
var
LAddr : NativeInt;
LByte : Byte;
LLast : Integer;
LIndex : Integer;
begin
Result := False;
if ADigit < 2 then Exit;
LAddr := Nativeint(ApByte);
LLast := LAddr + ADigit - 1 ;
for LIndex := 0 to (ADigit div 2 ) - 1 do begin
LByte := PByte(LAddr + LIndex)^;
PByte(LAddr + LIndex)^ := PByte(LLast - LIndex)^;
PByte(Llast - LIndex)^ := LByte;
end ;
Result := True;
end ;
// =============================================================================
// 数値のバイトオーダ゜を逆順にする処理
// 数値が 4 バイトの例
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LStream : TBytesStream;
LDigits : Integer;
LStartPos : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LStream := TBytesStream.Create;
try
// バイトデータを格納するストリームのサイズを設定
LStream.Size := 40 ;
// バイト配列に格納されている数値 1 つ分のバイト数
LDigits := SizeOf(Single);
// テストなのでバイト配列の途中からデータを格納
LStartPos := 8 ;
// テスト用のバイトデータを書き込む
// これは Single 型の 1.23456E5 のバイトデータ (ビッグエンディアン)
// ファイルからビッグエンディアンのデータをストリームに読み込んだと仮定
LStream.Bytes[LStartPos + 0 ] := $47 ;
LStream.Bytes[LStartPos + 1 ] := $F1 ;
LStream.Bytes[LStartPos + 2 ] := $20 ;
LStream.Bytes[LStartPos + 3 ] := $00 ;
// バイトオーダを逆順にする処理
// バイト配列に格納されている数値分,読み出し位置を変えて実行する
if (LStartPos + LDigits) <= LStream.Size then begin
ByteSwapp(@LStream.Bytes[LStartPos], LDigits);
// 結果を表示
for LIndex := LStartPos to LStartPos + LDigits - 1 do begin
Memo1.Lines.Add('%' + IntToHex(LStream.Bytes[LIndex], 2 ));
end ;
end ;
finally
FreeAndNil(LStream);
end ;
end ;
// =============================================================================
// 数値のバイトオーダ゜を逆順にする処理
// 数値が 8 バイトの例
// =============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LStream : TBytesStream;
LDigits : Integer;
LStartPos : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LStream := TBytesStream.Create;
try
// バイトデータを格納するストリームのサイズを設定
LStream.Size := 40 ;
// バイト配列に格納されている数値 1 つ分のバイト数
LDigits := SizeOf(Double);
// テストなのでバイト配列の途中からデータを格納
LStartPos := 24 ;
// テスト用のバイトデータを書き込む
// これは Double 型の 1.23456E5 のバイトデータ (ビッグエンディアン)
// ファイルからビッグエンディアンのデータをストリームに読み込んだと仮定
LStream.Bytes[LStartPos + 0 ] := $40 ;
LStream.Bytes[LStartPos + 1 ] := $FE ;
LStream.Bytes[LStartPos + 2 ] := $24 ;
LStream.Bytes[LStartPos + 3 ] := $00 ;
LStream.Bytes[LStartPos + 4 ] := $00 ;
LStream.Bytes[LStartPos + 5 ] := $00 ;
LStream.Bytes[LStartPos + 6 ] := $00 ;
LStream.Bytes[LStartPos + 7 ] := $00 ;
// バイトオーダを逆順にする処理
// バイト配列に格納されている数値分,読み出し位置を変えて実行する
if (LStartPos + LDigits) <= LStream.Size then begin
ByteSwapp(@LStream.Bytes[LStartPos], LDigits);
// 結果を表示
for LIndex := LStartPos to LStartPos + LDigits - 1 do begin
Memo1.Lines.Add('%' + IntToHex(LStream.Bytes[LIndex], 2 ));
end ;
end ;
finally
FreeAndNil(LStream);
end ;
end ;
単一の数値の場合も,上で作成した関数を利用してバイトオーダを逆順にできます.
copy code
// =============================================================================
// 数値のバイトオーダ゜を逆順にする処理
// 数値が Single 型の場合
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LValue : Single;
LIndex : Integer;
begin
Memo1.Lines.Clear;
// Single 型の 1.23456E5 のビッグエンディアンのバイトデータ
// ビッグエンディアンのバイト配列のデータを数値型の変数に代入したと仮定
LValue.Bytes[0 ] := $47 ;
LValue.Bytes[1 ] := $F1 ;
LValue.Bytes[2 ] := $20 ;
LValue.Bytes[3 ] := $00 ;
// バイトオーダを逆順にする処理
ByteSwapp(@LValue, SizeOf(LValue));
// 結果を表示
Memo1.Lines.Add(LValue.ToString);
Memo1.Lines.Add(FormatFloat('#.000E+00' , LValue));
Memo1.Lines.Add('' );
for LIndex := 0 to SizeOf(LValue) - 1 do begin
Memo1.Lines.Add('%' + IntToHex(LValue.Bytes[LIndex], 2 ));
end ;
end ;
システムのエンディアンを確認するには,$FF よりも小さい値を整数型に代入して,そのバイトテータから判定することができます.以下はそのコード例です.
Windows がインストールされている現在のコンピュータのほとんどが Intel 系の CPU を使用しています.Intel 系の CPU を使用しているコンピュータではリトルエンディアンになります.
図8
判定結果
copy code
// =============================================================================
// システムのエンディアンを判定する例
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LByteStream : TBytesStream;
LUint32 : UInt32;
begin
Memo1.Lines.Clear;
LByteStream := TBytesStream.Create;
try
LUint32 := 1 ;
LByteStream.WriteData(LUint32);
if LByteStream.Bytes[SizeOf(LUint32) - 1 ] = 0 then begin
Memo1.Lines.Add('little endian' );
end else begin
Memo1.Lines.Add('big endian' );
end ;
finally
FreeAndNil(LByteStream);
end ;
end ;