C#按位向左旋转并向右旋转

时间:2009-05-01 16:06:11

标签: c# bitwise-operators

来自C ++的_rotl_rotr的C#等价物(.NET 2.0)是什么?

7 个答案:

答案 0 :(得分:39)

这是你想要做的吗?

Jon Skeet answered this in another site

基本上你想要的是

(左)

(original << bits) | (original >> (32 - bits))

(右)

(original >> bits) | (original << (32 - bits))

另外,正如Mehrdad已经建议的那样,这只适用于uint,这也是Jon给出的例子。

答案 1 :(得分:23)

C#中的位旋转没有内置语言功能,但这些扩展方法应该可以完成这项工作:

public static uint RotateLeft(this uint value, int count)
{
    return (value << count) | (value >> (32 - count))
}

public static uint RotateRight(this uint value, int count)
{
    return (value >> count) | (value << (32 - count))
}

注意:正如Mehrdad所指出的那样,对于有符号整数的右移(>>)是一个特点:它用符号位而不是0来填充MSB,就像对无符号数一样。我现在已经改变了取代uint(无符号32位整数)的方法 - 这也更符合C ++ rotlrotr函数。如果你想要旋转整数,只需在传递之前将它们置之,然后再次转换返回值。

使用示例:

int foo1 = 8.RotateRight(3); // foo1 = 1
int foo2 = int.MinValue.RotateLeft(3); // foo2 = 4

(注意int.MinValue是111111111111111111111111 - 二进制32个1。)

答案 2 :(得分:9)

天真的转变版本不起作用。原因是,右移有符号数字将使用符号位填充左侧位,而不是0

您可以通过以下方式验证此事实:

Console.WriteLine(-1 >> 1);

正确的方法是:

public static int RotateLeft(this int value, int count)
{
    uint val = (uint)value;
    return (int)((val << count) | (val >> (32 - count)));
}

public static int RotateRight(this int value, int count)
{
    uint val = (uint)value;
    return (int)((value >> count) | (value << (32 - count)));
}

答案 3 :(得分:8)

使用最新的 C#7 ,您现在可以创建 by-ref 扩展方法,这样您就可以摆脱不断的繁忙工作将辅助函数的返回值存储回变量。

这很好地简化了旋转函数,并消除了一个常见的bug类,你忘记重新存储函数的返回值,同时可能引入一个新的,完全不同类型的bug - 其中{{ 1}}无意中被修改 in-situ 当你不想要或期望它们时。

ValueTypes

通常我会确保将public static void Rol(ref this ulong ul) => ul = (ul << 1) | (ul >> 63); public static void Rol(ref this ulong ul, int N) => ul = (ul << N) | (ul >> (64 - N)); public static void Ror(ref this ulong ul) => ul = (ul << 63) | (ul >> 1); public static void Ror(ref this ulong ul, int N) => ul = (ul << (64 - N)) | (ul >> N); /// note: ---^ ^---^--- extension method can now use 'ref' for ByRef semantics 放在像这样的小方法上,但经过一些调查(在x64上),我发现它根本没有必要。如果JIT确定该方法符合条件(例如,如果取消选中默认情况下启用的VisualStudio调试器复选框'Suppress JIT Optimization'),则无论如何都会内联方法,这就是这种情况。

  

如果术语不熟悉, JIT ,或者&#34;及时&#34;指的是IL指令的一次性转换为针对在运行时检测到的平台调整的本机代码,该过程按需发生,每个方法作为 .NET 程序运行

为了演示使用 by-ref 扩展方法,我将只关注上面显示的第一种方法&#34;向左旋转&#34 ;并比较传统 按值 扩展方法与较新的 by-ref 方法之间的JIT输出。以下是在Windows 10上。NET 4.7 中 x64 发布的两种测试方法。如上所述,这将与JIT进行比较优化&#39; not-suppress&#39;,因此在您看到的这些测试条件下,函数将完全消失为内联代码。

