当 MSB 为 1 时算术右移以 0 为单位
arithmetic right shift shifts in 0s when MSB is 1
作为练习,我必须编写以下函数:
将 x 乘以 2,如果溢出则饱和到 Tmin / Tmax,仅使用逐位和位移运算。
现在这是我的代码:
// xor MSB and 2nd MSB. if diferent, we have an overflow and SHOULD get 0xFFFFFFFF. otherwise we get 0.
int overflowmask = ((x & 0x80000000) ^ ((x & 0x40000000)<<1)) >>31;
// ^ this arithmetic bit shift seems to be wrong
// this gets you Tmin if x < 0 or Tmax if x >= 0
int overflowreplace = ((x>>31)^0x7FFFFFFF);
// if overflow, return x*2, otherwise overflowreplace
return ((x<<1) & ~overflowmask)|(overflowreplace & overflowmask);
现在当 overflowmask
应该是 0xFFFFFFFF
时,它是 1,这意味着算术位移 >>31
移入 0 而不是 1(MSB 异或为 1,然后移到底部)。
x 有符号且 MSB 为 1,因此根据 C99,算术右移应填入 1。我错过了什么?
编辑:我只是猜测这段代码不正确。要检测溢出,第二个 MSB 为 1 就足够了。
但是,我还是很纳闷为什么bit shift要补0。
编辑:
示例:x = 0xA0000000
x & 0x80000000 = 0x80000000
x & 0x40000000 = 0
XOR => 0x80000000
>>31 => 0x00000001
编辑:
解决方案:
int msb = x & 0x80000000;
int msb2 = (x & 0x40000000) <<1;
int overflowmask = (msb2 | (msb^msb2)) >>31;
int overflowreplace = (x >>31) ^ 0x7FFFFFFF;
return ((x<<1) & ~overflowmask) | (overflowreplace & overflowmask);
即使在二进制补码机器上,负操作数的右移 (>>
) 行为也是实现定义的。
更安全的方法是使用无符号类型并在 MSB 中显式或运算。
当您使用它时,您可能还想使用固定宽度的类型(例如 uint32_t
),而不是在不符合您期望的平台上失败。
0x80000000
被视为无符号数,这会导致所有内容都转换为无符号数,您可以这样做:
// xor MSB and 2nd MSB. if diferent, we have an overflow and SHOULD get 0xFFFFFFFF. otherwise we get 0.
int overflowmask = ((x & (0x40000000 << 1)) ^ ((x & 0x40000000)<<1)) >>31;
// this gets you Tmin if x < 0 or Tmax if x >= 0
int overflowreplace = ((x>>31)^0x7FFFFFFF);
// if overflow, return x*2, otherwise overflowreplace
return ((x<<1) & ~overflowmask)|(overflowreplace & overflowmask);
或将常量写成负数
或者我会将所有常量存储在 const int 变量中以确保它们已签名。
切勿对有符号类型使用按位操作数。如果对有符号整数进行右移,则由编译器决定是算术移位还是逻辑移位。
但这只是您的问题之一。当您使用十六进制整数常量 0x80000000
时,它实际上属于 unsigned int
. This accidentally turns your whole expression (x & 0x80000000) ^ ...
into unsigned type because of the 类型,称为 "the usual arithmetic conversions"。而 0x40000000
表达式是 signed int 并且按(特定编译器)预期的方式工作。
解决方案:
- 所有涉及的变量必须是
uint32_t
类型。
- 所有涉及的十六进制常量必须有
u
后缀。
- 要获得可移植的算术移位,您必须做
(x >> n) | (0xFFFFFFFFu << (32-n))
或一些 similar hack.
作为练习,我必须编写以下函数:
将 x 乘以 2,如果溢出则饱和到 Tmin / Tmax,仅使用逐位和位移运算。
现在这是我的代码:
// xor MSB and 2nd MSB. if diferent, we have an overflow and SHOULD get 0xFFFFFFFF. otherwise we get 0.
int overflowmask = ((x & 0x80000000) ^ ((x & 0x40000000)<<1)) >>31;
// ^ this arithmetic bit shift seems to be wrong
// this gets you Tmin if x < 0 or Tmax if x >= 0
int overflowreplace = ((x>>31)^0x7FFFFFFF);
// if overflow, return x*2, otherwise overflowreplace
return ((x<<1) & ~overflowmask)|(overflowreplace & overflowmask);
现在当 overflowmask
应该是 0xFFFFFFFF
时,它是 1,这意味着算术位移 >>31
移入 0 而不是 1(MSB 异或为 1,然后移到底部)。
x 有符号且 MSB 为 1,因此根据 C99,算术右移应填入 1。我错过了什么?
编辑:我只是猜测这段代码不正确。要检测溢出,第二个 MSB 为 1 就足够了。
但是,我还是很纳闷为什么bit shift要补0。
编辑:
示例:x = 0xA0000000
x & 0x80000000 = 0x80000000
x & 0x40000000 = 0
XOR => 0x80000000
>>31 => 0x00000001
编辑:
解决方案:
int msb = x & 0x80000000;
int msb2 = (x & 0x40000000) <<1;
int overflowmask = (msb2 | (msb^msb2)) >>31;
int overflowreplace = (x >>31) ^ 0x7FFFFFFF;
return ((x<<1) & ~overflowmask) | (overflowreplace & overflowmask);
即使在二进制补码机器上,负操作数的右移 (>>
) 行为也是实现定义的。
更安全的方法是使用无符号类型并在 MSB 中显式或运算。
当您使用它时,您可能还想使用固定宽度的类型(例如 uint32_t
),而不是在不符合您期望的平台上失败。
0x80000000
被视为无符号数,这会导致所有内容都转换为无符号数,您可以这样做:
// xor MSB and 2nd MSB. if diferent, we have an overflow and SHOULD get 0xFFFFFFFF. otherwise we get 0.
int overflowmask = ((x & (0x40000000 << 1)) ^ ((x & 0x40000000)<<1)) >>31;
// this gets you Tmin if x < 0 or Tmax if x >= 0
int overflowreplace = ((x>>31)^0x7FFFFFFF);
// if overflow, return x*2, otherwise overflowreplace
return ((x<<1) & ~overflowmask)|(overflowreplace & overflowmask);
或将常量写成负数
或者我会将所有常量存储在 const int 变量中以确保它们已签名。
切勿对有符号类型使用按位操作数。如果对有符号整数进行右移,则由编译器决定是算术移位还是逻辑移位。
但这只是您的问题之一。当您使用十六进制整数常量 0x80000000
时,它实际上属于 unsigned int
(x & 0x80000000) ^ ...
into unsigned type because of the 0x40000000
表达式是 signed int 并且按(特定编译器)预期的方式工作。
解决方案:
- 所有涉及的变量必须是
uint32_t
类型。 - 所有涉及的十六进制常量必须有
u
后缀。 - 要获得可移植的算术移位,您必须做
(x >> n) | (0xFFFFFFFFu << (32-n))
或一些 similar hack.