1

I am a beginner at C# and would like some advice on how to solve the following problem:

My code read 3 byte from ADC true FTDI ic and calculating value. Problem is that sometimes I get values under zero (less than 0000 0000 0000 0000 0000 0000) and my calculating value jump to a big number. I need to implement two's complement but I and I don't know how.

byte[] readData1 = new byte[3]; 
ftStatus = myFtdiDevice.Read(readData1, numBytesAvailable, ref numBytesRead); //read data from FTDI
int vrednost1 = (readData1[2] << 16) | (readData1[1] << 8) | (readData1[0]); //convert LSB MSB
int v = ((((4.096 / 16777216) * vrednost1) / 4) * 250); //calculate 
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
mrkefca
  • 55
  • 8
  • Do you have *big endian* or *little endian*? For Instance, what is `readData1` for `300`, please? Is it `{0, 1, 44}` or `{44, 1, 0}`? – Dmitry Bychenko Feb 25 '21 at 16:40

2 Answers2

3

Convert the data left-justified, so that your sign-bit lands in the spot your PC processor treats as a sign bit. Then right-shift back, which will automatically perform sign-extension.

int left_justified = (readData[2] << 24) | (readData[1] << 16) | (readData[0] << 8);
int result = left_justified >> 8;

Equivalently, one can mark the byte containing a sign bit as signed, so the processor will perform sign extension:

int result = (unchecked((sbyte)readData[2]) << 16) | (readData[1] << 8) | readData[0];

The second approach with a cast only works if the sign bit is already aligned left within any one of the bytes. The left-justification approach can work for arbitrary sizes, such as 18-bit two's complement readings. In this situation the cast to sbyte wouldn't do the job.

int left_justified18 = (readData[2] << 30) | (readData[1] << 22) | (readData[0] << 14);
int result18 = left_justified >> 14;
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2

Well, the only obscure moment here is endianness (big or little). Assuming that byte[0] stands for the least significant byte you can put

private static int FromInt24(byte[] data) {
  int result = (data[2] << 16) | (data[1] << 8) | data[0];

  return data[2] < 128
    ? result                       // positive number
    : -((~result & 0xFFFFFF) + 1); // negative number, its abs value is 2-complement
} 

Demo:

  byte[][] tests = new byte[][] {
    new byte[] { 255, 255, 255},
    new byte[] {   44,  1,   0},
  };

  string report = string.Join(Environment.NewLine, tests
    .Select(test => $"[{string.Join(", ", test.Select(x => $"0x{x:X2}"))}] == {FromInt24(test)}"));

  Console.Write(report);

Outcome:

[0xFF, 0xFF, 0xFF] == -1
[0x2C, 0x01, 0x00] == 300

If you have big endianness (e.g. 300 == {0, 1, 44}) you have to swap bytes:

private static int FromInt24(byte[] data) {
  int result = (data[0] << 16) | (data[1] << 8) | data[2];

  return data[0] < 128
    ? result
    : -((~result & 0xFFFFFF) + 1);
} 
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • Easy way to handle signed data is `((byte[2] << 24) | (byte[1] << 16) || (byte[0] << 8)) >> 8`. No need for a manual sign-extension step when the right-shift operator does it for you. – Ben Voigt Feb 25 '21 at 16:55
  • And I think you mean *endianness* not *ending* – Ben Voigt Feb 25 '21 at 16:56
  • @ Ben Voigt: Thank you! You are quite right your computation is better in case this `Int24` has the same endianness as `Int32`. Another reason for using `?` is to demonstrate OP how to compute 2-complement – Dmitry Bychenko Feb 25 '21 at 17:06
  • @mrkefca: Something like `int mrkefca = FromInt24(readData1);` – Dmitry Bychenko Feb 25 '21 at 17:08
  • @DmitryBychenko: Left-justification is possible with either endianness of course, for big endian it would be : `((data[0] << 24) | (data[1] << 16) | (data[2] << 8)) >> 8` – Ben Voigt Feb 25 '21 at 18:13
  • And yet another approach would be `(((sbyte)data[0]) << 16) | (data[1] << 8) | (data[2])`. The key is to make the computer see the correct bit as a sign bit. – Ben Voigt Feb 25 '21 at 18:16