[MethodImpl(MethodImplOptions.AggressiveInlining)]

这是每个相应呼叫站点的C#代码。由于完全JIT优化的AMD64代码非常小,我也可以在这里包含它。这是最佳情况:

static ulong Rol_ByVal(this ulong ul) => (ul << 1) | (ul >> 63);

static void Rol_ByRef(ref this ulong ul) => ul = (ul << 1) | (ul >> 63);
//                 notice reassignment here ---^  (c̲a̲l̲l̲e̲e̲ doing it instead of caller)

哇。是的,这不是开玩笑。马上我们可以看到, ECMA CIL (.NET)中间语言明显缺少static ulong x = 1; // static so it won't be optimized away in this simple test // ------- ByVal extension method; c̲a̲l̲l̲e̲r̲ must reassign 'x' with the result ------- x = x.Rol_ByVal(); // 00007FF969CC0481 mov rax,qword ptr [7FF969BA4888h] // 00007FF969CC0487 rol rax,1 // 00007FF969CC048A mov qword ptr [7FF969BA4888h],rax // ------- New in C#7, ByRef extension method can directly alter 'x' in-situ ------- x.Rol_ByRef(); // 00007FF969CC0491 rol qword ptr [7FF969BA4888h],1 - 指令系列几乎不是问题;抖动能够通过我们的一堆C#变通方法代码OpCodes.Rot来查看它的基本意图,在两种情况下x64 JIT通过简单地发出本地(ul << 1) | (ul >> 63)指令来实现。令人印象深刻的是,ByRef版本使用单个指令直接在主内存目标地址上执行旋转,甚至无需将其加载到寄存器中。

在ByVal的情况下,您仍然可以看到多余复制的剩余痕迹,这是在调用方法完全优化之前保持调用者原始值不变所必需的(因为它的本质是值类型语义)。对于整数旋转,它只是将目标整数额外提取/存储到64位寄存器rol中。

为了澄清这一点,让我们在调试会话中重新抑制JIT优化。这样做会使我们的辅助扩展方法返回,使用完整的主体和堆栈框架来更好地解释前一段的第一句话。首先,让我们看一下呼叫网站。在这里,我们可以看到传统rax语义的效果,这可以归结为确保没有较低的堆栈框架可以操纵任何父框架的ValueType副本:

<强> 通过值:

ValueType

<强> 通过引用

                     x = x.Rol_ByVal();
// 00007FF969CE049C  mov         rcx,qword ptr [7FF969BC4888h]  
// 00007FF969CE04A3  call        00007FF969CE00A8  
// 00007FF969CE04A8  mov         qword ptr [rbp-8],rax  
// 00007FF969CE04AC  mov         rcx,qword ptr [rbp-8]  
// 00007FF969CE04B0  mov         qword ptr [7FF969BC4888h],rcx  

正如我们可能期望与这两个片段中的每一个相关联的 C#代码,我们看到 by-val 来电者有一个呼叫返回后要做的工作。这是覆盖 x.Rol_ByRef(); // 00007FF969CE04B7 mov rcx,7FF969BC4888h // 00007FF969CE04C1 call 00007FF969CE00B0 // ...all done, nothing to do here; the callee did everything in-place for us 值&#39; x&#39;的父副本的过程。使用ulong寄存器中返回的完全独立的ulong值。

现在让我们看一下被调用目标函数的代码。看到它们需要迫使JIT去抑制&#34;优化。以下是x64 Release JIT为raxRol_ByVal函数发出的本机代码。

为了专注于两者之间微小而重要的区别,我剥夺了一些行政样板。 (我离开了堆栈框架设置并拆除了上下文,并展示了在这个例子中,辅助内容几乎使实际内容指令相形见绌。)你能看到ByRef的间接工作吗?好吧,我指出它有帮助: - /

Rol_ByRef

