C # побитовый поворот влево и поворот вправо - PullRequest
34 голосов
/ 01 мая 2009

Что такое эквивалент C # (.NET 2.0) _rotl и _rotr из C ++?

Ответы [ 6 ]

40 голосов
/ 01 мая 2009

Это то, что вы пытаетесь сделать?

Джон Скит ответил на это на другом сайте

По сути, вы хотите

(слева)

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

или

(справа)

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

Кроме того, как уже говорил Мехрдад, это работает только для uint, что является примером, который также приводит Джон.

23 голосов
/ 01 мая 2009

Нет встроенной языковой функции для ротации битов в 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))
}

Примечание: Как указывает Мехрдад, смещение вправо (>>) для целых чисел со знаком является особенностью: оно заполняет 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 1 с в двоичном формате.)

9 голосов
/ 01 мая 2009

Наивная версия переключения не будет работать. Причина в том, что смещенные вправо числа со знаком заполнят левые биты знаком , а не 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)));
}
8 голосов
/ 02 февраля 2018

Используя новейшие 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 «Подавить оптимизацию JIT» , который включен по умолчанию), методы будут встроены независимо, и это имеет место в данном случае.

В случае, если термин незнаком, JIT или «точно в срок» относится к разовому преобразованию инструкций IL в собственный код, настроенный для обнаруженной платформы. во время выполнения процесс, который происходит по требованию, для каждого метода как .NET , программа запускает .

Чтобы продемонстрировать использование метода расширения by-ref , я сосредоточусь только на первом показанном выше методе «повернуть влево» и сравню вывод JIT между традиционный по значению метод расширения и более новый по-ref подход. Вот два метода тестирования, которые нужно сравнить на x64 Release в .NET 4.7 на Windows 10. Как отмечалось выше, это будет с оптимизацией JIT, а не -suppressed ', поэтому в этих условиях тестирования, как вы увидите, функции полностью исчезнут во встроенном коде.

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  

Wow. Да, это не шутка. Сразу видно, что явное отсутствие OpCodes.Rot семейства инструкций на промежуточном языке ECMA CIL (.NET) в значительной степени не является проблемой; Джиттер мог видеть сквозь нашу кучу кода обхода C # (ul << 1) | (ul >> 63), чтобы предугадать его основное намерение, которое в обоих случаях реализует JIT x64, просто испуская собственную инструкцию 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  

* ** +1078 +1079 * по ссылке * 1 081 ** ** 1083 тысяча восемьдесят-два * 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 есть куча работы делать после возврата звонка. Это процесс перезаписи родительской копии значения ulong 'x' полностью независимым значением ulong, которое возвращается в регистр rax. Теперь давайте посмотрим на код для вызываемых целевых функций. Чтобы увидеть их, нужно заставить JIT «подавить» оптимизацию. Ниже приведен собственный код, который выпускает JIT Release x64 для функций Rol_ByVal и Rol_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, переданные в дочерний фрейм, если ребенок никогда не перезаписывает его; чтобы максимально избежать ненужного копирования, только ребенок может принять самое последнее решение. Также интересно, что у нас могло бы быть объяснение здесь неуклюжего использования rax в первом примере 'ByVal', который я показал. После того, как метод значения по значению был полностью уменьшен с помощью встраивания, почему вращение все еще должно происходить в регистре? Что ж, в этих последних двух версиях с полным текстом метода ясно, что первый метод возвращает ulong, а второй - void. Поскольку возвращаемое значение передается в rax, метод ByVal должен в любом случае извлекать его в этот регистр, так что вращать его там тоже нетрудно. Поскольку метод ByRef не должен возвращать никакого значения, ему не нужно ничего прикреплять к вызывающему объекту, не говоря уже о rax. Кажется вероятным, что «отсутствие необходимости беспокоиться о rax» освобождает код ByRef для нацеливания на экземпляр ulong, к которому его родительский ресурс поделился «где он лежит», используя причудливый qword ptr для косвенной передачи в память кадров стека родителя, вместо использования регистра. Так что это мое умозрительное, но, возможно, заслуживающее доверия объяснение "остаточной rax" тайны, которую мы видели ранее.
1 голос
/ 27 сентября 2012

Обратите внимание, что если вы хотите создать перегрузки, которые работают с более короткими целочисленными значениями, вам нужно добавить дополнительный шаг, как показано в:

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-разрядных перегрузок, поскольку сами операторы сдвига позаботятся об этом для таких размеров левых операндов.

0 голосов
/ 10 сентября 2014
// 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);
...