【问题标题】:C# bitwise rotate left and rotate rightC#按位向左旋转和向右旋转
【发布时间】:2009-05-01 16:06:10
【问题描述】:

C++ 中 _rotl_rotr 的 C# 等效项 (.NET 2.0) 是什么?

【问题讨论】:

标签: c# bitwise-operators


【解决方案1】:

这是你想要做的吗?

Jon Skeet answered this in another site

基本上你想要的是

(左)

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

(右)

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

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

【讨论】:

  • 只有在originaluint 时才能正常工作。请注意,Jon Skeet 使用 uint
  • @Mehrdad 幸运的是,我正在寻找使用 'uint' 的解决方案
  • 只需删除32,留下(original &lt;&lt; bits) | (original &gt;&gt; ( -bits)),您的代码也适用于其他无符号类型。
  • 我必须旋转一个字节,例如,"10001100" (140) 得到"0001101 (13) "。我尝试了提到的解决方案,但没有成功。有什么帮助吗?
  • System.Numerics命名空间中,有BitOperations.RotateLeftBitOperations.RotateRight
【解决方案2】:

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 指出的那样,有符号整数的右移 (&gt;&gt;) 是一个特殊之处:它用符号位填充 MSB,而不是像无符号数那样填充 0。我现在更改了获取和返回 uint(无符号 32 位整数)的方法 - 这也更符合 C++ rotlrotr 函数。如果你想旋转整数,只需在传递之前对它们进行大小写,然后再次转换返回值,当然。

示例用法:

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

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

【讨论】:

  • @Mehrdad:是的,在我发帖时才意识到这一点。现在修好了。 :)
  • 我认为这对于负数会失败。您可能想强制这些值是无符号的。
  • @plinth:它不会失败。对于有符号数和无符号数,按位运算的工作方式完全相同,只是由于二进制补码系统,有符号整数表示的数可以在位移/旋转期间改变符号。
  • @Mehrdad:所以我学到了一些新东西。 :) 将编辑帖子以修复它。
  • 请注意,RjuJIT 会检测到这种模式并发出 rol 和 ror 指令,至少在 2018 年的 x86 平台上是这样,但不确定何时开始发生这种情况。
【解决方案3】:

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

这很好地简化了旋转函数,并消除了一类常见的错误,即您忘记重新存储函数的返回值,但同时可能引入一种新的、完全不同类型的错误——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

通常我肯定会把[MethodImpl(MethodImplOptions.AggressiveInlining)] 放在像这样的小方法上,但经过一些调查(在 x64 上)我发现这里根本没有必要。如果 JIT 确定该方法是合格的(例如,如果您取消选中 VisualStudio 调试器复选框 'Suppress JIT Optimization',默认情况下启用),则无论如何都会内联方法,这就是这种情况。

如果术语不熟悉,JIT 或“即时”指的是一次性将 IL 指令转换为针对平台调整的本机代码在运行时检测到,在 .NET 程序运行时按方法按需发生的进程

为了演示 by-ref 扩展方法的使用,我将只关注上面显示的第一个方法“向左旋转”,并比较 JIT 输出在传统的 by-value 扩展方法和较新的 by-ref 方法之间。以下是要在 Windows 10 上 .NET 4.7 中的 x64 Release 上比较的两种测试方法。如上所述,这将使用 JIT优化“未抑制”,因此在您将看到的这些测试条件下,函数将完全消失在内联代码中。

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)

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

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  

哇。是的,这不是开玩笑。马上我们可以看到,在 ECMA CIL (.NET) 中间语言中明显缺乏OpCodes.Rot-family 指令几乎不是问题。抖动能够通过我们的一堆 C# 解决方法代码(ul &lt;&lt; 1) | (ul &gt;&gt; 63) 来判断其基本意图,在这两种情况下,x64 JIT 都通过简单地发出本机 rol 指令来实现。令人印象深刻的是,ByRef 版本使用一条指令直接在主存目标地址上执行轮换,甚至无需将其加载到寄存器中。

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

