Home > Back-end >  Preserving sign during a masking operation
Preserving sign during a masking operation

Time:03-23

I have a 16 bit word (short). I would like to take the lower 13 bits and save them in a new short while preserving the sign (i.e if bit 12 is a 1, then I want bits 13-15 in the new word to also be 1, but if bit 12 is a 0, then I want bits 13-15 in the new word to be 0).

For example, if I have

short a = 0xDADD // 0b1101101011011101
short b = 0xFADD // 0b1111101011001110

or

short a = 0xABCD // 0b1010101111001101
short b = 0x0BCD // 0b0000101111001101

I can do it with conditionals, but I'd like to try and avoid that if I can and stick to as few bitwise operations as possible. Another option would be to do a 3-bit shift to the left followed by a 3-bit arithmetic right shift, but it's my understanding that most C compilers interpret the >> operator as a logical shift. Is there a way to force an arithmetic shift?

Thanks in advance

CodePudding user response:

You can do this with (x & 0x1fff ^ 0x1000) - 0x1000. After masking the low 13 bits, this flips bit 12 and subtracts 4096 (212).

To see this works, consider value b (0 or 1) for bit 12 and value c (0 to 4095) for bits 11 to 0. In a 13-bit two’s complement representation, bit 12 represents −4096, so the represented value is −4096•b c. Instead, in our wider integer type, we have 4096•b c. Flipping b gives 4096•(1-b) c, and then subtracting 4096 gives 4096•(−b) c = −4096•b c, which is the desired result.

CodePudding user response:

This boils down to "sign extending from a constant bit width", for which Bit Twiddling Hacks provides an optimized solution:

Essentially, you just declare yourself a struct with a field containing the expected number of bits, and channel your assignments through a field on such a struct. For your case, you'd just adapt it to the 13 bit case:

short a = ...;
short b;
struct {signed short x:13;} s;
b = s.x = a;

The advantage to this is that you don't try any actual bit-twiddling yourself (which might produce optimal code on architecture X, but terrible results on architecture Y), you just let the compiler determine the optimal solution for the target for you.

Example:

int main(void)
{
  short a;
  short b;
  struct {signed short x:13;} s;

  a = 0xDADD;
  b = s.x = a;

  printf("hx\nhx\n\n", a, b);

  a = 0xABCD;
  b = s.x = a;
  printf("hx\nhx\n", a, b);
}

Try it online!

which outputs:

dadd
fadd
    
abcd
0bcd

If you're actually using C over C, a templated function makes this even easier (no manually rewriting it for each case, or using hacky unreliable macro-based code), using a template function:

template <typename T, unsigned B>
inline T signextend(const T x)
{
  struct {T x:B;} s;
  return s.x = x;
}

which simplifies the use case to:

short b = signextend<signed short, 13>(a);

Obviously, if you must use C for whatever reason, this is not an option, but template hacks like this are sometimes worth a switch even if you largely stick to C idioms.

  • Related