48

I am making application in C# which has a byte array containing hex values.

I am getting data as a big-endian but I want it as a little-endian and I am using Bitconverter.toInt32 method for converting that value to integer.

My problem is that before converting the value, I have to copy that 4 byte data into temporary array from source byte array and then reverse that temporary byte array.

I can't reverse source array because it also contains other data.

Because of that my application becomes slow.

In the code I have one source array of byte as waveData[] which contains a lot of data.

byte[] tempForTimestamp=new byte[4];
tempForTimestamp[0] = waveData[290];
tempForTimestamp[1] = waveData[289];
tempForTimestamp[2] = waveData[288];
tempForTimestamp[3] = waveData[287];
int number = BitConverter.ToInt32(tempForTimestamp, 0);

Is there any other method for that conversion?

SharpC
  • 6,974
  • 4
  • 45
  • 40
Dany
  • 2,034
  • 8
  • 34
  • 54

13 Answers13

44

Add a reference to System.Memory nuget and use BinaryPrimitives.ReverseEndianness().

using System.Buffers.Binary;
number = BinaryPrimitives.ReverseEndianness(number);

It supports both signed and unsigned integers (byte/short/int/long).

Herman
  • 2,738
  • 19
  • 32
36

In modern-day Linq the one-liner and easiest to understand version would be:

int number = BitConverter.ToInt32(waveData.Skip(286).Take(4).Reverse().ToArray(), 0);

You could also...

byte[] tempForTimestamp = new byte[4];
Array.Copy(waveData, 287, tempForTimestamp, 0, 4);
Array.Reverse(tempForTimestamp);
int number = BitConverter.ToInt32(tempForTimestamp);

:)

Zapnologica
  • 22,170
  • 44
  • 158
  • 253
Allison A
  • 5,575
  • 6
  • 28
  • 32
33

If you know the data is big-endian, perhaps just do it manually:

int value = (buffer[i++] << 24) | (buffer[i++] << 16)
          | (buffer[i++] << 8) | buffer[i++];

this will work reliably on any CPU, too. Note i is your current offset into the buffer.

Another approach would be to shuffle the array:

byte tmp = buffer[i+3];
buffer[i+3] = buffer[i];
buffer[i] = tmp;
tmp = buffer[i+2];
buffer[i+2] = buffer[i+1];
buffer[i+1] = tmp;
int value = BitConverter.ToInt32(buffer, i);
i += 4;

I find the first immensely more readable, and there are no branches / complex code, so it should work pretty fast too. The second could also run into problems on some platforms (where the CPU is already running big-endian).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    doesn't the 'int value = x[i] | x[j] | x[k] | ]x[l];' already assume big-endian? I know BitConverter assumes little-endian, so your first example would be needed if he was going to pass the result to BitConverter.ToInt32(tempForTimestamp, 0); but if he just wanted the int value, then wouldn't he just not use the bit shifts??? – Goku Aug 30 '18 at 14:17
  • 2
    @Goku "int = xxxx" says nothing whatsoever about endianness, and `BitConverter` **does not** assume little-endian; it asssumes CPU-endian - just: you happen to be using a little-endian CPU. Big-endian CPUs exist and can run .NET. If you want t check: query `BitConverter.IsLittleEndian` – Marc Gravell Aug 30 '18 at 14:18
  • 1
    @Goku what is `array1`? you can't assign an integer to an array, and you can't access an integer via indexers. The details **really, really matter here**, so we need to be very explicit. Also, it isn't true to say that Windows is strictly little-endian. Remember Itaniums? Now, when we're assigning an integer the entire point is that *how that is implemented internally* is masked from us *unless* we look beneath the covers. Integers on big and little endian CPUs behave identically in most ways, including shift operators. It is *only* when we do re-interpret casts (unsafe thunks, etc) that... – Marc Gravell Aug 30 '18 at 14:28
  • 1
    ...we can see the difference. Which basically means: serializers and interop code. Or things using SIMD. – Marc Gravell Aug 30 '18 at 14:28
  • 1
    I was wrong, I played around some more in a console app and completely forgot that if I shift and OR, the other byte being compared to is padded with 0s ON THE LEFT before doing the comparison (this lead to some bad assumptions from weird results). Sorry for the sloppy comments, I'll delete them so they don't confuse someone else – Goku Aug 30 '18 at 14:42
17

Here you go

