【问题标题】:arithmetic right shift shifts in 0s when MSB is 1MSB 为 1 时,算术右移 0s
【发布时间】:2020-04-26 23:40:23
【问题描述】:

作为练习,我必须编写以下函数:
将 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,这意味着算术位移&gt;&gt;31 移位了0s 而不是1s(MSB 异或到1,然后移位到底部)。 x 有符号且 MSB 为 1,因此根据 C99,算术右移应填充 1。我错过了什么?

编辑:我只是猜想这段代码不正确。要检测溢出,第二个 MSB 为 1 就足够了。
但是,我仍然想知道为什么移位填充为 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);

【问题讨论】:

  • 使用 signed 值的按位运算总是会有问题。
  • 所以我只需要想出一个不同的解决方案,因为 c 不像它所说的那样工作?
  • 您能否告诉我们x 的一些值,它不能按您的预期工作?您是否尝试过将您拥有的复杂表达式划分为更小且更易于调试的表达式,以查看每个小的子表达式给出您期望的值?
  • 发生了什么是x &amp; 0x80000000 中的0x80000000 变成了一个无符号 整数,使得整个表达式无符号。 0x80000000longlong long 值,这意味着它不是负数。因此,您正在移动一个无符号或其他非负数,这意味着它用零而不是一填充。我正在尝试查找有关此行为的参考或引用以获得答案。
  • 它可以是unsigned int 64 位有符号整数(longlong long,取决于平台和编译器)。

标签: c bit-manipulation bit-shift


【解决方案1】:

即使在二进制补码机器上,负操作数的右移 (&gt;&gt;) 行为也是由实现定义的。

更安全的方法是使用无符号类型并在 MSB 中显式 OR-。

当您使用它时,您可能还想使用固定宽度类型(例如uint32_t),而不是在不符合您期望的平台上失败。

【讨论】:

    【解决方案2】:

    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 变量中,以保证它们已签名。

    【讨论】:

    【解决方案3】:

    永远不要对有符号类型使用按位操作数。在有符号整数右移的情况下,是否得到算术或逻辑移位取决于编译器。

    不过,这只是您的问题之一。当您使用十六进制整数常量0x80000000 时,它实际上是unsigned int as explained here 类型。这会意外地将您的整个表达式 (x &amp; 0x80000000) ^ ... 变成无符号类型,因为 integer promotion rule 被称为“通常的算术转换”。而0x40000000 表达式是带符号的 int 并按(特定编译器)预期的方式工作。

    解决方案:

    • 所有涉及的变量必须是uint32_t类型。
    • 所有涉及的十六进制常量都必须以u 为后缀。
    • 要获得可移植的算术移位,您必须执行
      (x &gt;&gt; n) | (0xFFFFFFFFu &lt;&lt; (32-n)) 或一些similar hack

    【讨论】:

    • 十六进制文字是无符号的仅当对于int来说太大了。虽然问题似乎假设 int 是 32 位 2s 补码类型,但我们的回答者应该更加谨慎地做出这样的假设!
    • @TobySpeight 这并不重要,因为即使 int 是 16 位,在相同的规则之后,十六进制文字也会分别以 long/unsigned long 结束。
    • 这很重要,如果int 是 33 位或更多。
    • @TobySpeight 不是,所以您不必担心。如果我的代码在使用 > 32 位整数的高度虚构的系统上中断,我很高兴。解决方案不是修复代码,而是首先不使用该编译器。也可以加_Static_assert(sizeof(int) &lt; 5, "Stop using crap.");
    • 啊,有区别。当我的代码中断时,我会un很高兴。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多