I've telah mencoba untuk belajar C di waktu luang saya, dan bahasa-bahasa lain (C#, Java, dll.) memiliki konsep yang sama (dan sering sama operator) ...
Apa yang saya'm bertanya-tanya adalah, pada tingkat inti, yang tidak sedikit pergeseran (<<
, >>
, >>>
) do, masalah apa yang bisa membantu memecahkan, dan apa yang gotchas mengintai di tikungan? Dengan kata lain, seorang pemula mutlak's panduan untuk sedikit pergeseran dalam segala kebaikan.
Sedikit pergeseran operator melakukan persis apa namanya. Mereka menggeser bit. Berikut ini's singkat (atau tidak-jadi-singkat) pendahuluan untuk pergeseran yang berbeda operator.
>>
adalah aritmatika (atau ditandatangani) shift kanan operator.>>>
adalah logis (atau unsigned) shift kanan operator. <<
adalah shift kiri operator, dan memenuhi kebutuhan logis dan aritmatika shift.Semua operator ini dapat diterapkan untuk nilai-nilai integer (int
, lama
, mungkin pendek
dan byte
atau char
). Dalam beberapa bahasa, menerapkan shift operator untuk setiap tipe data yang lebih kecil dari int
secara otomatis mengubah ukuran operan untuk menjadi int
.
Perhatikan bahwa <<<
tidak seorang operator, karena itu akan mubazir.
Juga perhatikan bahwa C dan C++ tidak membedakan antara kanan shift operator. Mereka hanya memberikan >>
operator, dan hak-pergeseran perilaku adalah pelaksanaan yang ditetapkan untuk ditandatangani jenis. Sisa menjawab menggunakan C# / Java operator.
(Di semua mainstream C dan C++ implementasi termasuk gcc dan dentang/LLVM, >>
pada ditandatangani jenis aritmatika. Beberapa kode mengasumsikan ini, tetapi isn't sesuatu yang standar jaminan. It's tidak tidak terdefinisi, meskipun; standar membutuhkan implementasi untuk menentukan satu atau lain cara. Namun, kiri bergeser dari negatif ditandatangani angka adalah perilaku tidak terdefinisi (signed integer overflow). Jadi kecuali jika anda perlu aritmatika shift kanan, it's biasanya ide yang baik untuk melakukan sedikit pergeseran dengan unsigned jenis.)
Bilangan bulat yang disimpan dalam memori, sebagai rangkaian bit. Misalnya, nomor 6 disimpan sebagai 32-bit int
akan sama:
00000000 00000000 00000000 00000110
Pergeseran ini pola bit ke kiri satu posisi (6 << 1
) akan menghasilkan angka 12:
00000000 00000000 00000000 00001100
Seperti yang anda lihat, angka telah bergeser ke kiri dengan satu posisi, dan digit terakhir di sebelah kanan diisi dengan nol. Anda mungkin juga dicatat bahwa pergeseran kiri setara dengan perkalian dengan kekuatan 2. Jadi 6 << 1
setara dengan 6 * 2
, dan 6 << 3
setara dengan 6 * 8
. Baik mengoptimalkan compiler akan menggantikan perkalian dengan pergeseran bila mungkin.
Harap dicatat bahwa ini adalah tidak melingkar shift. Pergeseran nilai ini ke kiri satu posisi (3,758,096,384 << 1
):
11100000 00000000 00000000 00000000
hasil di 3,221,225,472:
11000000 00000000 00000000 00000000
Digit yang akan digeser "off the end" hilang. Tidak membungkus.
Logis shift kanan adalah sebaliknya ke kiri shift. Daripada bergerak bit ke kiri, mereka hanya bergerak ke kanan. Misalnya, pergeseran jumlah 12:
00000000 00000000 00000000 00001100
ke kanan dengan salah satu posisi (12 >>> 1
) akan kembali kami asli 6:
00000000 00000000 00000000 00000110
Jadi kita melihat bahwa pergeseran ke kanan setara dengan pembagian kekuasaan 2.
Namun, pergeseran tidak dapat merebut kembali "hilang" bit. Misalnya, jika kita menggeser pola ini:
00111000 00000000 00000000 00000110
ke kiri 4 posisi (939,524,102 << 4
), kita mendapatkan 2,147,483,744:
10000000 00000000 00000000 01100000
dan kemudian bergeser kembali ((939,524,102 << 4) >>> 4
) kita mendapatkan 134,217,734:
00001000 00000000 00000000 00000110
Kita tidak bisa kembali kita nilai asli setelah kita telah kehilangan bit.
Aritmatika shift kanan adalah persis seperti yang logis shift kanan, kecuali bukan padding dengan nol, maka bantalan dengan most significant bit. Hal ini karena yang paling signifikan adalah masuk sedikit, atau sedikit yang membedakan bilangan positif dan negatif. Oleh padding dengan bit paling signifikan, aritmatika shift kanan adalah tanda-melestarikan.
Misalnya, jika kita menafsirkan ini pola bit sebagai angka negatif:
10000000 00000000 00000000 01100000
kami memiliki jumlah -2,147,483,552. Pergeseran ini ke kanan posisi 4 dengan aritmatika shift (-2,147,483,552 >> 4) akan memberikan:
11111000 00000000 00000000 00000110
atau jumlah -134,217,722.
Jadi kita melihat bahwa kita telah diawetkan tanda-tanda dari angka negatif dengan menggunakan aritmatika shift kanan, daripada logis shift kanan. Dan sekali lagi, kita melihat bahwa kita melakukan pembagian dengan kekuatan 2.
Let's mengatakan bahwa kita memiliki satu byte:
0110110
Menerapkan single kiri bitshift membuat kita:
1101100
Paling kiri nol bergeser keluar dari byte, dan baru nol ditambahkan ke ujung kanan dari byte.
Bit don't rollover; mereka dibuang. Itu berarti jika anda shift kiri 1101100 dan kemudian shift kanan itu, anda memenangkan't mendapatkan hasil yang sama kembali.
Pergeseran ke kiri oleh N setara dengan mengalikan dengan 2N.
Pergeseran kanan dengan N adalah (jika anda menggunakan orang-orang' melengkapi) adalah setara dengan membagi dengan 2N dan pembulatan ke nol.
Bitshifting dapat digunakan untuk gila-gilaan cepat perkalian dan pembagian, yang disediakan anda bekerja dengan power dari 2. Hampir semua grafis tingkat rendah rutinitas menggunakan bitshifting.
Misalnya, jalan kembali pada masa lalu, kita menggunakan mode 13h (320x200 256 warna) untuk permainan. Dalam Mode 13h, video memori diletakkan secara berurutan per pixel. Yang dimaksudkan untuk menghitung lokasi untuk pixel, anda akan menggunakan matematika berikut:
memoryOffset = (row * 320) + column
Sekarang, kembali di hari dan usia, kecepatan sangat penting, jadi kami akan menggunakan bitshifts untuk melakukan operasi ini.
Namun, 320 lebih dari dua, sehingga untuk mendapatkan sekitar ini kita harus mengetahui apa yang menjadi kekuatan dari dua yang ditambahkan bersama-sama membuat 320:
(row * 320) = (row * 256) + (row * 64)
Sekarang kita dapat mengkonversi ke kiri shift:
(row * 320) = (row << 8) + (row << 6)
Untuk hasil akhir dari:
memoryOffset = ((row << 8) + (row << 6)) + column
Sekarang kita mendapatkan offset sama seperti sebelumnya, kecuali bukan yang mahal operasi perkalian, kita menggunakan dua bitshifts...di x86 itu akan menjadi sesuatu seperti ini (perhatikan, it's telah selamanya karena saya've dilakukan perakitan (editor's catatan: dikoreksi beberapa kesalahan dan menambahkan 32-bit contoh)):
mov ax, 320; 2 cycles
mul word [row]; 22 CPU Cycles
mov di,ax; 2 cycles
add di, [column]; 2 cycles
; di = [row]*320 + [column]
; 16-bit addressing mode limitations:
; [di] is a valid addressing mode, but [ax] isn't, otherwise we could skip the last mov
Total: 28 siklus pada apapun kuno CPU memiliki timing ini.
Vrs
mov ax, [row]; 2 cycles
mov di, ax; 2
shl ax, 6; 2
shl di, 8; 2
add di, ax; 2 (320 = 256+64)
add di, [column]; 2
; di = [row]*(256+64) + [column]
12 siklus yang sama kuno CPU.
Ya, kami akan bekerja keras untuk mencukur 16 siklus CPU.
Di 32 atau 64-bit mode, kedua versi ini mendapatkan banyak lebih pendek dan lebih cepat. Modern out-of-order eksekusi Cpu seperti Intel Skylake (lihat http://agner.org/optimize/) telah sangat hardware cepat berkembang biak (low latency dan throughput yang tinggi), sehingga gain yang jauh lebih kecil. AMD Bulldozer-keluarga sedikit lebih lambat, terutama untuk 64-bit berkembang biak. Pada Cpu Intel, dan AMD Ryzen, dua shift sedikit latency yang lebih rendah tetapi lebih banyak petunjuk dari multiply (yang dapat menyebabkan untuk menurunkan throughput):
imul edi, [row], 320 ; 3 cycle latency from [row] being ready
add edi, [column] ; 1 cycle latency (from [column] and edi being ready).
; edi = [row]*(256+64) + [column], in 4 cycles from [row] being ready.
vs
mov edi, [row]
shl edi, 6 ; row*64. 1 cycle latency
lea edi, [edi + edi*4] ; row*(64 + 64*4). 1 cycle latency
add edi, [column] ; 1 cycle latency from edi and [column] both being ready
; edi = [row]*(256+64) + [column], in 3 cycles from [row] being ready.
Kompiler akan melakukan ini untuk anda: Melihat bagaimana gcc, dentang, dan MSVC semua menggunakan shift+lea ketika mengoptimalkan kembali 320*baris + col;
.
Hal yang paling menarik untuk dicatat di sini adalah bahwa x86 memiliki shift-dan-tambahkan instruksi (LEA
) yang dapat melakukan kecil kiri bergeser dan menambahkan pada saat yang sama, dengan kinerja dan add
instruksi. LENGAN bahkan lebih kuat: satu operan dari setiap instruksi bisa kiri atau kanan bergeser secara gratis. Jadi scaling oleh compile-time konstan yang's dikenal sebagai power-of-2 dapat menjadi lebih efisien dari multiply.
OK, kembali di hari modern... sesuatu yang lebih berguna sekarang akan menggunakan bitshifting untuk menyimpan dua 8-bit nilai-nilai dalam 16-bit integer. Misalnya, di C#:
// Byte1: 11110000
// Byte2: 00001111
Int16 value = ((byte)(Byte1 >> 8) | Byte2));
// value = 000011111110000;
Dalam C++, compiler harus melakukan ini untuk anda jika anda menggunakan struct
dengan dua 8-bit anggota, tetapi dalam prakteknya don't selalu.
Bitwise operasi, termasuk sedikit pergeseran, adalah penting untuk low-level hardware atau tertanam pemrograman. Jika anda membaca spesifikasi untuk perangkat atau bahkan beberapa format file biner, anda akan melihat byte, kata, dan perkataan, dipecah menjadi non-byte blok bitfield, yang mengandung berbagai nilai-nilai kepentingan. Mengakses bit-bit tersebut-kolom untuk membaca/menulis adalah penggunaan yang paling umum.
Sederhana contoh nyata dalam pemrograman grafis adalah 16-bit pixel direpresentasikan sebagai berikut:
bit | 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| Blue | Green | Red |
Untuk mendapatkan nilai hijau anda akan melakukan hal ini:
#define GREEN_MASK 0x7E0
#define GREEN_OFFSET 5
// Read green
uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;
Penjelasan
Dalam rangka untuk mendapatkan nilai hijau-SATUNYA, yang dimulai pada offset 5 dan berakhir pada 10 (yaitu 6-bit), anda perlu menggunakan a (bit) masker, yang bila diterapkan terhadap seluruh 16-bit pixel, akan menghasilkan hanya bit kita tertarik.
#define GREEN_MASK 0x7E0
Masker yang tepat adalah 0x7E0 yang dalam biner adalah 0000011111100000 (yang merupakan 2016 dalam desimal).
uint16_t green = (pixel & GREEN_MASK) ...;
Untuk menerapkan masker, anda menggunakan operator AND (&).
uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;
Setelah menerapkan masker, anda'll berakhir dengan 16-bit nomor yang benar-benar hanya 11-bit nomor sejak MSB di 11 bit. Hijau adalah benar-benar hanya 6-bit, jadi kita perlu skala ke bawah menggunakan shift kanan (11 - 6 = 5), maka penggunaan 5 sebagai offset (#define GREEN_OFFSET 5
).
Juga umum adalah menggunakan sedikit pergeseran secara cepat perkalian dan pembagian dengan kekuatan 2:
i <<= x; // i *= 2^x;
i >>= y; // i /= 2^y;
Sedikit pergeseran ini sering digunakan pada tingkat rendah pemrograman grafis. Misalnya diberi warna pixel nilai dikodekan dalam 32-bit word.
Pixel-Color Value in Hex: B9B9B900
Pixel-Color Value in Binary: 10111001 10111001 10111001 00000000
Untuk pemahaman yang lebih baik, sama nilai biner diberi label dengan apa bagian mewakili warna apa bagian.
Red Green Blue Alpha
Pixel-Color Value in Binary: 10111001 10111001 10111001 00000000
Let's katakan misalnya kita ingin mendapatkan nilai hijau ini piksel warna. Kita dapat dengan mudah mendapatkan bahwa nilai oleh masking dan pergeseran.
Masker kami:
Red Green Blue Alpha
color : 10111001 10111001 10111001 00000000
green_mask : 00000000 11111111 00000000 00000000
masked_color = color & green_mask
masked_color: 00000000 10111001 00000000 00000000
Logis &
operator memastikan bahwa hanya nilai-nilai di mana masker 1 disimpan. Hal terakhir yang kita sekarang harus lakukan, adalah untuk mendapatkan yang benar nilai integer dengan menggeser semua bit ke kanan dengan 16 tempat (logis shift kanan).
green_value = masked_color >>> 16
Et voilá, kami memiliki integer yang mewakili jumlah yang hijau piksel warna:
Pixels-Green Value in Hex: 000000B9
Pixels-Green Value in Binary: 00000000 00000000 00000000 10111001
Pixels-Green Value in Decimal: 185
Hal ini sering digunakan untuk encoding atau decoding format gambar seperti jpg
,png
,...
.
Saya menulis tips dan trik saja, mungkin menemukan berguna dalam tes/ujian.
n = n*2
: n = n<<1
n = n/2
: n = n>>1
!(n & (n-1))
n
: n |= (1 << x)
x&1 == 0
(bahkan)x ^ (1<<n)
Perhatikan bahwa dalam implementasi Java, jumlah bit untuk shift mod'd dengan ukuran sumber.
Misalnya:
(long) 4 >> 65
sama dengan 2. Anda mungkin mengharapkan pergeseran bit ke kanan 65 kali akan nol semuanya, tapi itu's benar-benar setara dengan:
(long) 4 >> (65 % 64)
Hal ini berlaku untuk <<, >>, dan >>>. Saya belum mencobanya di dalam bahasa lain.
Beberapa Berguna Sedikit Operasi/Manipulasi dalam Python. Dilaksanakan @Ravi Prakash jawaban di python.
# basic bit operations
# int to bin
print(bin(10))
# bin to int
print(int('1010',2))
# multiplying x with 2 .... x**2== x << 1
print(200<<1)
# dividing x with 2 .... x /2 == x >> 1
print(200>>1)
# modulo x with 2 .... x%2 == x&1
if 20&1==0:
print("20 is a even number")
# check if n is power of 2 : check !(n & (n-1))
print(not(33 &(33-1)))
# getting xth bit of n : (n>>x)&1
print((10>>2)&1) # bin of 10==1010 and 2nd bit is 0
# toggle nth bit of x : x^(1<<n)
# take bin(10)==1010 and toggling 2nd bit in bin(10) we get 1110 === bin(14)
print(10^(1<<2))
Menyadari bahwa hanya 32 bit versi PHP yang tersedia di platform Windows.
Maka jika anda misalnya shift << atau >> lebih dari 31 bit, hasil unexpectable. Biasanya nomor asli bukan nol akan dikembalikan, dan itu bisa menjadi benar-benar rumit bug.
Tentu saja jika anda menggunakan versi 64 bit dari PHP (unix), anda harus menghindari pergeseran dengan lebih dari 63 bit. Namun, misalnya, MySQL menggunakan 64-bit BIGINT, jadi seharusnya tidak ada masalah kompatibilitas.
UPDATE: Dari PHP7 Windows, php membangun akhirnya mampu menggunakan full 64 bit bilangan bulat: Ukuran integer adalah platform-dependent, meskipun nilai maksimum sekitar dua miliar adalah nilai biasa (yang's 32 bit signed). 64-bit platform biasanya memiliki nilai maksimum sekitar 9E18, kecuali pada Windows sebelum PHP 7, di mana ia selalu 32 bit.