public static int SwapEndianness(int value)
{
    var b1 = (value >> 0) & 0xff;
    var b2 = (value >> 8) & 0xff;
    var b3 = (value >> 16) & 0xff;
    var b4 = (value >> 24) & 0xff;

    return b1 << 24 | b2 << 16 | b3 << 8 | b4 << 0;
} 
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
Ioannis Karadimas
  • 7,746
  • 3
  • 35
  • 45
15

The most straightforward way is to use the BinaryPrimitives.ReadInt32BigEndian(ReadOnlySpan) Method introduced in .NET Standard 2.1

var number = BinaryPrimitives.ReadInt32BigEndian(waveData[297..291]);
George Polevoy
  • 7,450
  • 3
  • 36
  • 61
  • 1
    Good answer. Thanks. – Joma Dec 01 '21 at 02:03
  • 1
    Yeah, this is the simplest and most performant. But `AsSpan()` should be used instead like this `waveData.AsSpan()[287..291]`. It is around 2.67x faster than without. Refer to my answer for more details. – Moaaz Assali Aug 16 '23 at 14:38
  • 1
    Actually although `AsSpan()` is still better when using `BinaryPrimitives`, I tried implementing manual byte shifting and it was faster than that too. The speed difference is not too big though (like 10-30% in 100k+ iterations), so it doesn't really matter and `BinaryPrimitives` is more convenient. – Moaaz Assali Aug 16 '23 at 15:08
10

Declare this class:

using static System.Net.IPAddress;

namespace BigEndianExtension
{
    public static class BigEndian
    {
        public static short ToBigEndian(this short value)   => HostToNetworkOrder(value);
        public static int   ToBigEndian(this int value)     => HostToNetworkOrder(value);
        public static long  ToBigEndian(this long value)    => HostToNetworkOrder(value);
        public static short FromBigEndian(this short value) => NetworkToHostOrder(value);
        public static int   FromBigEndian(this int value)   => NetworkToHostOrder(value);
        public static long  FromBigEndian(this long value)  => NetworkToHostOrder(value);
    }
}

Example, create a form with a button and a multiline textbox:

using BigEndianExtension;

private void button1_Click(object sender, EventArgs e)
{
    short int16 = 0x1234;
    int int32   = 0x12345678;
    long int64  = 0x123456789abcdef0;
    string text = string.Format("LE:{0:X4}\r\nBE:{1:X4}\r\n", int16, int16.ToBigEndian());

    text += string.Format("LE:{0:X8}\r\nBE:{1:X8}\r\n", int32, int32.ToBigEndian());
    text += string.Format("LE:{0:X16}\r\nBE:{1:X16}\r\n", int64, int64.ToBigEndian());
    textBox1.Text = text;
}
//Some code...
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
FRL
  • 748
  • 7
  • 9
  • 1
    A nice feature of this answer is that it is platform-independent. – Edward Brey May 31 '18 at 23:28
  • 1
    Unfortunately there is no overload of NetworkToHostOrder for unsigned integers, but you can use BinaryPrimitives.ReverseEndianness() by importing the System.Memory nuget. – Herman Jul 21 '18 at 22:20
  • 1
    `NetworkToHostOrder` is just a wrapper for `HostToNetworkOrder` and `HostToNetworkOrder` is using recursion for conversion. Direct implementation would be much better. – Igor Levicki May 19 '23 at 12:05
2

If you won't ever again need that reversed, temporary array, you could just create it as you pass the parameter, instead of making four assignments. For example:

int i = 287;
int value = BitConverter.ToInt32({
    waveData(i + 3),
    waveData(i + 2),
    waveData(i + 1),
    waveData(i)
}, 0);
M Granja
  • 845
  • 8
  • 26
2

I use the following helper functions

public static Int16 ToInt16(byte[] data, int offset)
{
    if (BitConverter.IsLittleEndian)
        return BitConverter.ToInt16(BitConverter.IsLittleEndian ? data.Skip(offset).Take(2).Reverse().ToArray() : data, 0);
    return BitConverter.ToInt16(data, offset);
}
public static Int32 ToInt32(byte[] data, int offset)
{
    if (BitConverter.IsLittleEndian)
        return BitConverter.ToInt32(BitConverter.IsLittleEndian ? data.Skip(offset).Take(4).Reverse().ToArray() : data, 0);
    return BitConverter.ToInt32(data, offset);
}
public static Int64 ToInt64(byte[] data, int offset)
{
    if (BitConverter.IsLittleEndian)
        return BitConverter.ToInt64(BitConverter.IsLittleEndian ? data.Skip(offset).Take(8).Reverse().ToArray() : data, 0);
    return BitConverter.ToInt64(data, offset);
}
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
Zapnologica
  • 22,170
  • 44
  • 158
  • 253
