仅使用C中的按位运算符向右旋转n

时间:2014-02-20 00:25:12

标签: c rotation bit-manipulation

我正在尝试通过仅使用按位运算符在C中实现rotateRight by n函数。

到目前为止,我已经决定使用它了。

y = x >> n
z = x << (32 - n)

g = y | z

例如,取值11010011

如果我尝试`rotateRight(5):

y变为11111110

z变为01100000

然后g变为111111110

但正确的答案应该是10011110

这几乎可以工作,但问题是当我需要它来执行逻辑移位时右移复制符号位,所以我的一些答案是他们应该是什么的负面。我该如何解决这个问题?

注意 我无法使用投射无符号类型

3 个答案:

答案 0 :(得分:5)

您可以转移无符号值:

y = (int)((unsigned)x >> n);
z = x << (32 - n);
g = y | z;

或者,你可以适当地掩盖:

y = (x >> n) & ~(-1 << (32 - n));
z = x << (32 - n);
g = y | z;

答案 1 :(得分:2)

虽然@jlahd的回答是正确的,但我会尝试简要说明logical shift rightarithmetic shift right之间的差异(可以找到另一个不同的差异图here

请先阅读链接然后如果您仍然感到困惑,请阅读以下内容:

正确解释两个不同的转变

现在,如果您将变量声明为int x = 8;,则C编译器知道此数字是已签名,并且当您使用此类移位运算符时:

int x = 8;
int y = -8;
int shifted_x, shifted_y;

shifted_x = x >> 2; // After this operation shifted_x == 2
shifted_y = y >> 2; // After this operation shifted_y == -2

原因是右移表示除以2 的幂。

现在,我很懒,所以让我的假设机器上的int为8位,这样我就可以省去一些写作。二进制8和-8看起来像这样:

 8 = 00001000
-8 = 11111000 ( invert and add 1 for complement 2 representation )

但在计算二进制数11111000时,十进制数为248。如果我们记得那个变量有一个符号,它只能代表-8

如果我们想保留一个班次的优良特性,其中班次代表除以2的幂(这确实很有用),我们现在想要签名数字,我们需要做出两种不同类型的右移因为

 248 >> 1 = 124 = 01111100
 -8  >> 1 = -4  = 11111100
// And for comparison
  8  >> 1 =  4  = 00000100

我们可以看到第一个移位在前面插入0,而第二个移位插入1.这是因为有符号数和无符号数之间的差异,在二进制补码表示中,除以2的幂

为了保持这种精确性,我们为有符号和无符号变量设置了两个不同的右移运算符。在汇编中,您可以明确说明您希望在C中使用哪个编译器根据声明的类型为您决定。

代码概括

我会以不同的方式编写代码,试图让自己至少与平台无关。

#define ROTR(x,n) (((x) >> (n)) | ((x) << ((sizeof(x) * 8) - (n))))
#define ROTR(x,n) (((x) >> (n)) | ((x) << ((sizeof(x) * 8) - (n))))

这稍微好一点但是你仍然要记得在使用这个宏时保持变量无符号。我可以尝试像这样投射宏:

#define ROTR(x,n) (((size_t)(x) >> (n)) | ((size_t)(x) << ((sizeof(x) * 8) - (n))))
#define ROTR(x,n) (((size_t)(x) >> (n)) | ((size_t)(x) << ((sizeof(x) * 8) - (n))))

但现在我假设您永远不会尝试旋转大于size_t的整数......

为了摆脱右移的高位,可能是1或0,取决于编译器选择的移位类型,可以尝试以下(满足您的无投射要求):

#define ROTR(x,n) ((((x) >> (n)) & (~(0u) >> (n))) | ((x) << ((sizeof(x) * 8) - (n))))
#define ROTR(x,n) ((((x) >> (n)) & (~(0u) >> (n))) | ((x) << ((sizeof(x) * 8) - (n))))

但是自~(0u) is of type unsigned int (first type which zero fits in the table)以来long类型无法正常工作,因此将我们限制为小于sizeof(unsigned int) * 8位的旋转。在这种情况下,我们可以使用~(0ul),但这会使其成为unsigned long类型,并且此类型在您的平台上可能效率低下,如果您想传入long long,我们该怎么办?我们需要它与x具有相同的类型,我们可以通过执行更加神奇的表达式来实现它,例如~((x)^(x)),但我们仍然需要将其转换为unsigned版本,以便不要去那里。

@MattMcNabb还在评论中指出了另外两个问题:

  1. 我们的左移操作可能会溢出。在signed类型上操作时,即使实际上它通常是相同的,我们也需要将左移操作中的x转换为unsigned类型because it is undefined behavior when an arithmetic shift operation overflows(请参阅{{ 3}})。但是如果我们施放它,我们将再次需要为演员选择合适的类型,因为它的大小以字节为单位将作为我们可以旋转的上限......

  2. 我们假设字节有8位。 this answer's reference to the standard代替8

  3. 在哪种情况下为什么要这么麻烦?为什么不回到之前的解决方案而只使用Which is not always the case, and we should use CHAR_BIT而不是the largest integer type, uintmax_t (C99)。但这现在意味着我们可能会因性能受到惩罚,因为我们可能使用大于处理器字的整数,并且每次算术操作可能只涉及一条汇编指令......不过这里是:

    #define ROTR(x,n) (((uintmax_t)(x) >> (n)) | ((uintmax_t)(x) << ((sizeof(x) * CHAR_BIT) - (n))))
    #define ROTR(x,n) (((uintmax_t)(x) >> (n)) | ((uintmax_t)(x) << ((sizeof(x) * CHAR_BIT) - (n))))
    

    实际上,可能没有完美的方法(至少没有我能想到的)。您可以让它适用于所有类型,也可以只处理等于或小于处理器字的内容(消除long long等)。但这很好,通用,应该遵守标准......

    如果你想要快速算法,你需要知道你在编写代码的机器,否则就无法优化。

    所以最终@jlahd的解决方案会更好,而我的解决方案可以帮助你让事情变得更通用(付费)。

答案 2 :(得分:0)

我已经在x86 Linux上使用gcc 4.6.3尝试了你的代码。

y = x >> n
z = x << (32 - n)

g = y | z

这是正确的。如果x等于11010011,则rotateRight(5)会使y变为00000110。“&gt;&gt;”不会添加1