Reputation: 112
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
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
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
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