为什么这个C算术移位实现不起作用?

时间:2016-01-30 08:36:53

标签: c bit-manipulation

我正在尝试编写一个C函数,它总是使用按位运算符找到无符号整数参数的算术右移。我设法编写了一个函数,它总能找到使用按位运算符的有符号整数的逻辑右移,但同样的想法似乎并不适用于找到算术移位。

这是我现在的职能:

unsigned arith(unsigned x, int k) {
    unsigned x_arith = (int) x >> k;

    x_arith = x_arith | ((-1<<(sizeof(x_arith)*8)-k)) & ~(-1<<(sizeof(x_arith)*8));
    return x_arith;
}

说明:

(-1 << (sizeof(x_arith)*8-k))

创建一个全1的二进制文件然后移动这些二进制文件,使第一个1与x_arth中最左边的1对齐。

& ~(-1 << (sizeof(x_arith)*8))

创建一个全1的二进制并移位它,使第一个二进制在x_arth中最左边的位之后对齐,然后翻转它,然后只保留与上面的二进制相同的那些位。

这整个过程可能会创建一个具有1的掩码,x_arith需要有掩码。最后,我|(或)使用此掩码的原始x_arith得到我需要进行移位算术并返回结果的1。

然而,结果仍然是一个逻辑转变。我尝试在上面的代码中改变很多东西,并且对我得到的奇怪数字感到非常沮丧。我做错了什么?

更新: 上面发布的函数确实找到了算术移位。问题是我用4和k = 1测试它,期望结果是6,结果是算术移位是如何工作的。这是一个更好看的版本,供将来参考:

unsigned arith_shift(unsigned x, int k) {

    unsigned x_arith = (int) x >> k;

    int bits = sizeof(int) * 8;

    unsigned mask = k? ((~0 << (bits-k)) & ~(~0 << bits)): 0;

    return x_arith | mask;

}

请注意,您可以同时使用-1~0作为全1的二进制数。但是,此函数可能在不同的计算机上表现不同,因为左移-1的行为未定义(请参阅e0k的答案)。

2 个答案:

答案 0 :(得分:2)

从C99 6.5.7 Bitwise shift operators第4段:(强调我的)

  
      
  1. E1的结果&lt;&lt; E2是E1左移E2位位置;腾出的位用零填充。如果E1具有无符号类型,则结果的值为E1 x 2 E2 ,比结果类型中可表示的最大值减少一个模数。 如果E1具有带符号类型和非负值,并且E1 x 2 E2 在结果类型中可表示,那么这就是结果值;否则,行为未定义。
  2.   

您正在尝试使用-1<<(sizeof(x_arith)*8)为-1的<{1}}:

因此,您的陈述属于可怕的“未定义行为”类别,这意味着所有赌注都已关闭。但一切都没有丢失。

首先,你可以作弊。 CPU的汇编语言通常有一个算术右移指令,所以你可以使用它。例如,使用x86指令E1(右移算术):

sar

请注意,我稍微更改了您的功能签名。从语义上讲,向右移位的位 int arith(int x, unsigned k) { asm volatile ("sar %1,%0" :"+r"(x) :"c"((unsigned char)k)); return x; } 的数量永远不应为负数。 (如果你想向左移,请使用它。)事实上,上面引用的段落(#3)陈述了(指k<<):

  

如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。

所以>>在任何情况下都不应该是负面的,我已经做到了k。我将unsigned更改为已签名的x,以便可以使用负数对其进行测试和检查(详情请参见下文)。返回类型已更改为匹配。

当然这个组装解决方案仅适用于x86机器。普遍的东西可能会更好。这个Shift operator in C的答案对比特移位的数学有一个很好的总结。对于非负 x ,算术右移 k 位与整数除以2 k 的结果相同。不同之处在于 x 为负,整数除法向零舍入,算术右移向舍入为负无穷大。这导致

int

三元运算符决定分区中是否有剩余部分。如果存在,则减1以“向下舍入”。 int arith(int x, unsigned k) { if ( x >= 0 ) { // Same as integer division for nonnegative x return x/(1<<k); } else { // If negative, divide but round to -infinity return x/(1<<k) - ( x % (1<<k) == 0 ? 0 : 1 ); } } 当然是计算2 k 的简单方法。请注意,此版本要求1<<k为签名类型,否则x将始终评估为true。

答案 1 :(得分:0)

你只需要创建一个k最左边1的掩码(如果数字是负数),这是由算术移位完成的符号扩展:

unsigned mask = x > 0x8000 ? (0xFFFF << (16-k)): 0;

然后将掩码应用于逻辑移位结果

unsigned x_arith = mask | (x >> k);

注意:我假设16位无符号,否则你需要使用sizeof()进行调整,因为这是作业,我把那一点留给你