Reversing byte order in .NET
Asked Answered
C

7

16

In the code below, why do X and Y take on different values than what I would think intuitively?

If the bytes 0-7 are written to the buffer, shouldn't the resulting long have bytes in the same order? It's like it's reading the long values in reverse order.

x    0x0706050403020100    long
y    0x0706050403020100    long
z    0x0001020304050607    long

MemoryStream ms = new MemoryStream();
byte[] buffer = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
ms.Write(buffer, 0, buffer.Length);
ms.Flush();
ms.Position = 0;

BinaryReader reader = new BinaryReader(ms);
long x = reader.ReadInt64();
long y = BitConverter.ToInt64(buffer, 0);
long z = BitConverter.ToInt64(buffer.Reverse<byte>().ToArray<byte>(), 0);

byte[] xbytes = BitConverter.GetBytes(x);
byte[] ybytes = BitConverter.GetBytes(y);
byte[] zbytes = BitConverter.GetBytes(z);

(I don't know what to tag this question, beyond just .NET.)


BitConverter.IsLittleEndian

is false. If my computer is big endian, why does this happen?

  • This is a Windows 7 64-bit machine
  • Intel Core2 Quad Q9400 2.66 GHz LGA 775 95W Quad-Core Processor Model BX80580Q9400
  • SUPERMICRO MBD-C2SBX+-O LGA 775 Intel X48 ATX Intel Motherboard

The results of this code (in response to Jason's comment):

byte[] buffer = new byte[] { 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
long y = BitConverter.ToInt64(buffer, 1);
Console.WriteLine(BitConverter.IsLittleEndian);
Console.WriteLine(y);

Result:

False
506097522914230528
Cantrell answered 5/1, 2010 at 0:32 Comment(9)
Added tag for you to search for :)Sulfate
Can you give the specs of the machine (processor, mb, O/S)?Ionization
This is an Intel Core 2, Windows 7. I should add, too, that BitConverter.IsLittleEndian returns false.Cantrell
Can you please do the following: byte[] buffer = new byte[] { 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; long y = BitConverter.ToInt64(buffer, 1); and report back the value of y. Please note that buffer has one extra byte than the definition you gave. I don't have access to a machine where BitConverter.IsLittleEndian is false.Maze
Jason: I responded to your questions in Edit 2 of the ticket.Cantrell
For clarity, would you mind outputting the result of Console.WriteLine(y) where y is defined as in my previous comment?Maze
Wait, are you sure that BitConverter.IsLittleEndian is false? Intel machines are little endian.Maze
@Jason: I have edited the ticket for your most recent comments. IsLittleEndian is printing as False, and the encoded number is printed out for you as well.Cantrell
Ugh. That is not cool. Next step: Go to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP in the Registry and tell me the major version number keys and for each of these the minor version (click on each major version key and there is string value called "Version"). Thanks.Maze
M
25

BinaryReader.ReadInt64 is little endian by design. From the documentation:

BinaryReader reads this data type in little-endian format.

In fact, we can inspect the source for BinaryReader.ReadInt64 using Reflector.

public virtual long ReadInt64() {
    this.FillBuffer(8);
    uint num = (uint) (((this.m_buffer[0] |
              (this.m_buffer[1] << 0x08)) |
              (this.m_buffer[2] << 0x10)) |
              (this.m_buffer[3] << 0x18));
    uint num2 = (uint) (((this.m_buffer[4] |
               (this.m_buffer[5] << 0x08)) |
               (this.m_buffer[6] << 0x10)) |
               (this.m_buffer[7] << 0x18));
    return (long) ((num2 << 0x20) | num);
}

Showing that BinaryReader.ReadInt64 reads as little endian independent of the underlying machine architecture.

Now, BitConverter.ToInt64 is suppose to respect the endianness of your underlying machine. In Reflector we can see

public static unsafe long ToInt64(byte[] value, int startIndex) {
    // argument checking elided
    fixed (byte* numRef = &(value[startIndex])) {
        if ((startIndex % 8) == 0) {
            return *(((long*) numRef));
        }
        if (IsLittleEndian) {
            int num = (numRef[0] << 0x00) |
                      (numRef[1] << 0x08) |
                      (numRef[2] << 0x10) |
                      (numRef[3] << 0x18);
            int num2 = (numRef[4] << 0x00) |
                       (numRef[5] << 0x08) |
                       (numRef[6] << 0x10) |
                       (numRef[7] << 0x18);
            return (((long) ((ulong) num)) | (num2 << 0x20));
        }
        int num3 = (numRef[0] << 0x18) |
                   (numRef[1] << 0x10) |
                   (numRef[2] << 0x08) |
                   (numRef[3] << 0x00);
        int num4 = (numRef[4] << 0x18) |
                   (numRef[5] << 0x10) |
                   (numRef[6] << 0x08) |
                   (numRef[7] << 0x00);
        return (((long) ((ulong) num4)) | (num3 << 0x20));
}

So what we see here is that if startIndex is congruent to zero modulo eight that a direct cast is done from eight bytes starting at address numRef. This case is handled specially because of alignment issues. The line of code

return *(((long *) numRef));

translates directly to

    ldloc.0      ;pushes local 0 on stack, this is numRef
    conv.i       ;pop top of stack, convert to native int, push onto stack
    ldind.i8     ;pop address off stack, indirect load from address as long
    ret          ;return to caller, return value is top of stack

So we see that in this case the key is the ldind.i8 instruction. The CLI is agnostic about the endianness of the underlying machine. It lets the JIT compiler handle that issue. On a little-endian machine, ldind.i8 will load higher addresses into more significant bits and on a big-endian machine ldind.i8 will load higher addresses into less significant bytes. Therefore, in this case, endianness is handled properly.

In the other case, you can see that there is an explicit check of the static property BitConverter.IsLittleEndian. In the case of little endian the buffer is interpreted as little endian (so that memory { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } is interpreted as the long 0x0706050403020100) and in case of big endian the buffer is interpreted as big endian (so that memory { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 } is interpreted as the long 0x0001020304050607). So, for BitConverter it all comes down to the endianness of the underyling machine. I note that you're on an Intel chip on Windows 7 x64. Intel chips are little endian. I note that in Reflector, the static constructor for BitConverter is defined as the following:

static BitConverter() {
    IsLittleEndian = true;
}

This is on my Windows Vista x64 machine. (It could differ on, say, .NET CF on an XBox 360.) There is no reason for Windows 7 x64 to be any different. Consequently, are you sure that BitConverter.IsLittleEndian is false? It should be true and therefore the behavior that you are seeing is correct.

Maze answered 7/1, 2010 at 16:5 Comment(3)
This is the single most useful answer I've ever found on stackoverflow. Thank you for drilling all the way down into behavior of the IL instructions. I was absolutely baffled as to why a certain piece of code was working before explicit endian correction took place.Argueta
So is there a way to force the array to be read reversed (emitting leading zeros, which become trailing zeros after reversal).Linnet
Given the fact bytes usually come from files with defined specs, I never understood why these conversions should have to depend on the endianness of the machine rather than, y'know, a boolean parameter...Gibb
E
6

You're on a little endian machine, where integers are stored least-significant byte first.

Elaterid answered 5/1, 2010 at 0:36 Comment(4)
As a non-.NET person, I am surprised at this question. I would have expected .NET's to insulate the programmer from endianness at least as much as Java does.Oma
The endianness of the machine is irrelevant, the CLI assumes little endian.Nephrolith
It doesn't. Nor does Java. Reversing byte order is way too expensive. Not much of an issue these days, little endian has won.Transalpine
BitConverter.IsLittleEndian returns falseCantrell
N
4

BinaryReader assumes Little Endian order: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.readint64.aspx

Nephrolith answered 5/1, 2010 at 0:48 Comment(0)
N
4

Are you COMPLETELY sure that BitConverter.IsLittleEndian is returning false?

If you inspect it through the debugger-watch before you have used any of it's methods you might get false even if it should return true.

Read out the value through code to be completely certain. See also IsLittleEndian field reports false, but it must be Little-Endian?

Neologism answered 27/5, 2011 at 18:54 Comment(0)
R
3

It's just:

if (BitConverter.IsLittleEndian == true) Array.Reverse(var);
Rombert answered 16/5, 2013 at 15:34 Comment(2)
Any explanations on why this piece should help?Yost
I'm curious about this one as well. This code seems to resolve the issue I'm having but I want to be sure what it actually does.Imogene
H
2

If you care about the endian-ness of your bytes, Jon Skeet wrote a class to allow you to choose the endian-order when you do the conversion.

See C# little endian or big endian?

Hieroglyphic answered 5/1, 2010 at 0:51 Comment(0)
C
1

BitConverter uses the endianness of the machine it's running on. To ensure a big-endian number, use IPAddress.HostToNetworkOrder. For example:

IPAddress.HostToNetworkOrder(BitConverter.ToInt64(buffer))
Crown answered 30/4, 2015 at 9:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.