简体   繁体   English

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

[英]Rotate right by n only using bitwise operators in C

I'm trying to implement a rotateRight by n function in C by only using bitwise operators. 我试图通过仅使用按位运算符在C中实现rotateRight by n函数。

So far, I have settled on using this. 到目前为止,我已经决定使用它了。

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

g = y | z

So take for instance the value 11010011 例如,取值11010011

If I were to try and `rotateRight(5): 如果我试着`rotateRight(5):

y becomes 11111110 y变成11111110

z becomes 01100000 z变为01100000

Then g becomes 111111110 然后g变成111111110

However the correct answer should be 10011110 但正确的答案应该是10011110

This almost works, but the problem is that the right-shift copies the sign bit when I need it to perform logical shift, and so some of my answers are the negative of what they should be. 这几乎可以工作,但问题是当我需要它来执行逻辑移位时右移复制符号位,所以我的一些答案是他们应该是什么的负面。 How can I fix this? 我怎样才能解决这个问题?

Note I am unable to use casting or unsigned types 注意我无法使用强制转换无符号类型

You could shift unsigned values: 您可以转移无符号值:

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

Or, you could mask appropriately: 或者,您可以适当地掩盖:

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

Though @jlahd answer is correct I will try and provide a brief explanation of the difference between a logical shift right and an arithmetic shift right (another nice diagram of the difference can be found here ). 虽然@jlahd答案是正确的,但我会尝试简单解释逻辑右移算术右移之间的区别( 这里可以找到另一个不同的差异图)。

Please read the links first and then if you're still confused read below: 请先阅读链接然后如果您仍然感到困惑,请阅读以下内容:

Brief explanation of the two different shifts right 简要解释两种不同的转变吧

Now, if you declare your variable as int x = 8; 现在,如果将变量声明为int x = 8; the C compiler knows that this number is signed and when you use a shift operator like this: C编译器知道这个号码是签名的 ,当你使用这样的shift运算符时:

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

The reason for this is that a shift right represents a division by a power of 2 . 这样做的原因是右移表示除以2的幂

Now, I'm lazy so lets make int 's on my hypothetical machine 8 bits so I can save myself some writing. 现在,我很懒所以让我们做int是我假想机上8位,所以我可以拯救自己写一些东西。 In binary 8 and -8 would look like this: 二进制8和-8看起来像这样:

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

But in computing the binary number 11111000 is 248 in decimal. 但在计算二进制数11111000 ,十进制数为248。 It can only represent -8 if we remember that that variable has a sign... 如果我们记得那个变量有一个符号,它只能代表-8 ...

If we want to keep the nice property of a shift where the shift represents a division by a power of 2 (this is really useful) and we want to now have signed numbers, we need to make two different types of right shifts because 如果我们想保留一个移位的优良属性,其中移位表示除以2的幂(这非常有用)并且我们现在想要签名数字,我们需要进行两种不同类型的右移,因为

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

We can see that the first shift inserted a 0 at the front while the second shift inserted a 1. This is because of the difference between the signed numbers and unsigned numbers, in two's complement representation, when dividing by a power of 2. 我们可以看到第一个移位在前面插入0,而第二个移位插入1.这是因为有符号数和无符号数之间的差异,在二进制补码表示中除以2的幂。

To keep this nicety we have two different right shift operators for signed and unsigned variables. 为了保持这种精确性,我们为有符号和无符号变量设置了两个不同的右移运算符。 In assembly you can explicitly state which you wish to use while in C the compiler decides for you based on the declared type. 在汇编中,您可以显式声明您希望在C中使用哪个编译器根据声明的类型为您决定。

Code generalisation 代码概括

I would write the code a little differently in an attempt to keep myself at least a little platform agnostic. 我会编写一些不同的代码,试图让自己至少保持一点平台不可知。

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

This is a little better but you still have to remember to keep the variables unsigned when using this macro. 这稍微好一点但是你仍然要记住在使用这个宏时保持变量无符号。 I could try casting the macro like this: 我可以尝试像这样投射宏:

#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))))

but now I'm assuming that you're never going to try and rotate an integer larger than size_t ... 但现在我假设你永远不会尝试旋转大于size_t的整数...