2

I dislike BitConverter, because (as Marc Gravell answered) it is specced to rely on system endianness, meaning you technically have to do a system endianness check every time you use BitConverter to ensure you don't have to reverse the array. And usually, with saved files, you generally know the endianness you're trying to read, and that might not be the same. You might just be handling file formats with big-endian values, too, like, for instance, PNG chunks.

Because of that, I just wrote my own methods for this, which take a byte array, the read offset and read length as arguments, as well as a boolean to specify the endianness handling, and which uses bit shifting for efficiency:

public static UInt64 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
{
    Int32 lastByte = bytes - 1;
    if (data.Length < startIndex + bytes)
        throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to read a " + bytes + "-byte value at offset " + startIndex + ".");
    UInt64 value = 0;
    for (Int32 index = 0; index < bytes; index++)
    {
        Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
        value |= (((UInt64)data[offs]) << (8 * index));
    }
    return value;
}

This code can handle any value between 1 and 8 bytes, both little-endian and big-endian. The only small usage peculiarity is that you need to both give the amount of bytes to read, and need to specifically cast the result to the type you want.

Example from some code where I used it to read the header of some proprietary image type:

Int16 imageWidth = (Int16) ReadIntFromByteArray(fileData, hdrOffset, 2, true);
Int16 imageHeight = (Int16) ReadIntFromByteArray(fileData, hdrOffset + 2, 2, true);

This will read two consecutive 16-bit integers off an array, as signed little-endian values. You can of course just make a bunch of overload functions for all possibilities, like this:

public Int16 ReadInt16FromByteArrayLe(Byte[] data, Int32 startIndex)
{
    return (Int16) ReadIntFromByteArray(data, startIndex, 2, true);
}

But personally I didn't bother with that.

And, here's the same for writing bytes:

public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt64 value)
{
    Int32 lastByte = bytes - 1;
    if (data.Length < startIndex + bytes)
        throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
    for (Int32 index = 0; index < bytes; index++)
    {
        Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
        data[offs] = (Byte) (value >> (8*index) & 0xFF);
    }
}

The only requirement here is that you have to cast the input arg to 64-bit unsigned integer when passing it to the function.

Nyerguds
  • 5,360
  • 1
  • 31
  • 63
2

You can also use Jon Skeet "Misc Utils" library, available at https://jonskeet.uk/csharp/miscutil/

His library has many utility functions. For Big/Little endian conversions you can check the MiscUtil/Conversion/EndianBitConverter.cs file.

var littleEndianBitConverter = new MiscUtil.Conversion.LittleEndianBitConverter();
littleEndianBitConverter.ToInt64(bytes, offset);

var bigEndianBitConverter = new MiscUtil.Conversion.BigEndianBitConverter();
bigEndianBitConverter.ToInt64(bytes, offset);

His software is from 2009 but I guess it's still relevant.

Endel Dreyer
  • 1,644
  • 18
  • 27
1
public static unsafe int Reverse(int value)
{
    byte* p = (byte*)&value;
    return (*p << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

If unsafe is allowed... Based on Marc Gravell's post

1

This will reverse the data inline if unsafe code is allowed...

fixed (byte* wavepointer = waveData)
   new Span<byte>(wavepointer + offset, 4).Reverse();
1

Similar to what @GeorgePolevoy answered but AsSpan(287) should be used instead:

BinaryPrimitives.ReadInt32BigEndian(waveData.AsSpan(287));

Using AsSpan() is around 2-2.5x faster from my tests than the one without. More details here from Microsoft documentation.

Here is a timing test to check on your machine:

Stopwatch stopwatch = new();

byte[] bytes = { 0x00, 0x00, 0x00, 0x00, 0x53, 0xb3, 0xd8 };

stopwatch.Start();
for (int i = 0; i < 1000000; i++)
    BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(3));
stopwatch.Stop();
Console.WriteLine($"Elapsed Time: {stopwatch.Elapsed}");

stopwatch.Restart();
for (int i = 0; i < 1000000; i++)
    BinaryPrimitives.ReadInt32BigEndian(bytes[3..]);
stopwatch.Stop();
Console.WriteLine($"Elapsed Time: {stopwatch.Elapsed}");
Moaaz Assali
  • 147
  • 1
  • 8