Bit-shift operatorları (bit-shift) və onlar necə işləyirlər?

Mən boş zamanlarımda C öyrənməyə çalışdım və digər dillərdə (C #, Java, və s.) Eyni konsepsiyaya (və tez-tez eyni operatorlara) sahib ...

Merak ediyorum ki, bir çekirdek kaydırmasında bir az kayma ( << , >> , >>> ), hansı problemlər həll edə bilər və bükülmənin arxasında nədir? Başqa sözlə, bütün gözəlliklərində bitləri dəyişmək üçün mütləq başlayanların təlimatı.

1237
26 сент. John Rudy tərəfindən təyin olunan 26 sentyabr. 2008-09-26 22:47 '08 at 10:47 pm 2008-09-26 22:47
@ 8 cavab

Bit növbəsində işləyənlər adları dəqiqləşdirir. Onlar bit dəyişdirirlər. Burada müxtəlif növbənövr operatorlarına qısa (və ya daha az qısa) bir giriş.

Operatorlar

  • >> aritmetik (və ya imzalanmış) növbə operatorudur.
  • >>> sağdan məntiqi (və ya imzalanmamış) növbə operatorudur.
  • << bir sol svit operatorudur və həm məntiqi, həm də sayıq növlərin ehtiyaclarına cavab verir.

Bu operatorların hamısı tam dəyərlərə tətbiq edilə bilər ( int , long , ehtimal shortbyte və ya char ). Bəzi dillərdə, shift operatorlarını int dan daha kiçik olan hər hansı bir məlumat növünə tətbiq etmək, operandın ölçüsünü int avtomatik olaraq dəyişir.

Xahiş edirik unutmayın ki <<< bir operator deyil, çünki lazımsız olacaq. Həmçinin C və C ++ düzgün rolu operatorları arasında fərq yoxdur. Onlar yalnız >> operatoru təmin edir və sağ əvəz davranış imzalanmış növlər üçün müəyyən edilmiş tətbiqdir.


Sol şəffaflıq (<)

Integerlər bir sıra bit kimi yaddaşda saxlanılır. Məsələn, 32 bit int kimi saxlanılan 6 int :

 00000000 00000000 00000000 00000110 

Bu bit nümunəsinin bir mövqedə sola ( 6 << 1 ) 6 << 1 edilməsi ədədi 12 ilə nəticələnəcəkdir:

 00000000 00000000 00000000 00001100 

Gördüyünüz kimi, ədədlər bir mövqedən sola silinir və sağdakı son rəqəm sıfıra doldurulur. Həmçinin, sol vərdişin 2-nin səlahiyyətləri ilə çarpılmasına bərabər olduğunu nəzərə ala bilərsiniz. Beləliklə 6 << 1 6 * 2 ə bərabərdir və 6 << 3 6 * 8 bərabərdir. Yaxşı bir optimallaşdırıcı kompilyator mümkün olduğunda növbədə dəyişiklikləri əvəz edəcəkdir.

Qeyri-dairəvi dəyişiklik

Xahiş edirik unutmayın ki, bunlar dairəvi dəyişikliklər deyil. Bu dəyəri sol bir mövqeyə 3,758,096,384 << 1 ( 3,758,096,384 << 1 ):

 11100000 00000000 00000000 00000000 

nəticələr 3,221,225,472:

 11000000 00000000 00000000 00000000 

"Sondan" dəyişən rəqəm ləğv edilir. Bu sarılmamışdır.


Mantıksız doğru kayma (→>)

Sağ tərəfdəki məntiqi dəyişiklik sola doğru tərsdir. Bitləri sola çəkmək yerinə, sadəcə sağa doğru hərəkət edirlər. Məsələn, 12 sayını dəyişmək:

 00000000 00000000 00000000 00001100 

bir mövqe ( 12 >>> 1 ) hüququ üçün orijinal 6 qaytarır:

 00000000 00000000 00000000 00000110 

Beləliklə, sağa keçid 2 səlahiyyətləri ilə bölünməyə bərabər olduğunu görürük.

Lost bitləri itkin

Ancaq "kaybolan" bitleri kaydıramaz. Məsələn, bu nümunəni dəyişsək:

 00111000 00000000 00000000 00000110 

4 mövqeyi ( 939,524,102 << 4 ) 939,524,102 << 4 2 147 483 744:

 10000000 00000000 00000000 01100000 

sonra geri keçmək ( (939,524,102 << 4) >>> 4 ) 134 217 734:

 00001000 00000000 00000000 00000110 

Bit bitdikdən sonra orijinal dəyərini geri ala bilmirik.


Arithmetic right shift (→)

Aritmetik sağ köçürmə, mantıksal sağa keçid ilə bənzəyir, istisna olmaqla, sıfıra doldurmaq yerinə, ən əhəmiyyətli biti doldurur. Bu, ən əhəmiyyətli bitin müsbət və mənfi ədədlər arasında fərq qoyan bir əlamətidir. Ən əhəmiyyətli bit ilə doldurulmuş, sağa olan aritmetik keçid işarəni qoruyur.

Məsələn, əgər bu bit naxışını mənfi bir nöqtə olaraq şərh edərsənsə:

 10000000 00000000 00000000 01100000 

2,147,483,552 nömrə var. Aritmetik dəyişmə ilə (4,147,483,552 → 4) 4 mövqe ilə sağa hərəkət edəcəyik:

 11111000 00000000 00000000 00000110 

və ya sayı -134,217,722.

Beləliklə, mənfi nöqtələrimizin əlamətini mantıksal sağ köçürmədən çox aritmetik sağ köçürmə ilə saxladığımızı görürük. Və yenə də görürük ki, biz 2 səlahiyyətləri bölüşdürürük.

1554
26 сент. Derek Park tərəfindən 26 sentyabrda verilən cavab . 2008-09-26 23:46 '08 at 11:46 'da 2008-09-26 23:46

Gəlin bir baytımız var:

 0110110 

Bir sol bitdvig tətbiqi, biz almaq:

 1101100 

Ən sol sıfır baytdan silindi və baytın sağ ucuna yeni bir sıfır əlavə edildi.

Bit bitməz; onlar atılır. Bu, 1101100 növbəsini tərk etdikdən sonra sağa hərəkət etsəniz, eyni nəticə əldə etməyəcəksiniz.

N-nin bir sol səthi 2 N-ə çarpmağa bərabərdir.

N-nin sağ tərəfinə (əgər siz əlavələrdən birini istifadə edirsinizsə) 2 N- ə bölünməyə və sıfıra yuvarlaqlaşdırmağa bərabərdir.

Bitshifting, güclü bir çarpma və bölünmə üçün istifadə edilə bilər, bir güclə işlədiyiniz halda. Demək olar ki, bütün aşağı səviyyəli qrafik proqramları bitrate istifadə edir.

Məsələn, köhnə günlərdə oyunlar üçün 13 saatlıq rejimi (320x200 256 rəng) istifadə etdik. 13 saat rejimində, video yaddaş hər piksel başına sıralanır. Bu, bir pikselin yerini hesablamaq deməkdir, aşağıdakı matematikadan istifadə edəcəyiniz:

 memoryOffset = (row * 320) + column 

İndi həmin gün və yaşda sürət kritik idi, buna görə də bu əməliyyatı yerinə yetirmək üçün bit-əymələrdən istifadə edəcəyik.

Bununla belə, 320-nin iki qüvvəsi deyil, bununla yanaşı, bu məsələni həll etmək üçün, birlikdə əlavə olunan iki gücün nə olduğunu bilmək lazımdır: 320:

 (row * 320) = (row * 256) + (row * 64) 

İndi biz bunu sola keçə bilərik:

 (row * 320) = (row << 8) + (row << 6) 

Son nəticə üçün:

 memoryOffset = ((row << 8) + (row << 6)) + column 
border=0

İndi biz əvvəlki kimi eyni xərcləri alırıq, bahalı çarpma əməliyyatı yerinə biz iki bit sətirdən istifadə edirik ... x86-da bu kimi bir şey olardı (qeyd etdiyim toplantı etdi (redaktor qeyd: bir neçə səhv qeydi və 32 bit nümunə əlavə etdi)):

 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 

Cəmi: hər hansı bir qədim prosessorda 28 dövrə bu vaxtlar idi.

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] 

