如何在C中实现算术右移

时间:2018-12-12 15:20:56

标签: c bit-manipulation bit-shift integer-arithmetic signed-integer

信号处理中的许多无损算法都需要评估<< em> a / 2 2 b ⌋形式的表达式,其中 a b 是有符号整数( 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平台

更新:

经过一番思考,我意识到我对上述问题有一个错误的暗示。如果平台不使用二进制补码,则使用算术移位计算负数的下限函数是没有意义的。目标是以可移植的方式实现表达式<< em> a // 2 b ⌋。

2 个答案:

答案 0 :(得分:4)

#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的前零个零翻转为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

答案 1 :(得分:2)

在运行时的早期,您可以检查假设的合理性

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