Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年08月17日] [更新 2019年10月19日] Delphi 一般・その他
   
バイトデータと数値型の値とエンディアン
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 0800]







バイトデータと数値型の値とエンディアン


数値型の値とそのバイトデータに関する記事とサンプルです.後半は数値のバイトオーダ (並び順) であるエンディアンについての内容となっています.






01_数値のバイトデータ - Helper の Bytes プロパティ


数値型の変数の値はメモリに書き込まれます.Single 型の数値の場合,TSingleHelper (Single 型のヘルパ) の Bytes プロパティ を使用すると,数値のデータをバイト単位で操作できます.以下は 4 バイトの実数値 123456 (1.23456E5) のバイトデータを表示する例です.
Single 型の Helper (TSingleHelper) は Delphi XE3 以降で利用できます.


 

図1
Single 型の Bytes プロパティを使用して数値のバイトデータを表示

リスト1
//=============================================================================
//  数値のバイトデータ表示
//  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 プロパティにバイトデータを代入した結果

リスト2
//=============================================================================
//  バイトデータを数値に変換
//  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 型の値として取得した毛塚

リスト3
//=============================================================================
//  メモリストリームにバイトデータを書き込む
//  書き込んだデータを 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 プロパティでバイトデータをメモリストリームに書き込んでいますが,,以下のようにバイト配列からも書き込むことができます.


リスト4
//=============================================================================
//  メモリストリームにバイト配列のデータを書き込む
//  書き込んだデータを 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;




03_数値のバイトオーダはリトルエンディアン


例えば 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 進数表示とは逆順バイトオーダとなっている

リスト5
//=============================================================================
//  整数値の 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;




04_数値のバイトオーダを逆順にする処理


もし,他のエンディアンのデータを扱わなければならない場合,このエンディアンの違いを考慮しなければなりません.エンディアンを変更するには,バイトオーダを逆順にします.

16 ビット (2 バイト) の整数値であれば System.Swap 関数で上位バイトと下位バイトを入れ換えることができます.数値のデータをバイト配列やメモリストリームに読み込んで処理するのであれば,本サンプルのような方法でバイトオーダを逆順にすることができます.



図6
バイトオーダを逆順にする
  • 4 バイトのデータの例
図7
バイトオーダを逆順にする
  • 8 バイトのデータの例

リスト6
//-----------------------------------------------------------------------------
//  バイトオーダを逆順にする関数
//  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;


単一の数値の場合も,上で作成した関数を利用してバイトオーダを逆順にできます.



リスト7
//=============================================================================
//  数値のバイトオーダ゜を逆順にする処理
//  数値が 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;




05_システムのエンディアン方式を判定


システムのエンディアンを確認するには,$FF よりも小さい値を整数型に代入して,そのバイトテータから判定することができます.以下はそのコード例です.
Windows がインストールされている現在のコンピュータのほとんどが Intel 系の CPU を使用しています.Intel 系の CPU を使用しているコンピュータではリトルエンディアンになります.



図8
判定結果

リスト8
//=============================================================================
//  システムのエンディアンを判定する例
//=============================================================================
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;