Operator shift kiri dan kanan (<< dan >>) sudah tersedia di C ++. Namun, saya tidak dapat menemukan bagaimana saya dapat melakukan operasi rotasi atau shift melingkar.
Bagaimana operasi seperti "Putar Kiri" dan "Putar Kanan" dilakukan?
Berputar ke kanan dua kali di sini
Initial --> 1000 0011 0100 0010
harus menghasilkan:
Final --> 1010 0000 1101 0000
Sebuah contoh akan sangat membantu.
(catatan editor: Banyak cara umum untuk mengekspresikan rotasi di C mengalami perilaku tidak terdefinisi jika jumlah rotasi nol, atau dikompilasi ke lebih dari satu instruksi mesin rotasi tunggal. Jawaban pertanyaan ini harus mendokumentasikan praktik terbaik.)
Jawaban:
Lihat juga versi sebelumnya dari jawaban ini pada pertanyaan rotasi lainnya dengan beberapa detail lebih lanjut tentang apa yang diproduksi asm gcc / clang untuk x86.
Cara paling ramah kompiler untuk mengekspresikan rotate dalam C dan C ++ yang menghindari Perilaku Tidak Terdefinisi tampaknya adalah implementasi John Regehr . Saya telah menyesuaikannya untuk memutar dengan lebar tipe (menggunakan tipe lebar tetap seperti
uint32_t
).#include <stdint.h> // for uint32_t #include <limits.h> // for CHAR_BIT // #define NDEBUG #include <assert.h> static inline uint32_t rotl32 (uint32_t n, unsigned int c) { const unsigned int mask = (CHAR_BIT*sizeof(n) - 1); // assumes width is a power of 2. // assert ( (c<=mask) &&"rotate by type width or more"); c &= mask; return (n<<c) | (n>>( (-c)&mask )); } static inline uint32_t rotr32 (uint32_t n, unsigned int c) { const unsigned int mask = (CHAR_BIT*sizeof(n) - 1); // assert ( (c<=mask) &&"rotate by type width or more"); c &= mask; return (n>>c) | (n<<( (-c)&mask )); }
Berfungsi untuk semua jenis bilangan bulat unsigned, tidak hanya
uint32_t
, jadi Anda dapat membuat versi untuk ukuran lain.Lihat juga versi template C ++ 11 dengan banyak pemeriksaan keamanan (termasuk
static_assert
bahwa lebar tipe adalah pangkat 2) , yang tidak terjadi pada beberapa DSP 24-bit atau mainframe 36-bit, misalnya.Saya akan merekomendasikan hanya menggunakan template sebagai back-end untuk pembungkus dengan nama yang menyertakan lebar putar secara eksplisit. Aturan promosi integer berarti bahwa
rotl_template(u16 & 0x11UL, 7)
akan melakukan rotasi 32 atau 64-bit, bukan 16 (bergantung pada lebarnyaunsigned long
). Evenuint16_t & uint16_t
dipromosikansigned int
oleh aturan integer-promotion C ++, kecuali pada platformint
yang tidak lebih luas dariuint16_t
.Pada x86 , versi ini sejajar dengan satu
rol r32, cl
(ataurol r32, imm8
) dengan kompiler yang melakukannya, karena kompilator tahu bahwa instruksi rotate dan shift x86 menutupi jumlah shift dengan cara yang sama seperti yang dilakukan sumber C.Dukungan kompiler untuk idiom penghindaran UB ini pada x86, untuk
uint32_t x
danunsigned int n
untuk perubahan jumlah variabel:ror
ataurol
untuk jumlah variabel.shld edi,edi,7
yang lebih lambat dan membutuhkan lebih banyak byte daripadarol edi,7
pada beberapa CPU (terutama AMD, tetapi juga beberapa Intel), ketika BMI2 tidak tersedia untukrorx eax,edi,25
menyimpan MOV._rotl
/_rotr
intrinsics dari<intrin.h>
pada x86 (termasuk x86-64).gcc untuk ARM menggunakan
and r1, r1, #31
untuk berputar variabel-hitung, tapi masih melakukan rotate yang sebenarnya dengan instruksi tunggal :ror r0, r0, r1
. Jadi gcc tidak menyadari bahwa jumlah rotasi pada dasarnya bersifat modular. Seperti yang dikatakan dokumen ARM, “ROR dengan panjang shiftn
,, lebih dari 32 sama dengan ROR dengan panjang shiftn-32
” . Saya pikir gcc menjadi bingung di sini karena pergeseran kiri / kanan pada ARM memenuhi hitungan, jadi pergeseran sebesar 32 atau lebih akan menghapus register. (Tidak seperti x86, di mana shift mask hitungannya sama dengan rotates). Ia mungkin memutuskan bahwa ia membutuhkan instruksi AND sebelum mengenali idiom rotate, karena cara kerja non-circular shift pada target tersebut.Kompiler x86 saat ini masih menggunakan instruksi tambahan untuk menutupi jumlah variabel untuk putaran 8 dan 16-bit, mungkin karena alasan yang sama mereka tidak menghindari AND pada ARM. Ini adalah pengoptimalan yang terlewat, karena performa tidak bergantung pada jumlah rotasi pada CPU x86-64 mana pun. (Penyembunyian hitungan diperkenalkan dengan 286 untuk alasan kinerja karena menangani pergeseran secara berulang, bukan dengan latensi konstan seperti CPU modern.)
BTW, lebih memilih rotate-right untuk variabel-count rotates, untuk menghindari compiler yang
32-n
menerapkan rotasi kiri pada arsitektur seperti ARM dan MIPS yang hanya menyediakan rotate-right. (Ini mengoptimalkan dengan penghitungan konstanta waktu kompilasi.)Fun Fakta: ARM tidak benar-benar memiliki dedicated pergeseran / petunjuk rotate, itu hanya MOV dengan sumber operan melalui laras-shifter dalam modus ROR :
mov r0, r0, ror r1
. Jadi rotate bisa dilipat menjadi operand sumber register untuk instruksi EOR atau sesuatu.Pastikan Anda menggunakan tipe unsigned untuk
n
dan nilai kembaliannya, jika tidak maka tidak akan berputar . (gcc untuk target x86 melakukan pergeseran kanan aritmatika, menggeser salinan bit-tanda daripada nol, yang mengarah ke masalah ketika AndaOR
menggeser dua nilai bersama-sama. Pergeseran kanan bilangan bulat bertanda negatif adalah perilaku yang ditentukan implementasi di C.)Juga, pastikan hitungan shift adalah tipe unsigned , karena
(-n)&31
dengan tipe yang ditandatangani bisa menjadi pelengkap atau tanda / besaran, dan tidak sama dengan 2 ^ n modular yang Anda dapatkan dengan komplemen unsigned atau two. (Lihat komentar di posting blog Regehr).unsigned int
bekerja dengan baik pada setiap kompiler yang saya lihat, untuk setiap lebarnyax
. Beberapa tipe lain benar-benar mengalahkan pengenalan idiom untuk beberapa kompiler, jadi jangan hanya menggunakan tipe yang sama sepertix
.Beberapa kompiler menyediakan intrinsik untuk rotasi , yang jauh lebih baik daripada inline-asm jika versi portabel tidak menghasilkan kode yang baik pada kompilator yang Anda targetkan. Tidak ada intrinsik lintas platform untuk kompiler apa pun yang saya ketahui. Ini adalah beberapa opsi x86:
<immintrin.h>
menyediakan_rotl
dan_rotl64
intrinsik , dan sama untuk shift kanan. MSVC membutuhkan<intrin.h>
, sedangkan gcc membutuhkan<x86intrin.h>
. An#ifdef
menangani gcc vs. icc, tetapi clang tampaknya tidak menyediakannya di mana pun, kecuali dalam mode kompatibilitas MSVC dengan-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. Dan asm yang dipancarkannya menyebalkan (masking ekstra dan CMOV)._rotr8
dan_rotr16
.<x86intrin.h>
juga menyediakan__rolb
/__rorb
untuk putar 8-bit kiri / kanan,__rolw
/__rorw
(16-bit),__rold
/__rord
(32-bit),__rolq
/__rorq
(64-bit, hanya ditentukan untuk target 64-bit). Untuk rotasi sempit, implementasi menggunakan__builtin_ia32_rolhi
atau...qi
, tetapi rotasi 32 dan 64-bit ditentukan menggunakan shift / atau (tanpa perlindungan terhadap UB, karena kode diia32intrin.h
hanya harus bekerja di gcc untuk x86). GNU C tampaknya tidak memiliki__builtin_rotate
fungsi lintas platform seperti yang dilakukannya__builtin_popcount
(yang berkembang menjadi apa pun yang optimal pada platform target, meskipun itu bukan satu instruksi). Sebagian besar waktu Anda mendapatkan kode yang bagus dari pengenalan idiom.// For real use, probably use a rotate intrinsic for MSVC, or this idiom for other compilers. This pattern of #ifdefs may be helpful #if defined(__x86_64__) || defined(__i386__) #ifdef _MSC_VER #include <intrin.h> #else #include <x86intrin.h> // Not just <immintrin.h> for compilers other than icc #endif uint32_t rotl32_x86_intrinsic(rotwidth_t x, unsigned n) { //return __builtin_ia32_rorhi(x, 7); // 16-bit rotate, GNU C return _rotl(x, n); // gcc, icc, msvc. Intel-defined. //return __rold(x, n); // gcc, icc. // can't find anything for clang } #endif
Agaknya beberapa kompiler non-x86 memiliki intrinsik juga, tapi jangan memperluas jawaban wiki-komunitas ini untuk memasukkan semuanya. (Mungkin lakukan itu dalam jawaban yang ada tentang intrinsik ).
(Versi lama dari jawaban ini menyarankan asm sebaris khusus MSVC (yang hanya berfungsi untuk kode 32bit x86), atau http://www.devx.com/tips/Tip/14043 untuk versi C. Komentar tersebut membalasnya .)
Asm sebaris mengalahkan banyak pengoptimalan , terutama gaya MSVC karena memaksa masukan untuk disimpan / dimuat ulang . Sebuah GNU C inline-asm rotate yang ditulis dengan hati-hati akan memungkinkan penghitungan menjadi operan langsung untuk penghitungan shift konstan waktu kompilasi, tetapi masih tidak dapat mengoptimalkan sepenuhnya jika nilai yang akan digeser juga merupakan konstanta waktu kompilasi setelah sebaris. https://gcc.gnu.org/wiki/DontUseInlineAsm .
sumber
bits = CHAR_BIT * sizeof(n);
danc &= bits - 1;
danreturn ((n >> c) | (n << (bits - c)))
, mana yang akan saya gunakan?bits - c
=32 - 0
. (Saya tidak mendapatkan ping dari ini karena saya hanya mengedit wiki, bukan menulisnya di tempat pertama.)0 < count < bits
adalah persyaratan konstan dari hampir semua CPU dan bahasa pemrograman yang mengimplementasikan rotasi (terkadang0 ≤ count < bits
, tetapi menggeser dengan jumlah bit yang tepat hampir selalu tidak ditentukan atau dibulatkan ke nop alih-alih menghapus nilainya, dan berputar, yah.)Karena ini C ++, gunakan fungsi inline:
template <typename INT> INT rol(INT val) { return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1)); }
Varian C ++ 11:
template <typename INT> constexpr INT rol(INT val) { static_assert(std::is_unsigned<INT>::value, "Rotate Left only makes sense for unsigned types"); return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1)); }
sumber
INT
merupakan bilangan bulat bertanda dan tanda disetel! Ujian misalnyarol<std::int32_t>(1 << 31)
yang harus dibalik ke 1 tetapi benar-benar menjadi-1
(karena tandanya dipertahankan).std::numeric_limits<INT>::digits
sebagai penggantiCHAR_BIT * sizeof
. Saya lupa jika tipe unsigned diperbolehkan memiliki padding yang tidak digunakan (misalnya integer 24-bit disimpan dalam 32 bit), tetapi jika demikian makadigits
akan lebih baik. Lihat juga gist.github.com/pabigot/7550454 untuk versi dengan pemeriksaan lebih lanjut untuk perubahan jumlah variabel.Kebanyakan kompiler memiliki intrinsik untuk itu. Visual Studio misalnya _rotr8, _rotr16
sumber
C ++ 20
std::rotl
danstd::rotr
Itu telah datang! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html dan harus menambahkannya ke
<bit>
tajuk.cppreference mengatakan bahwa penggunaannya akan seperti:
#include <bit> #include <bitset> #include <cstdint> #include <iostream> int main() { std::uint8_t i = 0b00011101; std::cout << "i = " << std::bitset<8>(i) << '\n'; std::cout << "rotl(i,0) = " << std::bitset<8>(std::rotl(i,0)) << '\n'; std::cout << "rotl(i,1) = " << std::bitset<8>(std::rotl(i,1)) << '\n'; std::cout << "rotl(i,4) = " << std::bitset<8>(std::rotl(i,4)) << '\n'; std::cout << "rotl(i,9) = " << std::bitset<8>(std::rotl(i,9)) << '\n'; std::cout << "rotl(i,-1) = " << std::bitset<8>(std::rotl(i,-1)) << '\n'; }
memberikan keluaran:
i = 00011101 rotl(i,0) = 00011101 rotl(i,1) = 00111010 rotl(i,4) = 11010001 rotl(i,9) = 00111010 rotl(i,-1) = 10001110
Saya akan mencobanya ketika dukungan datang ke GCC, GCC 9.1.0
g++-9 -std=c++2a
masih belum mendukungnya.Proposal tersebut mengatakan:
dan:
A
std::popcount
juga ditambahkan untuk menghitung jumlah 1 bit: Bagaimana menghitung jumlah bit set dalam integer 32-bit?sumber
Pasti:
template<class T> T ror(T x, unsigned int moves) { return (x >> moves) | (x << sizeof(T)*8 - moves); }
sumber
8
salah ejaCHAR_BIT
(yang tidak harus persis 8)?std::numeric_limits<T>::digits
.Bagaimana dengan sesuatu seperti ini, menggunakan bitset standar ...
#include <bitset> #include <iostream> template <std::size_t N> inline void rotate(std::bitset<N>& b, unsigned m) { b = b << m | b >> (N-m); } int main() { std::bitset<8> b(15); std::cout << b << '\n'; rotate(b, 2); std::cout << b << '\n'; return 0; }
HTH,
sumber
m %= N;
ke akun untuk shift>= N
.Jika x adalah nilai 8 bit, Anda dapat menggunakan ini:
x=(x>>1 | x<<7);
sumber
x
ditandatangani.Secara rinci, Anda dapat menerapkan logika berikut.
Jika Pola Bit 33602 di Integer
dan Anda perlu Roll over dengan 2 shif kanan kemudian: pertama buat salinan pola bit dan kemudian geser ke kiri: Panjang - RightShift yaitu panjang 16 nilai pergeseran kanan adalah 2 16 - 2 = 14
Setelah 14 kali pergeseran kiri Anda dapatkan.
Sekarang geser nilai ke kanan 33602, 2 kali sesuai kebutuhan. Anda mendapatkan
Sekarang ambil nilai OR antara 14 kali nilai pergeseran kiri dan 2 kali nilai pergeseran kanan.
Dan Anda mendapatkan nilai rollover yang bergeser. Ingat operasi yang sedikit bijak lebih cepat dan ini bahkan tidak memerlukan loop apa pun.
sumber
Dengan asumsi Anda ingin menggeser ke kanan demi
L
bit, dan inputnyax
adalah angka denganN
bit:unsigned ror(unsigned x, int L, int N) { unsigned lsbs = x & ((1 << L) - 1); return (x >> L) | (lsbs << (N-L)); }
sumber
Jawaban yang benar adalah sebagai berikut:
#define BitsCount( val ) ( sizeof( val ) * CHAR_BIT ) #define Shift( val, steps ) ( steps % BitsCount( val ) ) #define ROL( val, steps ) ( ( val << Shift( val, steps ) ) | ( val >> ( BitsCount( val ) - Shift( val, steps ) ) ) ) #define ROR( val, steps ) ( ( val >> Shift( val, steps ) ) | ( val << ( BitsCount( val ) - Shift( val, steps ) ) ) )
sumber
val
ditandatangani.Kode Sumber x nomor bit
int x =8; data =15; //input unsigned char tmp; for(int i =0;i<x;i++) { printf("Data & 1 %d\n",data&1); printf("Data Shifted value %d\n",data>>1^(data&1)<<(x-1)); tmp = data>>1|(data&1)<<(x-1); data = tmp; }
sumber
saran lain
template<class T> inline T rotl(T x, unsigned char moves){ unsigned char temp; __asm{ mov temp, CL mov CL, moves rol x, CL mov CL, temp }; return x; }
sumber
Di bawah ini adalah versi jawaban Dídac Pérez yang sedikit lebih baik , dengan kedua arah diterapkan, bersama dengan demo penggunaan fungsi ini menggunakan unsigned char dan unsigned long long values. Beberapa catatan:
cout << +value
trik untuk mengeluarkan karakter unsigned secara singkat yang saya temukan di sini: https://stackoverflow.com/a/28414758/1599699<put the type here>
sintaks eksplisit untuk kejelasan dan keamanan.Ini kode yang saya gunakan:
#include <iostream> using namespace std; template <typename T> inline T rotateAndCarryLeft(T rotateMe, unsigned char shiftNum) { static const unsigned char TBitCount = sizeof(T) * 8U; return (rotateMe << shiftNum) | (rotateMe >> (TBitCount - shiftNum)); } template <typename T> inline T rotateAndCarryRight(T rotateMe, unsigned char shiftNum) { static const unsigned char TBitCount = sizeof(T) * 8U; return (rotateMe >> shiftNum) | (rotateMe << (TBitCount - shiftNum)); } void main() { //00010100 == (unsigned char)20U //00000101 == (unsigned char)5U == rotateAndCarryLeft(20U, 6U) //01010000 == (unsigned char)80U == rotateAndCarryRight(20U, 6U) cout << "unsigned char " << 20U << " rotated left by 6 bits == " << +rotateAndCarryLeft<unsigned char>(20U, 6U) << "\n"; cout << "unsigned char " << 20U << " rotated right by 6 bits == " << +rotateAndCarryRight<unsigned char>(20U, 6U) << "\n"; cout << "\n"; for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum) { cout << "unsigned char " << 21U << " rotated left by " << +shiftNum << " bit(s) == " << +rotateAndCarryLeft<unsigned char>(21U, shiftNum) << "\n"; } cout << "\n"; for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum) { cout << "unsigned char " << 21U << " rotated right by " << +shiftNum << " bit(s) == " << +rotateAndCarryRight<unsigned char>(21U, shiftNum) << "\n"; } cout << "\n"; for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum) { cout << "unsigned long long " << 3457347ULL << " rotated left by " << +shiftNum << " bit(s) == " << rotateAndCarryLeft<unsigned long long>(3457347ULL, shiftNum) << "\n"; } cout << "\n"; for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum) { cout << "unsigned long long " << 3457347ULL << " rotated right by " << +shiftNum << " bit(s) == " << rotateAndCarryRight<unsigned long long>(3457347ULL, shiftNum) << "\n"; } cout << "\n\n"; system("pause"); }
sumber
--- Substituting RLC in 8051 C for speed --- Rotate left carry Here is an example using RLC to update a serial 8 bit DAC msb first: (r=DACVAL, P1.4= SDO, P1.5= SCLK) MOV A, r ?1: MOV B, #8 RLC A MOV P1.4, C CLR P1.5 SETB P1.5 DJNZ B, ?1 Here is the code in 8051 C at its fastest: sbit ACC_7 = ACC ^ 7 ; //define this at the top to access bit 7 of ACC ACC = r; B = 8; do { P1_4 = ACC_7; // this assembles into mov c, acc.7 mov P1.4, c ACC <<= 1; P1_5 = 0; P1_5 = 1; B -- ; } while ( B!=0 ); The keil compiler will use DJNZ when a loop is written this way. I am cheating here by using registers ACC and B in c code. If you cannot cheat then substitute with: P1_4 = ( r & 128 ) ? 1 : 0 ; r <<= 1; This only takes a few extra instructions. Also, changing B for a local var char n is the same. Keil does rotate ACC left by ADD A, ACC which is the same as multiply 2. It only takes one extra opcode i think. Keeping code entirely in C keeps things simpler sometimes.
sumber
Kelebihan fungsi:
unsigned int rotate_right(unsigned int x) { return (x>>1 | (x&1?0x80000000:0)) } unsigned short rotate_right(unsigned short x) { /* etc. */ }
sumber
#define ROTATE_RIGHT(x) ( (x>>1) | (x&1?0x8000:0) )
sumber