web-dev-qa-db-ja.com

C#ビット単位で左回転および右回転

C++の_rotlおよび_rotrのC#相当(.NET 2.0)は何ですか?

36
Prithis

これはあなたがやろうとしていることですか?

Jon Skeetが別のサイトでこれに回答しました

基本的にあなたが欲しいのは

(左用)

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

または

(右)

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

また、Mehrdadが既に示唆しているように、これはuintでのみ機能します。これは、Jonが提供する例でもあります。

44
Joseph

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が指摘するように、右シフト(>>)は、符号付き整数の場合に特有です。符号なし整数の場合のように、MSBに0ではなく符号ビットを入力します。代わりにuint(符号なし32ビット整数)を取得して返すようにメソッドを変更しました。これもC++のrotlおよびrotr関数に準拠しています。整数をローテーションする場合は、渡す前に大文字と小文字を区別し、もちろん戻り値をキャストします。

使用例:

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

(ご了承ください int.MinValueは111111111111111111111111-32の2進数で1です。)

24
Noldorin

最新のC#7を使用して、by-ref拡張メソッド。ヘルパー関数からの戻り値を変数に絶えず格納するという煩雑な作業を取り除くことができます。

これにより、回転関数が見事に合理化され、関数の戻り値を再格納するのを忘れている一般的なバグのクラスがなくなりますが、ValueTypesが誤って取得する新しい完全に異なるタイプのバグが発生する可能性があります変更したいin-situしたくない、または期待しない場合。

_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命令の1回限りの変換を指します。このプロセスは、オンデマンドで、メソッドごとに。NETプログラムが実行されます

by-ref拡張メソッドの使用を示すために、最初の上記の「左回転」の方法で、従来のby-value拡張機能のJIT出力を比較しますメソッドと新しいby-refアプローチ。 x64Releaseで比較される2つのテスト方法は、。NET 4.7Windows 10で。上記のように、これは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_-ファミリーの命令が明らかに不足していることがわかりますほとんど問題ではありません。ジッターは、C#回避策コード_(ul << 1) | (ul >> 63)_の山を通して、その本質的な目的を理解することができました。どちらの場合も、x64 JITは、ネイティブrol命令を発行するだけで実装します。印象的なことに、ByRefバージョンは単一の命令を使用して、レジスタにロードすることなく、メインメモリのターゲットアドレスで直接ローテーションを実行します。

ByValの場合でも、呼び出されたメソッドが完全に最適化される前に(値タイプのセマンティクスの本質であるように)、呼び出し元の元の値を変更せずにおくために必要であった過剰コピーの残りの痕跡が依然として見られます。ここで整数回転の場合、それは64ビットレジスタraxへのターゲット整数の追加のフェッチ/ストアです。

それを明確にするために、デバッグセッションでJIT最適化を再度抑制しましょう。そうすることで、ヘルパー拡張メソッドが復活し、前の段落の最初の文をよりよく説明するための完全な本文とスタックフレームが表示されます。まず、通話サイトを見てみましょう。ここでは、従来のValueTypeセマンティクスの効果を確認できます。つまり、下位のスタックフレームが親フレームのValueTypeコピーを操作できないことを保証することになります。

by-value:

_                     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  
_

by-reference

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

これら2つのフラグメントのそれぞれに関連付けられたC#コードから予想されるように、by-val呼び出し元は、呼び出しが戻った後に行うべき一連の作業を行います。これは、ulongレジスタに返される完全に独立したulong値でrax値「x」の親コピーを上書きするプロセスです。

次に、呼び出されたターゲット関数のコードを見てみましょう。それらを見るには、JITに最適化を「抑制」させる必要があります。以下は、x(64)JITが_Rol_ByVal_および_Rol_ByRef_関数に対して発行するネイティブコードです。

2つの小さな重要な違いに焦点を合わせるために、管理上の定型文の一部を取り除きました。 (私はコンテキストのスタックフレームのセットアップとティアダウンを残しました。この例で、その補助的なものが実際のコンテンツの指示をかなり小さくする方法を示すために)。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のプライベートコピーが上位スタックフレームに存在するアドレスを示します。これらのポインターに書き込みを行わないことが確実である限り、それらが存在するインスタンスをreadingから呼び出し先を隔離する必要がないことがわかります。これは、「遅延」または遅延アプローチで、各下位(子)スタックフレームに責任を割り当て、ValueTypeセマンティクスを保持します上位の呼び出し元。子が最終的に上書きしない場合、呼び出し側が子フレームに渡されたValueTypeをプロアクティブにコピーする必要はありません。不必要なコピーをできるだけ回避するために、可能な限り最新の判断を下せるのは子供だけです。

また、興味深いのは、ここで示した最初の「ByVal」の例でのraxの不格好な使用についての説明があるかもしれません。値による方法がインライン化によって完全に削減された後、なぜローテーションがまだレジスターで発生する必要があるのですか?

これらの最新の2つのフルメソッドボディバージョンでは、最初のメソッドがulongを返し、2番目のメソッドがvoidであることは明らかです。戻り値はraxで渡されるので、ここのByValメソッドはとにかくそれをそのレジスターにフェッチする必要があるので、そこでも回転させるのは簡単です。 ByRefメソッドは値を返す必要がないため、raxはもちろんのこと、呼び出し元に何も貼り付ける必要はありません。 「raxに煩わされる必要がない」というのは、ByRefコードを解放して、親が共有しているulongインスタンスをターゲットとする「どこにあるか」というファンシーな_qword ptr_レジスタを使用する代わりに、親のスタックフレームメモリに間接的に。それが、私が先に見た「残余のrax」の謎についての私の推測ですが、おそらく信頼できる説明です。

13
Glenn Slayden

単純なシフトのバージョンは機能しません。その理由は、右シフトの符号付き数値は、左のビットを符号ビット、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)));
}
9
Mehrdad Afshari

短い整数値を操作するオーバーロードを作成する場合は、次に示すように、ステップを追加する必要があることに注意してください。

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ビットのオーバーロードでは、マスキング操作は必要ありません。シフトオペレーター自体が左側のオペランドのそれらのサイズに対応するからです。

2
John Beyer