Eyni qədim prosessorda 12 dövr.

Bəli, 16 CPU dövrünü saxlamaq üçün çox çalışacağıq.

32 və ya 64 bit rejimində, hər iki versiya daha qısa və daha sürətli olur. Intel Skylake kimi müasir qeyri-standart prosessorlar (bax: http://agner.org/optimize/ ), çox sürətli donanım sürətinə (aşağı gecikmə və yüksək bant genişliyi) malikdirlər, belə ki, qazanc çox aşağıdır. AMD Bulldozer ailəsi bir qədər yavaş, xüsusilə 64 bitlik çarpma üçün. Intel və AMD Ryzen prosessorları üzrə iki növbədə gecikmədən bir qədər azdır, lakin vurma prosesindən daha çox təlimat (ötürülən azalma səbəb ola bilər):

 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. 

qarşı

 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. 

Derleyiciler bunu sizin üçün edərlər: gcc, c> necə istifadə etdiyinə baxın. return 320*row + col; .

Burada qeyd etmək istərdim ki, x86-da kiçik dəyişiklikləri yerinə yetirə və eyni zamanda add performans göstəriciləri ilə əlavə edə biləcək bir keçid və əlavə ( LEA ) təlimatı var . ARM daha güclüdür: hər hansı bir komanda bir operand qaldırıla bilər və ya sağa köçürülə bilər. Beləliklə, bildiyimiz kimi, bir gücün -2 olduğu, kompilyasiya zamanı sabitliyi ilə miqyaslama, çarpma prosesindən daha təsirli ola bilər.


OK, müasir dövrlərdə ... bir şey daha faydalıdır, artıq 16 bitlik tam ədəddə iki 8-bit dəyər saxlamaq üçün bit-ticarət istifadə etmək olardı. Məsələn, C # -də:

 // Byte1: 11110000 // Byte2: 00001111 Int16 value = ((byte)(Byte1 >> 8) | Byte2)); // value = 000011111110000; 

C ++-da, iki 8-bit elementli bir struct istifadə etdiyiniz təqdirdə kompilyatorlar sizin üçün bunu etməlidir, amma praktikada həmişə belə deyil.

186
26 сент. Cavab FlySwat 26 sep tərəfindən verilir . 2008-09-26 22:55 '08 saat 22:55 'da 2008-09-26 22:55

Bitsel əməliyyatlar, o cümlədən bit dəyişkənliyi, aşağı səviyyəli hardware və ya quraşdırılmış proqramlaşdırma üçün əsasdır. Bir cihaz və ya hətta ikili fayl formatları üçün dəqiqləşdirməni oxuyursanız, fərqli dəyərlər olan qeyri-byte-hizalı bit sahələrinə bölünmüş baytlar, sözlər və sözlər görəcəksiniz. Bu oxu / yazma bit sahələrinə daxil olmaq ən yaygın istifadə edir.

Grafik proqramlaşdırmada sadə real dünya nümunəsi 16 bit piksel aşağıdakı kimi təqdim olunur:

  bit | 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | Blue | Green | Red | 

Yaşıl dəyər almaq üçün aşağıdakıları yerinə yetirəcəksiniz:

  #define GREEN_MASK 0x7E0 #define GREEN_OFFSET 5 // Read green uint16_t green = (pixel  GREEN_MASK) >> GREEN_OFFSET; 

Şərhlər

Yalnız 5-də başlayan və 10-da (yəni 6 bit) bitən yalnız yaşıl dəyəri almaq üçün, bütün 16 bit pikselə qarşı tətbiq olunan bir (bit) maska ​​istifadə etməyiniz lazımdır, yalnız bizə maraqlı bitləri verəcəkdir.

 #define GREEN_MASK 0x7E0 

Müvafiq maska ​​0x7E0, ikili formatda olan 0000011111100000 (2016-a bərabərdir).

 uint16_t green = (pixel  GREEN_MASK) ...; 

 uint16_t green = (pixel  GREEN_MASK) >> GREEN_OFFSET; 

Maska tətbiq edildikdən sonra, MSB 11 bitdə olduğundan, həqiqətən, yalnız bir 11-bit saylı 16 bitlik bir nömrəni alacaqsınız. Yaşıl, həqiqətən, yalnız 6 bitdir, beləliklə, doğru şəffaflıq (11 - 6 = 5) istifadə edərək #define GREEN_OFFSET 5 , beləliklə, 5 #define GREEN_OFFSET 5 kimi istifadə edirik ( #define GREEN_OFFSET 5 ).

Sürətlə çarpma və 2 səlahiyyətləri ilə bölünmə üçün bit növbəsində istifadəsi də geniş yayılmışdır:

  i <<= x; // i *= 2^x; i >>= y; // i /= 2^y; 
91
27 сент. Cavab verilir robottobor 27 Sentyabr. 2008-09-27 01:22 '08 at 1:22 2008-09-27 01:22

Bit Maskeleme və Shift

Bit-shift tez-tez aşağı səviyyəli grafik proqramlaşdırma istifadə olunur. Məsələn, 32 bitli bir sözlə kodlaşdırılmış bir piksel rəng dəyəri.

  Pixel-Color Value in Hex: B9B9B900 Pixel-Color Value in Binary: 10111001 10111001 10111001 00000000 

Daha yaxşı başa düşmək üçün bölmələrin hansı rəng hissəsini təmsil etdiyini göstərən eyni ikili dəyər.

  Red Green Blue Alpha Pixel-Color Value in Binary: 10111001 10111001 10111001 00000000 

Məsələn, bu rəngin yaşıl dəyərini almaq istəyirik. Maskalanma və ofset ilə asanlıqla bu dəyəri əldə edə bilərik.

Bizim maska:

  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 

Mantıksal operator > yalnız maska ​​1-ə bərabər olan dəyərlərin saxlanılmasını təmin edir. Sonuncu şey bütün bu bitləri sağa 16 yerə (məntiqi sağ köç) dəyişməklə doğru tamsayı dəyərini əldə etməkdir.

  green_value = masked_color >>> 16 

Bəli, biz pikseldən yaşıl miqdarı təmsil edən bir tam ədədimiz var:

  Pixels-Green Value in Hex: 000000B9 Pixels-Green Value in Binary: 00000000 00000000 00000000 10111001 Pixels-Green Value in Decimal: 185 

Çox vaxt jpg , png , resim kimi formatları şifrələmək və ya kodlaşdırmaq üçün istifadə olunur.

45
31 марта '15 в 13:49 2015-03-31 13:49 Cavab Basti Funck tərəfindən 31 Mart 'da 13:49' də verilir 2015-03-31 13:49

Onlardan biri belədir: tətbiqin asılılığı (ANSI standartına uyğun olaraq):

 char x = -1; x >> 1; 

x artıq 127 (01111111) və ya başqa -1 (11111111) ola bilər.

Praktikada bu, adətən, sonuncu olur.

27
26 сент. Cavab 26 sentyabr tarixində verilir 2008-09-26 23:07 '08 at 23:07 2008-09-26 23:07

Yalnız məsləhətlər və tövsiyələr yazıram, testlərdə / imtahanlarda faydalı ola bilər.

  1. n = n*2 : n = n<<1
  2. n = n/2 : n = n>>1
  3. N 2 olub olmadığını yoxlayın (1,2,4,8, ...): yoxlayın !(n (n-1))
  4. n : n |= (1 << x) xth bitinin alınması
  5. X xətti olub-olmadığını yoxlayın: x == 0 (hətta)
  6. x ^ (1<<n) bit x: x ^ (1<<n)
11
12 окт. Ravi Prakaşın 12 oktyabrda verdiyi cavabı 2016-10-12 01:43 '16 'da 1:43' də 2016-10-12 01:43

Qeyd edək ki, Java tətbiqində, kaydırılmaq üçün lazım olan bitlərin sayı mənbənin ölçüsünə görə dəyişir.

Məsələn:

 (long) 4 >> 65 

2-ə bərabərdir. Bitləri sağa 65 dəfə dəyişə bilərsiniz ki, hər şey sıfırdır, amma əslində bu bərabərdir:

 (long) 4 >> (65 % 64) 

Bu, <<, → və → → üçün doğrudur. Bunu digər dillərdə sınamamışam.

8
28 авг. Patrick Monkelban tərəfindən verilmiş cavab 28 avqust . 2015-08-28 16:16 '15 'də 16:16' de 2015-08-28 16:16

Bir Windows platformasında yalnız PHP-nin 32 bitlik bir versiyasının mövcud olduğunu unutmayın.

Daha sonra, məsələn, << və ya → 31 bitdən çox keçdikdə nəticələr qeyri-dəqiqdir. Ümumiyyətlə, sıfırdan başqa orijinal sayı qaytarılacaq və bu çox mürəkkəb bir səhv ola bilər.

Əlbəttə, PHP'nin (unix) 64-bit versiyasını istifadə edirsinizsə, 63 bitdən çox keçməməlisiniz. Lakin, məsələn, MySQL 64-bit BIGINT istifadə edir, buna görə uyğunluq məsələləri olmamalıdır.

UPDATE: PHP7-dən Windows, php builds nəhayət tam 64 bit tamsayı istifadə edə bilər: Bütün iki ölçülü maksimum dəyəri adi dəyəri (bu 32 bit) baxmayaraq, bütün ölçüsü platforma asılıdır. 64 bit platformalar, adətən, 32 bit olan PHP 7-dən əvvəl Windows istisna olmaqla, adətən ən çox 9E18 dəyərinə malikdir.

-2
23 окт. Cavab lukyer 23 oktyabr . 2015-10-23 17:28 '15 'da 17:28' de, 2015-10-23 17:28