为了澄清这一点,让我们在调试会话中重新抑制 JIT 优化。这样做会使我们的辅助扩展方法回来,带有完整的主体和堆栈帧,以更好地解释上一段的第一句话。首先,让我们看一下调用站点。在这里我们可以看到传统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  

参考

                     x.Rol_ByRef();
// 00007FF969CE04B7  mov         rcx,7FF969BC4888h  
// 00007FF969CE04C1  call        00007FF969CE00B0
//             ...all done, nothing to do here; the callee did everything in-place for us

正如我们对与这两个片段相关的 C# 代码所期望的那样,我们看到 by-val 调用者有一个通话返回后要做的一堆工作。这是用rax 寄存器中返回的完全独立的ulong 值覆盖ulong 值“x”的父副本的过程。

现在让我们看看被调用的目标函数的代码。看到它们需要强制 JIT “抑制”优化。以下是 x64 Release JIT 为 Rol_ByValRol_ByRef 函数发出的本机代码。

为了专注于两者之间微小但至关重要的区别,我删除了一些管理样板文件。 (我为上下文留下了堆栈框架设置和拆卸,并展示了在此示例中,辅助内容如何使实际的内容指令相形见绌。)你能看到 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 值实例——这两个示例在这方面是相同的。父级指示其私有副本ul 驻留在上层堆栈帧中的地址。事实证明,没有必要将被调用者与读取它们所在的那些实例隔离开来,只要我们可以确定它们永远不会写入这些指针。这是一种“惰性”或延迟的方法,它为每个较低(子)堆栈帧分配维护其较高调用者的ValueType语义的责任。如果子框架永远不会覆盖它,则调用者无需主动复制传递给子框架的任何ValueType;为了最大限度地避免不必要的复制,只有孩子才能做出最新可能的决定。

另外有趣的是,在我展示的第一个 'ByVal' 示例中,我们可能会对 rax 的笨拙用法做出解释。在通过内联完全减少了按值方法之后,为什么还需要在寄存器中进行轮换?

在这两个最新的完整方法体版本中,很明显第一个方法返回ulong,第二个是void。因为返回值是在rax 中传递的,所以这里的 ByVal 方法无论如何都必须将它提取到该寄存器中,因此在此处旋转它也很容易。因为 ByRef 方法不需要返回任何值,所以它不需要在任何地方为其调用者粘贴任何东西,更不用说在rax 中了。似乎“不必理会rax”将 ByRef 代码释放到其父级共享“它所在的位置”的ulong 实例,使用花哨的qword ptr 间接进入父级的堆栈帧内存,而不是使用寄存器。所以这就是我对我们之前看到的“残余rax”之谜的推测,但也许是可信的解释。

【讨论】:

  • 很好的演示。以前不知道ref this
【解决方案4】:

简单的 shift 版本是行不通的。原因是,右移有符号数会用符号位填充左边的位,而不是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)((val >> count) | (val << (32 - count)));
}

【讨论】:

  • 或者您可以更改扩展方法以引入一个 uint 并返回一个 uint,这就是 Jon 所做的
  • @Joseph:在这种情况下,您应该在使用 int 调用时手动进行转换。
  • 是的,仅获取/返回 uint 的函数会更有意义(这也是我在更新后的帖子中所做的)。否则,如果你真的想旋转一个 uint,你实际上不需要做任何事情时,你会施放 4 次!
  • 无论如何,这些东西并没有真正增加多少价值。我刚刚提交了答案以澄清签名的右移事实,这在进行按位运算时真的很重要。
  • +1 顺便说一句,因为您确实指出了右移有符号整数的问题。
【解决方案5】:

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

【讨论】:

  • 只是为任何搜索在 System.Numerics 命名空间中找到它的人添加。
【解决方案6】:

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

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 位重载不需要屏蔽操作,因为移位运算符自己会处理这些大小的左侧操作数。

【讨论】:

    【解决方案7】:
    // 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);
    

    【讨论】:

    • 非常效率低下!
    猜你喜欢
    • 2017-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多