【C#】リトルエンディアンをビッグエンディアンに変換する

タイトルの通り変換をするための操作の紹介をしたいと思います。

ネットワークに TCP/IP でデータを流すときのネットワークバイトオーダーは「ビッグエンディアン」とする事が多いです。いつも使ってる PC上(のC#の内部表現)では「リトルエンディアン」形式が使われています。

このため、多バイト長の変数の内容は PC の内部表現のリトルエンディアンをビッグエンディアンに変換してから送信、受信データはビッグエンディアンからする必要がある場合があります。*1(昨今HTTPでJSONなどではあまり意識しませんが、特にバイナリデータをネットワークに流す場合のマルチバイトデータ送受信時には相互変換が必要です)

では早速例を見ていきます。

// uint(=符号なし32ビット整数)の変換の例
元データ順序 = 0xA1B2C3D4
変換後データ順序 = 0xD4C3B2A1

// double(64 ビット浮動小数点)の変換の例
元データ値
0.002560xF1 68 E3 88 B5 F8 64 3F)
変換後データをdoubleで解釈すると
-2.0258655953055083E+2380x3F 64 F8 B5 88 E3 68 F1)

// ★変数の中で1バイトごとに並び順が入れ替わっています。

多バイト長のデータは内部表現が全て逆順になってればOKです。

大抵の場合 CPU のアーキテクチャがリトルエンディアンなので .NET は基本的にリトルエンディアンしか考慮していません(=BitConverter.IsLittleEndian が false を返すランタイム実装が存在しない的な意味で)

しかし、ネットワークバイトオーダーのためビッグエンディアンを無視できないため、変換クラスを作成してみました。(最も Span を使うのが昨今の .NET の正解なので新しい .NET を使用するのであれば BinaryPrimitives を使ってSpan 経由で操作するのが正解だと思います)

public static class MyBitConverter
{
    // .NETはintは32bitという風にサイズが固定で変化しない

    // 共通化できるものは処理を移譲する
    public static char   Reverse(char value)   => (char)Reverse((ushort)value);
    public static short  Reverse(short value)  => (short)Reverse((ushort)value);
    public static int    Reverse(int value)    => (int)Reverse((uint)value);
    public static long   Reverse(long value)   => (long)Reverse((ulong)value);

    // 伝統的な16ビット入れ替え処理16bit
    public static ushort Reverse(ushort value)
    {
        return (ushort)((value & 0xFF) << 8 | (value >> 8) & 0xFF);
    }

    // 伝統的な32ビット入れ替え処理
    public static uint Reverse(uint value)
    {
        return (value & 0xFF) << 24 |
                ((value >> 8) & 0xFF) << 16 |
                ((value >> 16) & 0xFF) << 8 |
                ((value >> 24) & 0xFF);
    }

    // 伝統的な64ビット入れ替え処理
    public static ulong Reverse(ulong value)
    {
        return (value & 0xFF) << 56 |
                ((value >>  8) & 0xFF) << 48 |
                ((value >> 16) & 0xFF) << 40 |
                ((value >> 24) & 0xFF) << 32 |
                ((value >> 32) & 0xFF) << 24 |
                ((value >> 40) & 0xFF) << 16 |
                ((value >> 48) & 0xFF) << 8 |
                ((value >> 56) & 0xFF);
    }

    // 浮動小数点はちょっと効率悪いけどライブラリでできる操作でカバーする
    public static float Reverse(float value)
    {
        byte[] bytes = BitConverter.GetBytes(value); // これ以上いい処理が思いつかない
        Array.Reverse(bytes);
        return BitConverter.ToSingle(bytes, 0);
    }

    public static double Reverse(double value)
    {
        byte[] bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        return BitConverter.ToDouble(bytes, 0);
    }
}

使用方法は以下の通り。変換したい値を引数に指定して戻り値で受けるだけです。戻り値を受けた変数の内容はバイト順序が逆になっています。(数字はめちゃくちゃになります。

public static void Main(string[] args)
{
    uint src = 0xA1B2C3D4;
    uint dest = EndianUtil.Reverse(src);

    double dsrc = 0.00256;
    double d = EndianUtil.Reverse(dsrc);
}

また、ネットワークから受けた変数も上記のメソッドを通すことによって「ビッグエンディアン」から「リトルエンディアン」のようにバイトを逆順にすることができます。

IIEnumerable.Reverse() で反転しないこと

int reverse = BitConverter.ToInt32(BitConverter.GetBytes(value).Reverse().ToArray(), 0);

ちょっと無理やりですが Linq を絡めてこのように実装してもエンディアンを反転できます。が、処理効率がかなり悪い (≒手元で計測したら45倍くらい遅い & 10倍くらいメモリ使ってました) のでやめておきましょう。低レイヤーで高頻度に呼び出すようなメソッドに Linq を使用するとパフォーマンスがボトルネックになる可能性があります。

*1:エンディアンの概念や詳細はほかサイトを参照ください。