Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何以可移植的方式在C中执行算术右移?_C_Bit Manipulation - Fatal编程技术网

如何以可移植的方式在C中执行算术右移?

如何以可移植的方式在C中执行算术右移?,c,bit-manipulation,C,Bit Manipulation,我们正在编写一个模拟器,需要符号传播右移。 仿真系统使用2的补码 我读到C中有符号整数上的运算符是实现定义的。因此,我不能相信它会在所有平台上产生正确的位模式 这意味着我需要使用位操作来再现算术右移,如果可能的话,我希望避免不必要的分支 编辑: 针对一项评论: 缺少的一点是OP需要定义什么结果是“正确的” 当符号位设置为x且x>>y“ 我基本上想重现SAR x86指令的行为。 负数用2的补码表示。右移基本上也意味着负数被2除 这意味着对于从1开始的位模式。因此,对于1xxxxxx,右移的结果应该

我们正在编写一个模拟器,需要符号传播右移。 仿真系统使用2的补码

我读到C中有符号整数上的
运算符是实现定义的。因此,我不能相信它会在所有平台上产生正确的位模式

这意味着我需要使用位操作来再现算术右移,如果可能的话,我希望避免不必要的分支

编辑:

针对一项评论:

缺少的一点是OP需要定义什么结果是“正确的” 当符号位设置为x且x>>y“

我基本上想重现SAR x86指令的行为。 负数用2的补码表示。右移基本上也意味着负数被2除


这意味着对于从1开始的位模式。因此,对于1xxxxxx,右移的结果应该是11xxxxxx。对于以0开头的位模式,因此0xxxxxxx右移应导致00xxxxxx。所以MSB是“粘性的”。未定义超过字长的移位。

我认为使用
没有什么大问题,但如果要进行算术右移,则可以将数字除以
2
的幂
x
,其中,
x
是您想要进行的右移量,因为将一个数字除以二相当于一次右移


假设您想执行
a>>x
。然后它也可以通过执行
a/(int)pow(2,x)
来实现
pow(2,x)
是数学幂,或者你也可以把它看作是
2
到幂
x
如果你可以有特定于平台的代码,你可以测试现有的
>
操作符(它可能会也可能不会做你想要的有符号整数,但很可能会扩展符号)。对于大多数平台来说,这是迄今为止最简单、最有效的解决方案,因此,如果考虑到可移植性,我将提供另一种解决方案作为后备方案。(不过,我并不完全确定是否有任何好的方法可以通过预处理器对此进行测试,因此测试需要进入构建解决方案。)

如果要手动执行此操作,可以通过有条件地对高位掩码按位ORing来执行,或者在许多情况下:

#define asr(x, shift) ((x) / (1 << (shift)) // do not use as is, see below

#定义asr(x,shift)((x)/(1一种可能的方法是首先执行无符号右移,然后根据最高有效位的值对移位值进行符号扩展。利用以下事实,当添加两位
a
b
时,总和位是
a^b
,进位是
a&b
,我们可以用两种方式构造符号扩展s、 事实证明,使用基于和位的方法更有效

下面的代码将算术右移模拟为函数
算术右移()
以及测试框架;
T
是您希望操作的整数类型

#包括
#包括
#包括
#定义T int
#使用进位(1)定义扩展位
#使用\u和\u位(2)定义扩展\u
#定义符号扩展方法使用求和位扩展
算术右移(ta,int-s)
{
无符号T掩码_msb=(无符号T)1>s;
mask_msb=mask_msb>>s;
#if(符号扩展方法==使用求和位扩展)
返回(T)((ua^mask_msb)-mask_msb);
#else//SIGN\u EXTEND\u方法
申报表(T)(ua-2*(ua&U msb));
#endif//SIGN\u EXTEND\u方法
}
内部sar_ref(内部a,内部s)
{
国际关系;
__asm mov eax,dword ptr[a];
__asm mov ecx,s;
__asm sar eax,cl;
__asm mov dword ptr[res],eax;
返回res;
}
内部主(空)
{
无符号整数x;
INTA、s、res、ref;
s=0;
做{
x=0;
做{
a=(int)x;
res=算术右移(a,s);
ref=sar_ref(a,s);
if(ref!=res){
printf(!!!!a=%08x s=%d res=%08x ref=%08x\n),
a、 s,res,ref);
返回退出失败;
}
x++;
}而(x),;
s++;
}s<32;
返回退出成功;
}