您可能会注意到这两个调用实际上都是通过引用传递父 static ulong Rol_ByVal(this ulong ul) => (ul << 1) | (ul >> 63); // 00007FF969CD0760 push rbp // 00007FF969CD0761 sub rsp,20h // 00007FF969CD0765 lea rbp,[rsp+20h] // ... // 00007FF969CE0E4C mov rax,qword ptr [rbp+10h] // 00007FF969CE0E50 rol rax,1 // 00007FF969CD0798 lea rsp,[rbp] // 00007FF969CD079C pop rbp // 00007FF969CD079D ret static void Rol_ByRef(ref this ulong ul) => ul = (ul << 1) | (ul >> 63); // 00007FF969CD0760 push rbp // 00007FF969CD0761 sub rsp,20h // 00007FF969CD0765 lea rbp,[rsp+20h] // ... // 00007FF969CE0E8C mov rax,qword ptr [rbp+10h] // 00007FF969CE0E90 rol qword ptr [rax],1 <--- ! // 00007FF969CD0798 lea rsp,[rbp] // 00007FF969CD079C pop rbp // 00007FF969CD079D ret 值的父实例 - 这两个示例在这方面都是相同的。父节点指示其ulong的私有副本驻留在上层堆栈帧中的地址。事实证明,只要我们确定他们从不写那些指针,就没有必要将被调查者与读取那些他们撒谎的实例隔离开来。这是一个懒惰的&#34;或延迟方法,为每个较低(子)堆栈帧分配保留其较高级别调用者的 ValueType 语义的责任。如果孩子永远不会覆盖它,则不需要呼叫者主动复制传递给子帧的任何ul;为了尽可能地避免不必要的复制,只有孩子才能做出最新的决定。

同样有趣的是,我们可能会在第一个&#39; ByVal&#39;中对ValueType的笨拙使用进行解释。我展示的例子。在通过内联完全减少了按值方法后,为什么轮换仍然需要在寄存器中进行?

在这些最新的两个完整方法体版本中,很明显第一个方法返回rax,第二个方法返回ulong。由于返回值是在void中传递的,因此ByVal方法无论如何都必须将其提取到该寄存器中,因此在那里旋转它也是明智之举。因为ByRef方法不需要返回任何值,所以它不需要在任何地方为其调用者添加任何内容,更不用说在rax中。似乎&#34;没有必要打扰rax&#34;释放ByRef代码以定位其父级共享的rax实例&#39;它所在的位置,使用花式ulong间接进入父级的堆栈帧内存,而不是使用注册表。因此,这是对“剩余qword ptr&#34;&#34;残余rax&#34;的推测,但可能是可信的解释。我们之前看到的谜。

答案 4 :(得分:1)

请注意,如果要创建对较短的整数值进行操作的重载,则需要添加一个额外的步骤,如下所示:

public static byte RotateLeft(
    this byte value,
    int count )
{
    // Unlike the RotateLeft( uint, int ) and RotateLeft( ulong, int ) 
    // overloads, we need to mask out the required bits of count 
    // manually, as the shift operaters will promote a byte to uint, 
    // and will not mask out the correct number of count bits.
    count &= 0x07;
    return (byte)((value << count) | (value >> (8 - count)));
}

32位和64位重载不需要屏蔽操作,因为移位运算符本身会为那些大小的左侧操作数进行处理。

答案 5 :(得分:0)

对于使用.NET Core 3.0或.NET 5.0及更高版本的用户,可以使用BitOperations.RotateLeftRotateRight

答案 6 :(得分:-1)

// if you are using string

string str=Convert.ToString(number,2);

str=str.PadLeft(32,'0');




//Rotate right


str = str.PadLeft(33, str[str.Length - 1]);

str= str.Remove(str.Length - 1);

number=Convert.ToInt32(str,2);



//Rotate left


str = str.PadRight(33, str[0]);

str= str.Remove(0,1);

number=Convert.ToInt32(str,2);