はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】リトルエンディアンとビッグエンディアンの変換を行う

はじめに

今回はエンディアンの変換についてお話したいと思います。

エンディアン(英: endianness)は、複数のバイトなどを並べる順序の種類である。一般的な用語による表現ではバイトオーダ(英: byte order)、ないしそれを一部訳して日本語ではバイト順とも言う。

エンディアン - Wikipedia

概要

エンディアンには色々と種類があるようですが、ビッグエンディアンリトルエンディアンが有名なようです。

例えば、十六進法で表現すると1234ABCDという1ワードが4バイトのデータを、バイト毎に上位側(通常左側)から「12 34 AB CD」のように並べる順序はビッグエンディアン[1]、下位側(通常右側)から「CD AB 34 12」のように並べる順序はリトルエンディアン[2]である。

エンディアン - Wikipedia

これらの違いは以下のサイトが体感的で分かりやすかったです。
16進数変換(リトルエンディアン変換)ツール

ビッグエンディアンとリトルエンディアンの変換

リトルエンディアンかどうか調べる

エンディアンはCPUによって決まっていて、C#ではBitConverter.IsLittleEndianを利用することでリトルエンディアンかどうかを調べることができます。
https://docs.microsoft.com/ja-jp/dotnet/api/system.bitconverter.islittleendian?view=net-6.0

// True
Console.WriteLine(BitConverter.IsLittleEndian);

エンディアンを変更する

基本的にbyte[]を逆順に並べるだけで良いです。

// 10進数 : 1, 35, 69, 103, 137, 171, 205, 239
var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
            
// 10進数 : 239, 205, 171, 137, 103, 69, 35, 1
Console.WriteLine(string.Join(",", bytes.Reverse().ToArray()));
// 10進数 : 1, 35, 69, 103, 137, 171, 205, 239
var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };

Array.Reverse(bytes);
// 10進数 : 239, 205, 171, 137, 103, 69, 35, 1
Console.WriteLine(string.Join(",", bytes));

BinaryReader/Writerでの仕様

C#にはバイトの読み書きを行うBinaryReaderBinaryWriterというクラスが用意されていますが、どちらもリトルエンディアンを利用しています。
BinaryReader クラス (System.IO) | Microsoft Docs
BinaryWriter クラス (System.IO) | Microsoft Docs

またビッグエンディアンに対応しているBinaryReader/Writerは実装されていないようなので、自前で実装する必要があるようです。

ただエンディアンを利用した読み書きとしてBinaryPrimitivesというstaticクラスが存在します。

特定のエンディアンを持つプリミティブとしてバイトを読み取ります。

docs.microsoft.com

伝統的なエンディアン変換

何バイト単位でバイトの並びを逆にするかは、型によって異なるようです。例えばint4byteですね。
sizeof 演算子 - C# リファレンス | Microsoft Docs

まずはintを伝統的なビットシフトによって実装してみます。

int a = 24225748;

// 212, 167, 113, 1
Console.WriteLine(string.Join(",", BitConverter.GetBytes(a)));

// ビットシフトを利用してエンディアン変換
int b = (int)(
    ((a & 0xff000000) >> 24) |
    ((a & 0x00ff0000) >> 8) |
    ((a & 0x0000ff00) << 8) |
    ((a & 0x000000ff) << 24)
    );

// 1, 113, 167, 212
Console.WriteLine(string.Join(",", BitConverter.GetBytes(b)));

初見だとビットシフトが訳わからないかと思いますが、図にしてみると分かりやすいかもしれません。

ビットシフトの動き

ほかの型でも同じようにしてあげればOKです。

また仕組みを理解するためにはビットシフトをしてもよいですが、ぶっちゃけBinaryPrimitivesを利用した方がよいとは思います。