我正在尝试编写一个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的答案)。
答案 0 :(得分:2)
从C99 6.5.7 Bitwise shift operators第4段:(强调我的)
- E1的结果&lt;&lt; E2是E1左移E2位位置;腾出的位用零填充。如果E1具有无符号类型,则结果的值为E1 x 2 E2 ,比结果类型中可表示的最大值减少一个模数。 如果E1具有带符号类型和非负值,并且E1 x 2 E2 在结果类型中可表示,那么这就是结果值;否则,行为未定义。
醇>
您正在尝试使用-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()进行调整,因为这是作业,我把那一点留给你