12

I'm trying to convert 4 bytes into a 32 bit unsigned integer.

I thought maybe something like:

UInt32 combined = (UInt32)((map[i] << 32) | (map[i+1] << 24) | (map[i+2] << 16) | (map[i+3] << 8));

But this doesn't seem to be working. What am I missing?

Pocki
  • 121
  • 1
  • 1
  • 3

3 Answers3

13

Your shifts are all off by 8. Shift by 24, 16, 8, and 0.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 1
    Assuming you choose to stick with the math rather than delegate, yes. – Ignacio Vazquez-Abrams Jun 20 '11 at 02:31
  • @Ignacio, the confusing part for me is that I'm shifting a byte more positions than it has. So, why does shifting a byte 24 positions work? It isn't cast to the 32bit int until after all the shifts. – Pocki Jun 20 '11 at 02:32
  • 2
    The byte's value occupies bits 1 through 8. You want to shift it 24 bits so it occupies bits 24 through 32. If you shift if 32 bits, it goes off the end into oblivion. – Joel B Fant Jun 20 '11 at 02:34
  • 1
    The compiler is aware of that. But it's still performing the operation in 32 bits, regardless of the size of the left operand. – Ignacio Vazquez-Abrams Jun 20 '11 at 02:35
  • @Ignacio, can I read about this compiler behavior of using 32 bits for these operations even if the type being operated on is smaller than that? I just want to make sense of it. – Pocki Jun 20 '11 at 02:36
  • @Pocki: Everything is aligned on the zeroeth (rightmost, least significant bit). Each byte takes up eight bits, so to get the most significant bit in the most significant byte to the leftmost (most significant) position in the 32 bit word, you have to shift it by 24 bits. The least-significant byte doesn't have to be shifted at all. Write a simple illustration of this on a piece of paper, so you can visualize it. – Robert Harvey Jun 20 '11 at 02:44
  • @Pocki: I don't have a reference for it myself, but I know that C compilers operate in this manner and I can see how the designers of C# would want that behavior to seem familiar to developers coming over. – Ignacio Vazquez-Abrams Jun 20 '11 at 02:46
  • @Rober Harvey, I understand that portion. What I don't fully understand is how you can even shift a ~byte~ 24 bits. A byte holds 8 bits, so shifting it 24 positions would be too much. It isn't converted to the 32 bit uint until after the shifts. So how is it able to perform the shifts which are larger than bytes? Ignacio claims the shifts are being performed with 32 bits available. How? – Pocki Jun 20 '11 at 02:46
  • @Robert: What he doesn't get is that since `map[n]` is only 8 bits in size, why a shift larger than 8 doesn't clear it completely. – Ignacio Vazquez-Abrams Jun 20 '11 at 02:49
  • @Pocki: The shifting operators automatically work on `int`s at a minimum, so they put a `byte` or a `short` into an `int` and then do the shift. If the value is already a `long`, it doesn't decrease the size. – Joel B Fant Jun 20 '11 at 02:51
  • @Pocki: Joel is correct. All bytes are cast to a 32 bit integer value *before the shifting is performed.* – Robert Harvey Jun 20 '11 at 02:54
  • Thanks everyone for explaining that! That was the disconnect that didn't make sense to me. – Pocki Jun 20 '11 at 02:55
  • I just noticed that shifting a `ushort` also converts to `int` rather than `uint`. `long l = (uint)255 << 24` and `long l = 255 << 24` have very different results. – Joel B Fant Jun 20 '11 at 03:08
9

Use the BitConverter class.

Specifically, this overload.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • 1
    and.. the reason you would use this over manual shifts is because if my C# code were to ever be compiled on an architecture with a different endianess than it would still work? – Pocki Jun 20 '11 at 02:58
  • 1
    @Pocki: No, bit-shifting will always work the same way, no matter which order the actual bytes themselves are stored in memory. `<<` always shifts bits "higher", and `>>` shifts them "lower". – Joel B Fant Jun 20 '11 at 03:10
  • 3
    @Pocki: Yes, *the conversion is endian-specific.* There is a system-specific flag that the class uses to adjust the conversion. The relevant code looks like this: `if (IsLittleEndian) { return (((numRef[0] | (numRef[1] << 8)) | (numRef[2] << 0x10)) | (numRef[3] << 0x18)); } return ((((numRef[0] << 0x18) | (numRef[1] << 0x10)) | (numRef[2] << 8)) | numRef[3]);` – Robert Harvey Jun 20 '11 at 05:38
7

BitConverter.ToInt32()

You can always do something like this:

public static unsafe int ToInt32(byte[] value, int startIndex)
{
    fixed (byte* numRef = &(value[startIndex]))
    {
        if ((startIndex % 4) == 0)
        {
            return *(((int*)numRef));
        }
        if (IsLittleEndian)
        {
            return (((numRef[0] | (numRef[1] << 8)) | (numRef[2] << 0x10)) | (numRef[3] << 0x18));
        }
        return ((((numRef[0] << 0x18) | (numRef[1] << 0x10)) | (numRef[2] << 8)) | numRef[3]);
    }
}

But this would be reinventing the wheel, as this is actually how BitConverter.ToInt32() is implemented.

Alex Aza
  • 76,499
  • 26
  • 155
  • 134