为了便于移植并避免有符号整数右移的实现定义行为,请使用
无符号
执行所有移位

下面是答案的一个变体。它不按位宽度(即UB)移动,也不依赖于2的补码。没有分支。如果在一台罕见的机器上不使用not 2的补码,可能会产生陷阱值

#if INT_MAX == 0x7FFF && UINT_MAX == 0xFFFF
  #define W 16
#elif INT_MAX == 0x7FFFFFFF && UINT_MAX == 0xFFFFFFFF
  #define W 32
#else
  // Following often works
  #define W (sizeof (unsigned)*CHAR_BIT)
#endif

int TwosComplementArithmeticRightShift(int x, int shift) {
  unsigned ux = (unsigned) x;
  unsigned sign_bit = ux >> (W-1);
  y = (ux >> shift) | (((0-sign_bit) << 1) << (W-1-shift));
return y;
}
#如果INT_MAX==0x7FFF&&UINT_MAX==0xFFFF
#定义w16
#elif INT_MAX==0x7FFFFFFF&&UINT_MAX==0xFFFFFFFF
#定义w32
#否则
//下面的方法经常奏效
#定义W(sizeof(无符号)*字符位)
#恩迪夫
int Two完成算术右移(int x,int shift){
无符号ux=(无符号)x;
无符号符号\u位=ux>>(W-1);

y=(ux>>shift)|((0-符号位)shift)|((0-((无符号)x)>>(W-1))算术右移相当于除以要移位的数字的2,如果除数是非负的,则向零舍入,否则向无穷大舍入

因此,GNU C中的通用可移植算术右移宏可以是:

#if /*$auto*{{{*/ \
    __GNUC__>=7
    #define $let(L,R) __auto_type L = R
#else
    #define $let(L,R) __typeof(R) L = R
#endif //}}}
#define $sar(X,By) /*{{{*/ \
    (__extension__({ \
       $let($sar_X,X); \
       $let($sar_divisor, ($sar_X*0+1)<<(By) ); \
       $sar__($sar_X, $sar_divisor);  \
    }))
    #define $sar__(X,Divisor) ((X)/(Divisor)+((X)%(Divisor)>=0?0:-1))
    /*}}}*/
这需要5位操作

解释 如前所述,算术右移
x>>n
对应于除法
x/2**n
。如果系统仅支持逻辑右移,则可以首先将负数转换为正数,然后将其符号复制回
sgn(x)*(abs(x)/2**n)
。这相当于右移前后与+/-1相乘
sgn(x)*((sgn(x)*x)/2**n)

将整数与+/-1相乘可以通过条件无分支求反
s^(s+x)
(x^s)-s
进行模拟。当
s
0
时,不会发生任何事情,并且
x
保持不变
#if /*$auto*{{{*/ \
    __GNUC__>=7
    #define $let(L,R) __auto_type L = R
#else
    #define $let(L,R) __typeof(R) L = R
#endif //}}}
#define $sar(X,By) /*{{{*/ \
    (__extension__({ \
       $let($sar_X,X); \
       $let($sar_divisor, ($sar_X*0+1)<<(By) ); \
       $sar__($sar_X, $sar_divisor);  \
    }))
    #define $sar__(X,Divisor) ((X)/(Divisor)+((X)%(Divisor)>=0?0:-1))
    /*}}}*/
#if (-2>>1==-1 && -2l>>1==-1 && -2ll>>1==-1) 
    //does signedX>>Shift do an arithmetic right shift?

    #define $sar(X,By) ((X)>>(By))
#else
   //...the previous snippet...
#endif
int s = -((unsigned) x >> 31);
int sar = (s^x) >> n ^ s;
int shift(int value, int count)
{
  return ((value > 0) - (value < 0)) * (abs(value) >> count);
}
// shift x right y bits (0..31) with sign replication */
uint32_t sar32(uint32_t x, uint32_t y) {
    uint32_t bottom = x >> y;
    uint32_t top = -((x & (1u << 31)) >> y);
    return top | bottom;
}
// shift x right y bits with sign replication, intel behavior */
uint32_t sar32(uint32_t x, uint32_t y) {
    uint32_t bottom = x >> (y &= 31);
    uint32_t top = -((x & (1u << 31)) >> y);
    return top | bottom;
}