0

I get some values from hardware registers where values are stored in 16-bit unsigned integers but these values are actually signed. Knowing the last bit is the sign bit, a colleague has done the following snippet to convert them to 2's complement values :

/* Take 15 bits of the data (last bit is the sign) */
#define DATAMASK 0x7FFF

/* Sign is bit 15 (starting from zero) with the 15 bit data */
#define SIGNMASK 0x8000
#define SIGNBIT  15


int16_t calc2sComplement(uint16_t data)
{
     int16_t temp, sign;
     int16_t signData;

     sign  = (int16_t)((data & SIGNMASK) >> SIGNBIT);

     if (sign) 
     {
          temp = (~data) & DATAMASK;
          signData = (short)(temp * -1);
     }
     else 
     {
          temp = (data & DATAMASK);
          signData = temp;
     }

     return(signData);
}

As far as I know, unsigned integers types and signed integers types only differs by their type and the meaning of the last bit ; so casting such as following should work as well :

int16_t calc2sComplement(uint16_t data)
{
     return(static_cast<int16_t>(data));
}

and when needing to push values to the hardware, the reverse operation is straightforward, unlike the calculation. The advantage of the former solution is it's toolchain-free ; since it can change sooner or later (gcc 4.4.7, and so C++03), I would prefer not having to do it but there won't be any regression when compiled years after. The advantage of the latter is it's more readable, close to standard and avoid unnecessary operations.

What would be the best in my case to be sure to keep the same behaviour if compiled again after a toolchain change (even the standard types are redefined somewhere in the toolchain and I do not really have the hand on it) ? If you would keep the first solution, how would improve it and/or code the reverse conversion (keep in mind that data can be a pointer over a buffer of data) ?

Paradox
  • 738
  • 12
  • 30
  • `int16_t` and `uint16_t` will always be 16 bits. If they weren't then the first solution would not work either. However, the C++ standard doesn't really say that `short` *must* be 16 bits. It just says that it must be at *least* 16 bits. So your second (and shorter) solution is actually more future-proof (since it doesn't have a cast to `short`) – Some programmer dude Mar 30 '17 at 14:10

1 Answers1

2

In the end, let's answer to myself. So, the best way to convert values to or from two's complement, and preventing any unexpected behaviour is to perform two's complement conversion as follows :

int16_t calc2sComplement(uint16_t data)
{
     return(static_cast<int16_t>(data));
}

and to do the reverse operation :

uint16_t inv2sComplement(int16_t data)
{
     return(static_cast<uint16_t>(data));
}

This method is proven to be completely safe (as long as primitives types are not redefined somewhere in the toolchain - which is considered bad practice but was actually my case, hence my question in the 1st place) by relying on the definition of the primitive built-in types.

Paradox
  • 738
  • 12
  • 30
  • I believe the behavior of this is not guaranteed for `data > INT_MAX`. On many machines, this will work, but the result of the static cast will be implementation defined. – Ben Jones Feb 21 '20 at 17:50
  • Your colleague's solution using bitmasks is the only guaranteed way to make sure this has the proper behavior on all machines. – Ben Jones Feb 21 '20 at 17:52
  • Never mind I was wrong. sorry for digging this up after three years. I think the relevant piece of info is that the [fixed-width types](https://en.cppreference.com/w/cpp/types/integer) are guaranteed to use two's complement. Something I was not aware of. – Ben Jones Feb 21 '20 at 18:11