2

I am struggling to unpack my data from a long type to two numbers. Not sure where i am going wrong.

I create a unique number from two numbers by packing them into a long:

    public static long Squeeze(float x, float y)
    {
        return ((long)x << 32) | (long)y;
    }

So the long consists for 4 bytes for x then 4 bytes for y.

Then i am trying to get the numbers back out with:

float x = (float)(hash >> 32);
float y = (float)(hash | int.MaxValue); // this should be 1111 1111 1111 1111 i think

But it doesn't seem to work, x appears to be correct, but y is giving me numbers that it should not.

Also it needs to work for negative numbers too.

Example:

(2.0, 9.0) => Packed: 8589934601 => Unpacked: (2, 1.073742E+10)
(-1.0, -1.0) => Packed: -1 => Unpacked: (-1.0, -2147484000.0)
WDUK
  • 1,412
  • 1
  • 12
  • 29

3 Answers3

4

You need & (bitwise and) instead of | (bitwise or) to extract by mask:

unchecked 
{
    ...
    float y = (float)(int)(hash & (long)uint.MaxValue)
 }
Renat
  • 7,718
  • 2
  • 20
  • 34
4

You should use BitConverter instead of casting:

public static long Squeeze(float x, float y)
{
    var bytes = new byte[8];
    BitConverter.GetBytes(x).CopyTo(bytes, 0);
    BitConverter.GetBytes(y).CopyTo(bytes, 4);
    return BitConverter.ToInt64(bytes, 0);
}

and

public static void Unpack(long value, out float x, out float y)
{
    var bytes = BitConverter.GetBytes(value);
    x = BitConverter.ToSingle(bytes, 0);
    y = BitConverter.ToSingle(bytes, 4);
}

As long as you're not transferring the byte array out of the program, it doesn't matter whether the system uses little endian or big endian byte ordering. If you need to know, you can check BitConverter.IsLittleEndian.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Is raw bitwise operations not doable then ? I'm worried the `ToArray()` is going to give me performance hits, i'm doing this thousands of times a frame so its performance critical and allocating is going cause problems. – WDUK Dec 17 '19 at 23:48
  • @WDUK to be fair, you haven't mentioned performance until just now. I was just addressing _correctness_. – Patrick Roberts Dec 17 '19 at 23:50
  • Sure i appreciate the correctness. Are you saying then that bitwise alone isn't going to achieve what i want to achieve? Since the goal was to do it via bit manipulation using bitwise operations. – WDUK Dec 17 '19 at 23:51
  • @WDUK there are much faster methods than this, but the ones that I know of would require you to compile with the `unsafe` flag. And no, you can't manipulate floats using bitwise operations without losing precision. – Patrick Roberts Dec 17 '19 at 23:52
  • @WDUK does your target environment have [`BitConverter.SingleToInt32Bits()`](https://learn.microsoft.com/en-us/dotnet/api/system.bitconverter.singletoint32bits)? If so, that would likely be faster. – Patrick Roberts Dec 17 '19 at 23:59
  • 1
    @WDUK the answers [here](https://stackoverflow.com/q/27237776/1541563) may also help if you're still having performance issues. Particularly [the accepted answer](https://stackoverflow.com/a/27238358/1541563) as well as [this neat trick](https://stackoverflow.com/a/59273138/1541563). The latter might have some thread safety issues though, if that's a concern. – Patrick Roberts Dec 18 '19 at 00:08
0

Here's an Endian agnostic version of Patrick Roberts' answer.

public static long PackLong(int x, int y)
{
    var bytes = new byte[8];

    if (BitConverter.IsLittleEndian)
    {
        BitConverter.GetBytes(x).CopyTo(bytes, 4);
        BitConverter.GetBytes(y).CopyTo(bytes, 0);
    }
    else
    {
        BitConverter.GetBytes(x).CopyTo(bytes, 0);
        BitConverter.GetBytes(y).CopyTo(bytes, 4);
    }


    return BitConverter.ToInt64(bytes, 0);
}

public static void UnpackLong(long value, out int x, out int y)
{
    var bytes = BitConverter.GetBytes(value);

    if (BitConverter.IsLittleEndian)
    {
        x = BitConverter.ToInt32(bytes, 4);
        y = BitConverter.ToInt32(bytes, 0);
    }
    else
    {
        x = BitConverter.ToInt32(bytes, 0);
        y = BitConverter.ToInt32(bytes, 4);
    }
}

A helper function like this can be quite misleading when Endianess is not accounted for.

Prince Owen
  • 1,225
  • 12
  • 20