user5493558
user5493558

Reputation: 112

C# signed/unsigned cast issues

I need to convert between signed integers and their internal representation as series of bytes. In C I was using functions like:

unsigned char hibyte(unsigned short i)
{return i>>8;}

unsigned char lobyte(unsigned short i)
{return i & 0xFF;}

unsigned short makeshort(unsigned char hb, unsigned char lb)
{return ((short)hb << 8) | (short)lb;}

The problem is that this code does not work under C# because the rules of signed/unsigned cast are not the same: as I understand a C# cast mean conversion of the value whereas in C casting between signed/unsigned types does not modify the underlying data. Moreover in C#, for signed numbers the >> operator shifts in the sign bit. All this makes it difficult to convert my code to C# e.g.

1) the C# function

public static byte hibyte(short i)
{return (byte) (i>>8);}

throws an overflow exception if i is negative

2) the C# function

public static ushort makeshort(byte hb, byte lb)
{return (short) (((ushort)hb << 8) | (ushort)lb); }

throws an overflow exception if the resulting short is negative. Here the expression "(ushort)hb << 8" works because the shift is done on unsigned number. But then I need to interpret the same data as a signed integer and I don't know how to do it. I understand for C# such C-like cast is cheating because a positive value may become a negative value but this is what I actually need (eg. for processing a byte stream read from a device etc.) For the moment I'm using the C code compiled as an unmanaged dll for all binary manipulations like this but this is not very elegant and I'm sure this can be done somehow (possibly simply) in C#. Any suggestions are welcome!

Upvotes: 4

Views: 4822

Answers (3)

Brian Hull
Brian Hull

Reputation: 11

As others have mentioned, you can use the BitConverter class, though they failed to account for endianness in the actual code (it was just briefly mentioned at the end):

public static (byte Hibyte, byte Lobyte) GetBytes(short i)
{   // This is my recommendation; it gets both bytes in one call, so it
    // may be more efficient.
    var bytes = BitConverter.GetBytes(i);
    if (BitConverter.IsLittleEndian)
        Array.Reverse(bytes)
    return (bytes[0], bytes[1]);
}

public static (byte Hibyte, byte Lobyte) GetBytes(ushort i)
{   // BitConverter works equally well with unsigned types.
    var bytes = BitConverter.GetBytes(i);
    if (BitConverter.IsLittleEndian)
        Array.Reverse(bytes)
    return (bytes[0], bytes[1]);
}

public static byte Hibyte(short i)
{   // If you want to use your original schema, here's the hi byte:
    if (BitConverter.IsLittleEndian)
        return BitConverter.GetBytes()[1];
    return BitConverter.GetBytes()[0];
}

public static byte Hibyte(ushort i)
{   // Again, same thing works for ushort
    if (BitConverter.IsLittleEndian)
        return BitConverter.GetBytes()[1];
    return BitConverter.GetBytes()[0];
}

public static short MakeShort(byte hb, byte lb)
{
    byte[] bytes = new byte[] { hb, lb };
    if (BitConverter.IsLittleEndian)
        Array.Reverse(bytes);
    return BitConverter.ToInt16(bytes);
}

public static ushort MakeUShort(byte hb, byte lb)
{
    byte[] bytes = new byte[] { hb, lb };
    if (BitConverter.IsLittleEndian)
        Array.Reverse(bytes);
    return BitConverter.ToUInt16(bytes);
}

And, while others have mentioned using unchecked, they neglect one issue: as you noted in the original question, when doing a right shift on signed integral types, the sign bit gets repeated, so while this works:

public static byte Hibyte(ushort i)
{ return (byte)(i >> 8); }
// No need for unchecked, because result will always fit in one byte and is never < 0.

public static byte Lobyte(ushort i)
{ return (byte)(i & 0xFF); } // No need for unchecked for the same reason.

public static byte LoByte(short i)
{ return (byte)(i & 0xFF); } 
// Like above, no need for unchecked for the same reasons; also, bitwise & works the same 
// for both signed and unsigned types.

public static ushort MakeUShort(byte hb, byte lb)
{ return (ushort)((hb << 8) | lb); } 
// Again, no need for unchecked; result is always 16 bits and never negative.

public static short MakeShort(byte hb, byte lb)
{ unchecked { return (short)((hb << 8) | lb); } }
// This time, we may need unchecked because result may overflow short.

...the code given for the high byte of a signed short needs an extra cast:

public static byte Hibyte(short i)
{ unchecked { return (byte)((ushort)i >> 8); } }
// Again, the unchecked is needed, this time because i may be negative, 
// which may need to be accounted for when casting to a ushort.

By casting the signed short to an unsigned ushort before shifting, we keep the sign bit from being added to the front 8 times. Alternatively, we could use a bitwise & to ignore the propagated sign bits:

public static byte HiByte (short i)
{ return (byte)((i >> 8) & 0xFF); }
// Since bitwise operations never result in overflow, and by the time we cast at the end, 
// the number is guaranteed to fit in a byte and be >= 0, we no longer need any unchecked blocks.

Any of these would generate the same results.

Upvotes: 1

Anders Forsgren
Anders Forsgren

Reputation: 11111

Several answers have already noted the BitConverter class, as well as using unchecked with bit shifts and casts. I'll just quickly demonstrate the third option: "C-style union structs".

[StructLayout(LayoutKind.Explicit)]
struct Converter
{
   [FieldOffset(0)]
   public ushort UshortValue;
   [FieldOffset(0)]
   public short ShortValue;
   [FieldOffset(0)]
   public byte LoByte;    
   [FieldOffset(1)]
   public byte HiByte;
}

Then use like so.

ushort test1 = new Converter { ShortValue = -123 }.UshortValue; // 65413
ushort test2 = new Converter { HiByte = 1, LoByte = 100 }.UshortValue; // 356
byte test3 = new Converter { UshortValue = 356 }.LoByte;  // 100

It has the advantage over BitConverter that you don't need to allocate a temporary byte array.

Upvotes: 7

Matthew Watson
Matthew Watson

Reputation: 109732

You could use the BitConverter class to do this instead:

short x = 1;
byte[] bytes = BitConverter.GetBytes(x);
short y = BitConverter.ToInt32(bytes, 0);

This has overloads for the other integral types int and long too.

If you really want to write the code yourself, you can avoid overflow exceptions by specifying unchecked like so:

public static byte hibyte(short i)
{
    unchecked
    {
        return (byte)(i >> 8);
    }
}

public static ushort makeushort(byte hb, byte lb)
{
    unchecked
    {
        return (ushort)((hb << 8) | lb);
    }
}

public static short makeshort(byte hb, byte lb)
{
    unchecked
    {
        return (short)((hb << 8) | lb);
    }
}

I'd just use BitConverter though; it's pretty fast. However, note that it always uses the endianness of the machine on which the code is running.

This is reported via BitConverter.IsLittleEndian.

If the data you're converting has a different endianness you have to do it yourself.

Upvotes: 4

Related Questions