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

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

まず、誤解を恐れずに言うと、ネットワークにデータを流すときは「ビッグエンディアン」形式、いつも使ってるPC上(のC#の内部表現)では「リトルエンディアン」形式が使われています。

更に、ネットワーク上にデータを流すときは「ビッグエンディアン」形式と暗黙的に決まっています。従って、2バイト以上の変数の内容はエンディアンを変換してから送信する必要があります。*1(相手がHTTPでJSONとかではこのような事は意識する必要ないのですが、対向システムがTCP通信の場合、多バイト長のデータ送受信時に必要になります。)

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

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

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

多バイト長のデータ内部表現がが1バイトごとに全て逆順になってればOKです。というわけで、各型で使用できる変換クラスを作成してみました。

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);
    }

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

    // 伝統的な16ビット入れ替え処理64bit
    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);
}

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

IEnumerable.ToArray()は処理効率が悪め

以下のようにするとバイト順序の反転は、見た目が簡単に処理できるのですが、IEnumerable.ToArray()(System.Linq系のメソッド全般に言えることですが)は、処理効率が結構悪いです。メモリは倍使うし、処理速度が2.5倍くらい遅いので、低レイヤーの処理で何万回も呼び出される箇所に以下のような処理を記述すると思わぬボトルネックになったりするため、そういった場合避けた方が賢明です。

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

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