在屏蔽操作期间保留符号
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);
}
输出:
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,这是想要的结果。
我有一个 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);
}
输出:
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,这是想要的结果。