In order to get rid of the upper bits of the right shift which may be 1's or 0's depending on the type of shift the compiler chooses one might try the following (which satisfies your no casting requirement): 为了摆脱右移的高位,可能是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))))

But it would not work as expected for the long type since the ~(0u) is of type unsigned int (first type which zero fits in the table) and hence restricts us to rotations that are less than sizeof(unsigned int) * 8 bits. 但它不会像long类型那样工作,因为~(0u)unsigned int类型(第一种类型,零适合表) ,因此将我们限制为小于sizeof(unsigned int) * 8位的旋转。 In which case we could use ~(0ul) but that makes it of unsigned long type and this type may be inefficient on your platform and what do we do if you want to pass in a long long ? 在这种情况下,我们可以使用~(0ul)但这使得它成为unsigned long类型,这种类型在您的平台上可能效率低下,如果您想传入很long long我们该怎么办? We would need it to be of the same type as x and we could achieve it by doing more magical expressions like ~((x)^(x)) , but we would still need to turn it into and unsigned version so lets not go there. 我们需要它与x类型相同,我们可以通过做更多神奇的表达式来实现它,比如~((x)^(x)) ,但是我们仍然需要将它转换为unsigned版本,所以不要去那里。

@MattMcNabb also points out in the comments two more problems: @MattMcNabb还在评论中指出了另外两个问题:

  1. our left shift operation could overflow. 我们的左移操作可能会溢出。 When operating on signed types, even though in practice it is most often the same, we need to cast the x in the left shift operation to an unsigned type, because it is undefined behavior when an arithmetic shift operation overflows (see this answer's reference to the standard ). 当对有signed类型进行操作时,即使在实践中它通常是相同的,我们需要将左移位操作中的x转换为unsigned类型, 因为当算术移位操作溢出时它是未定义的行为 (参见本答案的引用)标准 )。 But if we cast it we will once again need to pick a suitable type for the cast because its size in bytes will act as an upper limit on what we can rotate... 但是如果我们施放它,我们将再次需要为演员选择合适的类型,因为它的大小以字节为单位将作为我们可以旋转的上限...

  2. We are assuming that bytes have 8 bits. 我们假设字节有8位。 Which is not always the case, and we should use CHAR_BIT instead of 8 . 情况并非总是如此,我们应该使用CHAR_BIT而不是8

In which case why bother? 在哪种情况下为什么要打扰? Why not go back to the previous solution and just use the largest integer type, uintmax_t (C99) , instead of size_t . 为什么不回到之前的解决方案,只使用最大的整数类型uintmax_t (C99) ,而不是size_t But this now means that we could be penalized in performance since we might be using integers larger than the processor word and that could involve more than just one assembly instruction per arithmetic operation... Nevertheless here it is: 但现在这意味着我们可能会因为我们可能使用大于处理器字的整数而在性能上受到惩罚,并且每次算术运算可能涉及的不仅仅是一条汇编指令......不过这里它是:

#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))))

So really, there is likely no perfect way to do it (at least none that I can think of). 所以真的,可能没有完美的方法(至少没有我能想到的)。 You can either have it work for all types or have it be fast by only dealing with things equal to or smaller than the processor word (eliminate long long and the likes). 您可以让它适用于所有类型,也可以通过仅处理等于或小于处理器字的事物来快速处理(消除long long等等)。 But this is nice and generic and should adhere to the standard... 但这很好,通用,应该遵守标准......

If you want fast algorithms there is a point where you need to know what machine/s you're writing code for otherwise you can't optimize. 如果你想要快速算法,你需要知道你正在编写代码的机器,否则就无法优化。

So in the end @jlahd's solution will work better, whilst my one might help you make things more generic (at a cost). 所以最终@jlahd的解决方案会更好地工作,而我的解决方案可能会帮助你使事情变得更通用(需要付出代价)。

I've tried your code on x86 Linux with gcc 4.6.3. 我已经在x86 Linux上使用gcc 4.6.3尝试了你的代码。

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

g = y | z

This works correct.If x equals 11010011 then rotateRight(5) will makes y become 00000110 .">>" will not add 1 . 这是正确的。如果x等于11010011rotateRight(5)将使y变为00000110 。“>>”将不会添加1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM