2

I have a simple piece of code to convert an Int to two shorts:

public static short[] IntToTwoShorts(int a)
{
    byte[] bytes = BitConverter.GetBytes(a);
    return new short[] { BitConverter.ToInt16(bytes, 0), BitConverter.ToInt16(bytes, 2) };
}

If I pass in 1851628330 (‭0x6E5D 9B2A‬) the result is:

{short[2]}
    [0]: -25814
    [1]: 28253

The problem is that -25814 is 0xFFFF 9B2A

I've tried various flavours including bit shifting. What's going on? That result isn't a short, and doesn't have 16 bits!

StuartLC
  • 104,537
  • 17
  • 209
  • 285
AntGamle
  • 21
  • 1
  • 2
    "The problem is that -25814 is 0xFFFF 9B2A" Not if you're regarding it as a short. (It's not clear why you're going via BitConverter, or what you expect the results to be. You should definitely make the latter clear in the question.) – Jon Skeet Nov 23 '17 at 11:34
  • Another way to look at it is that the least significant two bytes can't really be made into a signed short. '9' = 1001b means that the sign bit is set, so it represents a negative value as a `short`. Can you return the 16 bit numbers as `ushort`? – StuartLC Nov 23 '17 at 11:39
  • I'm converting old code that used CopyMem....It was so easy. I just want the bit pattern in two shorts - I did start with using Bitwise OR but had the same results. – AntGamle Nov 23 '17 at 12:36

3 Answers3

1

The trick is to use ushort when combining back two short into int:

public static short[] IntToTwoShorts(int a) {
  unchecked {
    return new short[] {
       (short) a,
       (short) (a >> 16)
    };
  }
}

public static int FromTwoShorts(short[] value) {
  unchecked {
    if (null == value)
      throw new ArgumentNullException("value");
    else if (value.Length == 1)
      return (ushort)value[0]; // we don't want binary complement here 
    else if (value.Length != 2)
      throw new ArgumentOutOfRangeException("value"); 

    return (int)((value[1] << 16) | (ushort)value[0]); // ... and here
  }
}

The cause of the unexpected behaviour is that negative numbers (like -25814) are represented as binary complements and so you have the same value (-25814) represented differently in different integer types:

-25814 ==             0x9b2a // short, Int16
-25814 ==         0xffff9b2a //   int, Int32
-25814 == 0xffffffffffff9b2a //  long, Int64 

Some tests

int a = 1851628330;
short[] parts = IntToTwoShorts(a);

Console.WriteLine($"[{string.Join(", ", parts)}]");
Console.WriteLine($"{FromTwoShorts(parts)}");
Console.WriteLine($"{FromTwoShorts(new short[] { -25814 })}");
Console.WriteLine($"0x{FromTwoShorts(new short[] { -25814 }):X}");

Outcome:

[-25814, 28253]
1851628330
39722
0x9B2A
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
0

I would approach the problem with something like this:

public static short[] IntToTwoShorts(int a)
{
    short retVar[2];
    //Upper 16 byte masked with 0x0000FFFF
    retVar[0] = (short) (a >> 16) & (65535);
    //Lower 16 byte masked with 0x0000FFFF
    retVar[1] = (short) (a >> 0) & (65535);

    return retVar;
}
Detonar
  • 1,409
  • 6
  • 18
0

The problem isn't with the code (although there may be more efficient ways to do split integers). By attempting to represent an int as two signed 16 bit shorts, you now need to consider that the sign bit could be present in both shorts. Hence the comment that ushort[] would be a more appropriate choice of representation of the two 16 bit values.

The problem seems to be with the understanding of why a 4 byte signed integer (DWORD) can't be effectively represented in two 2 byte signed shorts (WORD)s.

The problem is that -25814 is 0xFFFF 9B2A

This isn't true - you've represented -25814 as a short, so it can't possibly be 0xFFFF 9B2A - that's a 32 bit representation. Its 16 bit representation is just 9B2A.

If you open up the Calculator on Windows, and set the mode to programmer, and tinker with the HEX and DEC bases, and then flipping between DWORD and WORD representations of the values, you should see that the 16 bit values you've extracted from the 32 bit int are correctly represented (provided that you understand the representation):

Your original 32 bit Integer (DWORD) is 1851628330:

Original DWord

The high word 28253 doesn't have the sign bit set, so you seem satisfied with the conversion to 6E5D:

enter image description here

However, if the low word is interpreted as a signed short, then you'll find that it's bit sign is set, so hence it reported as a negative value. However, the representation (bits, and hex) does correctly represent the last 16 bits of your original 32 bit int.

enter image description here

StuartLC
  • 104,537
  • 17
  • 209
  • 285