Converting Endianness in C#

A while back, I found myself wanting to convert the endianness of an integer or floating-point number in C# (for a binary-to-C#-class conversion library that’s still waiting to be finished). Since then, I’ve had a few people ask the same question (normally because they’re reading or writing some low-level format using a BinaryReader or BinaryWriter), so thought I’d write up what I found.

If you dig around, you’ll find IPAddress.HostToNetworkOrder and IPAddress.NetworkToHostOrder which work for short, int, and long. Looking at the source also reveals that they will do nothing on big endian platforms - which may be significant if you’re running on ARM for example. They also won’t help you if you’re working with unsigned types (although you can quite easily cast to the equivalent signed type, then back again), or floats/doubles (although if you’re doing this, you deserve all the pain you get).

I ended up writing a little utility class which can convert the endianness of a larger range of types in an efficient manner, which requiring unsafe code:=. The trick to convert a float to the byte-equivalent int (and likewise double to long) came originally from chilversc on ##csharp, and was later discussed by the great Raymond Chen on his blog The Old New Thing.

using System.Runtime.InteropServices;

public static class EndianUtilities
{
    public static ushort Swap(ushort val)
    {
        unchecked
        {
            return (ushort)(((val & 0xFF00U) >> 8) | ((val & 0x00FFU) << 8));
        }
    }

    public static short Swap(short val)
    {
        unchecked
        {
            return (short)Swap((ushort)val);
        }
    }

    public static uint Swap(uint val)
    {
        // Swap adjacent 16-bit blocks
        val = (val >> 16) | (val << 16);
        // Swap adjacent 8-bit blocks
        val = ((val & 0xFF00FF00U) >> 8) | ((val & 0x00FF00FFU) << 8);
        return val;
    }

    public static int Swap(int val)
    {
        unchecked
        {
            return (int)Swap((uint)val);
        }
    }

    public static ulong Swap(ulong val)
    {
        // Swap adjacent 32-bit blocks
        val = (val >> 32) | (val << 32);
        // Swap adjacent 16-bit blocks
        val = ((val & 0xFFFF0000FFFF0000U) >> 16) | ((val & 0x0000FFFF0000FFFFU) << 16);
        // Swap adjacent 8-bit blocks
        val = ((val & 0xFF00FF00FF00FF00U) >> 8) | ((val & 0x00FF00FF00FF00FFU) << 8);
        return val;
    }

    public static long Swap(long val)
    {
        unchecked
        {
            return (long)Swap((ulong)val);
        }
    }

    public static float Swap(float val)
    {
        // (Inefficient) alternatives are BitConverter.ToSingle(BitConverter.GetBytes(val).Reverse().ToArray(), 0)
        // and BitConverter.ToSingle(BitConverter.GetBytes(Swap(BitConverter.ToInt32(BitConverter.GetBytes(val), 0))), 0)

        UInt32SingleMap map = new UInt32SingleMap() { Single = val };
        map.UInt32 = Swap(map.UInt32);
        return map.Single;
    }

    public static double Swap(double val)
    {
        // We *could* use BitConverter.Int64BitsToDouble(Swap(BitConverter.DoubleToInt64Bits(val))), but that throws if
        // system endianness isn't LittleEndian

        UInt64DoubleMap map = new UInt64DoubleMap() { Double = val };
        map.UInt64 = Swap(map.UInt64);
        return map.Double;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct UInt32SingleMap
    {
        [FieldOffset(0)] public uint UInt32;
        [FieldOffset(0)] public float Single;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct UInt64DoubleMap
    {
        [FieldOffset(0)] public ulong UInt64;
        [FieldOffset(0)] public double Double;
    }
}