如何在C语言中实现算术右移



信号处理中的许多无损算法需要对形式为⌊&nbsp的表达式进行评估a/ 2b⌋,其中ab是有符号整数(a可能为负,b非负(,⌊·⌋是底函数。这通常会导致以下实现。

int floor_div_pow2(int numerator, int log2_denominator)
{
return numerator >> log2_denominator;
}

不幸的是,C标准规定,如果左操作数具有带符号类型和负值,则>>运算符的结果是实现定义的。

为了确保在所有平台上的正确行为,可以用多个if-else条件替换这个简单的函数,从而导致程序性能较差。(必须处理整数溢出,并考虑numeratorINT_MIN的情况。(

因此,我想问,在C中实现算术右移的最佳实践是什么?理想情况下,我正在寻找编译成与上面的代码片段相同的代码1的构造,同时避免实现定义的行为。

1考虑,例如gcc和x86-64平台

更新:

经过一番思考,我意识到我在上面的问题中做了一个错误的暗示。如果平台不使用2的补码,那么使用算术移位计算负数的底函数是没有意义的。目标是实现表达式⌊a/ 2b⌋以可移植的方式。

#define USES_ARITHMETIC_SHR(TYPE) ((TYPE)(-1) >> 1 == (TYPE)(-1))
int asr(int value, int amount) /* Better codegen on some older compilers */
{
return !USES_ARITHMETIC_SHR(int) && value < 0 ? ~(~value >> amount) : value >> amount ;
}
int asr2(int value, int amount) /* Completely portable */
{
return value < 0 ? ~(~value >> amount) : value >> amount ;
}

此代码决定是否首先使用内置的>>运算符。你可能想信任或不信任预处理器,它会给你与目标体系结构相同的结果,但安全的后备方法是不信任它

让我们来解释value < 0 ? ~(~value >> amount) : value >> amount部分。

  1. 如果value >= 0,那么无论>>是逻辑的还是算术的,我们都可以使用它
  2. 如果value < 0,则~value是按位补码,它将是正数,并且(~value >> amount)将是可移植的(顶部的amount位数将被清除,其余的如预期向右移位(
    ~(~value >> amount)将翻转所有位,包括将顶部amount的0翻转为1,这正是算术右移所需的

假设USES_ARITHMETIC_SHR(int) == true-O2一起编译为:的代码

asr(int, int): // x86-64 GCC 4.4.7
mov     eax, edi
mov     ecx, esi
sar     eax, cl
ret
asr(int, int): // x86-64 Clang 3.4.1
mov     cl, sil
sar     edi, cl
mov     eax, edi
ret
asr(int, int): // ARM GCC 4.5.4
mov     r0, r0, asr r1
bx      lr

这个应该是可移植的,但我也不确定它是否真的是可移植。如果你不是,你可以#define USES_ARITHMETIC_SHR(TYPE) false,也可以省略检查,只检查value < 0。但这会导致一些较旧的编译器上的代码不太优化。

最新版本的编译器(GCC 8+、Clang 7+(将asrasr2这两个版本编译为与上述相同的高效程序集,因此您可以使用任何一个版本的代码。以下是较旧的编译器如何处理asr2,这是一个非常便携的解决方案。

asr2(int, int): // x86-64 GCC 4.4.7
test    edi, edi
js      .L8
mov     eax, edi
mov     ecx, esi
sar     eax, cl
ret
.L8:
mov     eax, edi
mov     ecx, esi
not     eax
sar     eax, cl
not     eax
ret
asr2(int, int): // x86-64 Clang 3.4.1
mov     cl, sil
sar     edi, cl
mov     eax, edi
ret
asr2(int, int): // ARM GCC 4.5.4
cmp     r0, #0
mvnlt   r0, r0
mvnlt   r0, r0, asr r1
movge   r0, r0, asr r1
bx      lr

在运行时早期的某个时候,您可以检查假设的健全性

int check_sanity()
{
if (~0ll != ~0ll>>8)
{
return 0; // not sane
}
return 1; // sane
}

最新更新