我正在尝试通过仅使用按位运算符在C中实现rotateRight by n函数。
到目前为止,我已经决定使用它了。
y = x >> n
z = x << (32 - n)
g = y | z
例如,取值11010011
如果我尝试`rotateRight(5):
y
变为11111110
z
变为01100000
然后g
变为111111110
但正确的答案应该是10011110
这几乎可以工作,但问题是当我需要它来执行逻辑移位时右移复制符号位,所以我的一些答案是他们应该是什么的负面。我该如何解决这个问题?
注意 我无法使用投射或无符号类型
答案 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 right和arithmetic 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还在评论中指出了另外两个问题:
我们的左移操作可能会溢出。在signed
类型上操作时,即使实际上它通常是相同的,我们也需要将左移操作中的x转换为unsigned
类型because it is undefined behavior when an arithmetic shift operation overflows(请参阅{{ 3}})。但是如果我们施放它,我们将再次需要为演员选择合适的类型,因为它的大小以字节为单位将作为我们可以旋转的上限......
我们假设字节有8位。 this answer's reference to the standard代替8
。
在哪种情况下为什么要这么麻烦?为什么不回到之前的解决方案而只使用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
。