在屏蔽操作期间保留符号

Preserving sign during a masking operation

我有一个 16 位字(短)。我想采用较低的 13 位并将它们保存在一个新的 short 中,同时保留符号(即如果第 12 位是 1,那么我希望新字中的第 13-15 位也为 1,但如果第 12 位是a 0,那么我希望新单词中的第 13-15 位为 0)。

例如,如果我有

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

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

我可以用条件来做到这一点,但如果可以的话,我想尽量避免这种情况,并尽可能少地使用按位运算。另一种选择是向左移动 3 位,然后进行算术右移 3 位,但据我了解,大多数 C 编译器将 >> 运算符解释为逻辑移位。有没有办法强制进行算术移位?

提前致谢

这归结为“从恒定位宽扩展的符号”,Bit Twiddling Hacks为此提供了优化解决方案:

本质上,您只需将自己声明为一个 struct,其中包含一个包含预期位数的字段,并通过此类结构上的一个字段引导您的分配。对于您的情况,您只需将其调整为 13 位情况即可:

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

这样做的好处是您不需要自己尝试任何实际的 bit-twiddling(这可能会在体系结构 X 上产生最佳代码,但在体系结构 Y 上产生糟糕的结果),您只需让编译器确定最佳代码为您解决目标。

示例:

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

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

  printf("%04hx\n%04hx\n\n", a, b);

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

Try it online!

输出:

dadd
fadd
    
abcd
0bcd

如果您实际上是在 C++ 上使用 C++,模板函数会使这更容易(无需为每种情况手动重写它,或使用 hacky 不可靠的 macro-based 代码),使用模板函数:

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

将用例简化为:

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

显然,如果您出于某种原因必须使用 C,这不是一个选项,但即使您主要坚持使用 C 习惯用法,像这样的模板 hack 有时也值得切换。

您可以使用 (x & 0x1fff ^ 0x1000) - 0x1000 执行此操作。屏蔽低 13 位后,翻转位 12 并减去 4096 (212).

要查看此效果,请考虑位 12 的值 b(0 或 1)和位 12 的值 c(0 到 4095) bits 11 to 0。在13位二进制补码表示中,bit 12表示−4096,所以表示的值为−4096•b + c.相反,在我们更广泛的整数类型中,我们有 4096•b + c。翻转 b 得到 4096•(1-b) + c,然后减去 4096 得到 4096• (−b) + c = −4096•b + c,这是想要的结果。