Bit İşlemleri
Bu bölümde mikro işlemcinin en küçük bilgi birimi olan bitlerle yapılan işlemleri tanıyacağız. Bit işlemleri mikro işlemcinin en temel olanaklarındandır.
Bu işlemler hem alt düzey programcılık açısından bilinmelidir, hem de parametre olarak bayrak alan işlevler için gereklidir. Bayrak alan işlevlere özellikle C kütüphanelerinden uyarlanmış olan D kütüphanelerinde rastlanabilir.
Verinin en alt düzeyde gerçekleştirilmesi
D gibi bir programlama dili aslında bir soyutlamadır. Program içinde tanımladığımız Öğrenci
gibi bir kullanıcı türünün bilgisayarın iç yapısı ile doğrudan bir ilgisi yoktur. Programlama dillerinin amaçlarından birisi, donanımın anladığı dil ile insanın anladığı dil arasında aracılık yapmaktır.
Bu yüzden her ne kadar D dilini kullanırken donanımla ilgili kavramlarla ilgilenmek gerekmese de, üst düzey kavramların en alt düzeyde elektronik devre elemanlarına nasıl bağlı olduklarını anlamak önemlidir. Bu konularda başka kaynaklarda çok miktarda bilgi bulabileceğinizi bildiğim için bu başlığı olabildiğince kısa tutacağım.
Transistör
Modern elektronik aletlerin işlem yapma yetenekleri büyük ölçüde transistör denen elektronik devre elemanı ile sağlanır. Transistörün bir özelliği, devrenin başka tarafındaki sinyallerle kontrol edilebilmesidir. Bir anlamda elektronik devrenin kendi durumundan haberinin olmasını ve kendi durumunu değiştirebilmesini sağlar.
Transistörler hem mikro işlemcinin içinde hem de bilgisayarın ana belleğinde çok büyük sayılarda bulunurlar. Programlama dili aracılığıyla ifade ettiğimiz işlemleri ve verileri en alt düzeyde gerçekleştiren elemanlardır.
Bit
Bilgisayarlarda en küçük bilgi birimi bittir. Bit en alt düzeyde bir kaç tane transistörün belirli bir düzende bir araya getirilmesi ile gerçekleştirilir ve veri olarak iki farklı değerden birisini depolayabilir: 0 veya 1. Depoladığı veriyi tekrar değiştirilene kadar veya enerji kaynağı kesilene kadar korur.
Bilgisayarlar veriye bit düzeyinde doğrudan erişim sağlamazlar. Bunun nedeni, her bitin adreslenebilmesinin bilgisayarın karmaşıklığını ve maliyetini çok arttıracak olması ve tek bitlik kavramların desteklenmeye değmeyecek kadar nadir olmalarıdır.
Bayt
Bayt, birbirleriyle ilişkilendirilen 8 bitin bir araya gelmesinden oluşur. Bilgisayarlarda adreslenebilen, yani ayrı ayrı erişilebilen en küçük veri bayttır. Bellekten tek seferde en az bir bayt veri okunabilir ve belleğe en az bir bayt veri yazılabilir.
Bu yüzden, yalnızca false
ve true
diye iki farklı değer alan ve aslında tek bitlik bilgi taşıyan bool
türü bile 1 bayt olarak gerçekleştirilir. Bunu bool.sizeof
değerine bakarak kolayca görebiliriz:
writeln(bool.stringof, ' ', bool.sizeof, " bayttır");
bool 1 bayttır
Yazmaç
Mikro işlemcinin kendi içinde bulunan depolama ve işlem birimleri yazmaçlardır. Yazmaçlar oldukça kısıtlı ama çok hızlı işlemler sunarlar.
Yazmaçlar her işlemcinin bit genişliğine bağlı olan sayıda bayttan oluşurlar. Örneğin, 32 bitlik işlemcilerde yazmaçlar 4 bayttan, 64 bitlik işlemcilerde de 8 bayttan oluşur. Yazmaç büyüklüğü hem mikro işlemcinin etkin olarak işleyebildiği bilgi miktarını hem de en fazla ne kadar bellek adresleyebildiğini belirler.
Programlama dili aracılığıyla gerçekleştirilen her iş eninde sonunda bir veya daha fazla yazmaç tarafından halledilir.
İkili sayı sistemi
Günlük hayatta kullandığımız onlu sayı sisteminde 10 rakam vardır: 0123456789. Bilgisayar donanımlarında kullanılan ikili sayı sisteminde ise iki rakam vardır: 0 ve 1. Bu, bitin iki değer alabilmesinden gelir. Bitler örneğin üç farklı değer alabilseler, bilgisayarlar üçlü sayı sistemini kullanırlardı.
Günlük hayatta kullandığımız sayıların basamakları birler, onlar, yüzler, binler, vs. diye artarak adlandırılır. Örneğin, 1023 gibi bir sayı şöyle ifade edilebilir:
1023 == 1 adet 1000, 0 adet 100, 2 adet 10, ve 3 adet 1
Dikkat ederseniz, sola doğru ilerlendiğinde her basamağın değeri 10 kat artmaktadır: 1, 10, 100, 1000, vs.
Aynı tanımı ikili sayı sistemine taşıyınca, ikili sistemde yazılmış olan sayıların basamaklarının da birler, ikiler, dörtler, sekizler, vs. şeklinde artması gerektiğini görürüz. Yani sola doğru ilerlendiğinde her basamağın değeri 2 kat artmalıdır: 1, 2, 4, 8, vs. Örneğin, 1011 gibi bir ikili sayı şöyle ifade edilebilir:
1011 == 1 adet 8, 0 adet 4, 1 adet 2, ve 1 adet 1
Basamaklar numaralanırken, en sağdaki basamağa (en düşük değerli olan basamağa) 0 numaralı basamak denir. Buna göre, ikili sayı sisteminde yazılmış olan 32 bitlik işaretsiz bir değerin bütün basamaklarını ve basamak değerlerini şöyle gösterebiliriz:
Basamak Numarası | Değeri |
---|---|
31 | 2,147,483,648 |
30 | 1,073,741,824 |
29 | 536,870,912 |
28 | 268,435,456 |
27 | 134,217,728 |
26 | 67,108,864 |
25 | 33,554,432 |
24 | 16,777,216 |
23 | 8,388,608 |
22 | 4,194,304 |
21 | 2,097,152 |
20 | 1,048,576 |
19 | 524,288 |
18 | 262,144 |
17 | 131,072 |
16 | 65,536 |
15 | 32,768 |
14 | 16,384 |
13 | 8,192 |
12 | 4,096 |
11 | 2,048 |
10 | 1,024 |
9 | 512 |
8 | 256 |
7 | 128 |
6 | 64 |
5 | 32 |
4 | 16 |
3 | 8 |
2 | 4 |
1 | 2 |
0 | 1 |
Yüksek değerli bitlere üst bit, düşük değerli bitlere alt bit denir.
İkili sistemde yazılan hazır değerlerin 0b
ile başladıklarını Hazır Değerler bölümünde görmüştük. İkili sistemde değerler yazarak bu tabloya nasıl uyduklarına bakabiliriz. Okumayı kolaylaştırmak için alt çizgi karakterlerinden de yararlanarak:
import std.stdio; void main() { // 1073741824 4 1 // ↓ ↓ ↓ int sayı = 0b_01000000_00000000_00000000_00000101; writeln(sayı); }
Çıktısı:
1073741829
Dikkat ederseniz, o hazır değerin içinde rakamı 1 olan yalnızca 3 adet basamak vardır. Yazdırılan değerin bu basamakların yukarıdaki tablodaki değerlerinin toplamı olduğunu görüyoruz: 1073741824 + 4 + 1 == 1073741829.
İşaretli türlerin işaret biti
En üst bit işaretli türlerde sayının artı veya eksi olduğunu bildirmek için kullanılır:
int sayı = 0b_10000000_00000000_00000000_00000000;
writeln(sayı);
-2147483648
En üst bitin diğerlerinden bağımsız olduğunu düşünmeyin. Örneğin, yukarıdaki sayı diğer bitlerinin 0 olmalarına bakarak -0 değeri olarak düşünülmemelidir (zaten tamsayılarda -0 diye bir değer yoktur). Bunun ayrıntısına burada girmeyeceğim ve bunun D'nin de kullandığı ikiye tümleyen sayı gösterimi ile ilgili olduğunu söylemekle yetineceğim.
Burada önemli olan, yukarıdaki tabloda gösterilen en yüksek 2,147,483,648 değerinin yalnızca işaretsiz türlerde geçerli olduğunu bilmenizdir. Aynı deneyi uint
ile yaptığımızda tablodaki değeri görürüz:
uint sayı = 0b_10000000_00000000_00000000_00000000;
writeln(sayı);
2147483648
Bu yüzden, aksine bir neden olmadığı sürece aşağıda gösterilenler gibi bit işlemlerinde her zaman için işaretsiz türler kullanılır: ubyte
, ushort
, uint
, ve ulong
.
On altılı sayı sistemi
Yukarıdaki hazır değerlerden de görülebileceği gibi, ikili sayı sistemi okunaklı değildir. Hem çok yer kaplar hem de yalnızca 0 ve 1'lerden oluştuğu için okunması ve anlaşılması zordur. Bu yüzden, daha kullanışlı olan on altılı sayı sistemi yaygınlaşmıştır.
On altılı sayı sisteminde toplam 16 rakam vardır. Alfabelerde 10'dan fazla rakam bulunmadığı için Latin alfabesinden de 6 harf alınmış ve bu sistemin rakamları olarak 0123456789abcdef kabul edilmiştir. O sıralamadan bekleneceği gibi; a, b, c, d, e, ve f harfleri sırasıyla 10, 11, 12, 13, 14, ve 15 değerlerindedir. abcdef harfleri yerine isteğe bağlı olarak ABCDEF harfleri de kullanılabilir.
Yukarıdaki sayı sistemlerine benzer biçimde, bu sistemde sola doğru ilerlendiğinde her basamağın değeri 16 kat artar: 1, 16, 256, 4096, vs. Örneğin, on altılı sistemdeki 8 basamaklı bir sayının basamak değerleri şöyledir:
Basamak Numarası | Değeri |
---|---|
7 | 268,435,456 |
6 | 16,777,216 |
5 | 1,048,576 |
4 | 65,536 |
3 | 4,096 |
2 | 256 |
1 | 16 |
0 | 1 |
On altılı hazır değerlerin 0x
ile yazıldıklarını hatırlayarak bir deneme:
// 1048576 4096 1 // ↓ ↓ ↓ uint sayı = 0x_0030_a00f; writeln(sayı);
3186703
Bunun nedenini sayı içindeki sıfır olmayan basamakların katkılarına bakarak anlayabiliriz: 3 adet 1048576, a adet 4096, ve f adet 1. a'nın 10 ve f'nin 15 olduklarını hatırlayarak hesaplarsak: 3145728 + 40960 + 15 == 3186703.
On altılı ve ikili sistemde yazılan sayılar kolayca birbirlerine dönüştürülebilirler. On altılı sistemdeki bir sayıyı ikili sisteme dönüştürmek için, sayının her basamağı ikili sistemde dört basamak olarak yazılır. Birbirlerine karşılık gelen değerler şöyledir:
On altılı | İkili | Onlu |
---|---|---|
0 | 0000 | 0 |
1 | 0001 | 1 |
2 | 0010 | 2 |
3 | 0011 | 3 |
4 | 0100 | 4 |
5 | 0101 | 5 |
6 | 0110 | 6 |
7 | 0111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
a | 1010 | 10 |
b | 1011 | 11 |
c | 1100 | 12 |
d | 1101 | 13 |
e | 1110 | 14 |
f | 1111 | 15 |
Örneğin, yukarıda kullandığımız 0x0030a00f on altılı değerini ikili olarak şöyle yazabiliriz:
// on altılı: 0 0 3 0 a 0 0 f uint ikili = 0b_0000_0000_0011_0000_1010_0000_0000_1111;
İkili sistemden on altılı sisteme dönüştürmek için de ikili sayının her dört basamağı on altılı sistemde tek basamak olarak yazılır. Yukarıda ilk kullandığımız ikili değer için:
// ikili: 0100 0000 0000 0000 0000 0000 0000 0101 uint on_altılı = 0x___4____0____0____0____0____0____0____5;
Bit işlemleri
Değerlerin bitlerle nasıl ifade edildiklerini ve ikili veya on altılı olarak nasıl yazıldıklarını gördük. Şimdi değerleri bit düzeyinde değiştiren işlemlere geçebiliriz.
Her ne kadar bit düzeyindeki işlemlerden bahsediyor olsak da, bitlere doğrudan erişilemediğinden bu işlemler en az 8 biti birden etkilemek zorundadırlar. Örneğin, ubyte
türündeki bir ifadenin 8 bitinin hepsi de, ama ayrı ayrı olarak bit işlemine dahil edilir.
Ben üst bitin özel anlamı nedeniyle işaretli türleri gözardı edeceğim ve bu örneklerde uint
türünü kullanacağım. Siz buradaki işlemleri ubyte
, ushort
, ve ulong
türleriyle; ve işaret bitinin önemini hatırlamak şartıyla byte
, short
, int
, ve long
türleriyle de deneyebilirsiniz.
Önce aşağıdaki işlemleri açıklamada yardımcı olacak bir işlev yazalım. Kendisine verilen sayıyı ikili, on altılı, ve onlu sistemde göstersin:
import std.stdio; void göster(uint sayı) { writefln(" %032b %08x %10s", sayı, sayı, sayı); } void main() { göster(123456789); }
Sırasıyla ikili, on altılı, ve onlu:
00000111010110111100110100010101 075bcd15 123456789
Tersini alma işleci ~
Bu işleç önüne yazıldığı ifadenin bitleri ters olanını üretir. 1 olan bitler 0, 0 olanlar 1 olur:
uint değer = 123456789; göster(değer); writeln("~ --------------------------------"); göster(~değer);
Bu işlecin etkisi ikili gösteriminde çok kolay anlaşılıyor. Her bit tersine dönmüştür:
00000111010110111100110100010101 075bcd15 123456789 ~ -------------------------------- 11111000101001000011001011101010 f8a432ea 4171510506
Bu işlecin bit düzeyindeki etkisini şöyle özetleyebiliriz:
~0 → 1 ~1 → 0
Ve işleci &
İki ifadenin arasına yazılır. İki ifadenin aynı numaralı bitlerine sırayla bakılır. Sonuç olarak her iki ifadede de 1 olan bitler için 1 değeri, diğerleri için 0 değeri üretilir.
uint soldaki = 123456789; uint sağdaki = 987654321; göster(soldaki); göster(sağdaki); writeln("& --------------------------------"); göster(soldaki & sağdaki);
Mikro işlemci bu işlemde her iki ifadenin 31, 30, 29, vs. numaralı bitlerini ayrı ayrı kullanır.
Çıktıda önce soldaki ifadeyi, sonra da sağdaki ifadeyi görüyoruz. Kesikli çizginin altında da bit işleminin sonucu yazdırılıyor:
00000111010110111100110100010101 075bcd15 123456789 00111010110111100110100010110001 3ade68b1 987654321 & -------------------------------- 00000010010110100100100000010001 025a4811 39471121
Dikkat ederseniz, kesikli çizginin altına yazdırdığım sonuç değerde 1 olan bitler çizginin üstündeki her iki ifadede de 1 değerine sahip olan bitlerdir.
Bu işleç bu yüzden ve işleci olarak isimlendirilmiştir: soldaki ve sağdaki bit 1 olduğunda 1 değerini üretir. Bunu bir tablo ile gösterebiliriz. İki bitin 0 ve 1 oldukları dört farklı durumda ancak iki bitin de 1 oldukları durum 1 sonucunu verir:
0 & 0 → 0 0 & 1 → 0 1 & 0 → 0 1 & 1 → 1
Gözlemler:
- Bir taraf 0 ise diğer taraftan bağımsız olarak sonuç 0'dır; 0 ile "ve"lemek, sıfırlamak anlamına gelir.
- Bir taraf 1 ise sonuç diğerinin değeridir; 1 ile "ve"lemek etkisizdir.
Veya işleci |
İki ifadenin arasına yazılır. İki ifadenin aynı numaralı bitlerine sırayla bakılır. Her iki ifadede de 0 olan bitlere karşılık 0 değeri üretilir; diğerlerinin sonucu 1 olur:
uint soldaki = 123456789; uint sağdaki = 987654321; göster(soldaki); göster(sağdaki); writeln("| --------------------------------"); göster(soldaki | sağdaki);
00000111010110111100110100010101 075bcd15 123456789 00111010110111100110100010110001 3ade68b1 987654321 | -------------------------------- 00111111110111111110110110110101 3fdfedb5 1071639989
Dikkat ederseniz, sonuçta 0 olan bitler her iki ifadede de 0 olan bitlerdir. Bitin soldaki veya sağdaki ifadede 1 olması, sonucun da 1 olması için yeterlidir:
0 | 0 → 0 0 | 1 → 1 1 | 0 → 1 1 | 1 → 1
Gözlemler:
- Bir taraf 0 ise sonuç diğerinin değeridir; 0 ile "veya"lamak etkisizdir.
- Bir taraf 1 ise diğer taraftan bağımsız olarak sonuç 1'dir; 1 ile "veya"lamak 1 yapmak anlamına gelir.
Ya da işleci ^
İki ifadenin arasına yazılır. İki ifadenin aynı numaralı bitlerine sırayla bakılır. İki ifadede farklı olan bitlere karşılık 1 değeri üretilir; diğerlerinin sonucu 0 olur:
uint soldaki = 123456789; uint sağdaki = 987654321; göster(soldaki); göster(sağdaki); writeln("^ --------------------------------"); göster(soldaki ^ sağdaki);
00000111010110111100110100010101 075bcd15 123456789 00111010110111100110100010110001 3ade68b1 987654321 ^ -------------------------------- 00111101100001011010010110100100 3d85a5a4 1032168868
Dikkat ederseniz, sonuçta 1 olan bitler soldaki ve sağdaki ifadelerde farklı olan bitlerdir. İkisinde de 0 veya ikisinde de 1 olan bitlere karşılık 0 üretilir.
0 ^ 0 → 0 0 ^ 1 → 1 1 ^ 0 → 1 1 ^ 1 → 0
Gözlem:
- Kendisiyle "ya da"lamak sıfırlamak anlamına gelir
Değeri ne olursa olsun, aynı değişkenin kendisiyle "ya da"lanması 0 sonucunu üretir:
uint değer = 123456789;
göster(değer ^ değer);
00000000000000000000000000000000 00000000 0
Sağa kaydırma işleci >>
İfadenin değerini oluşturan bitleri belirtilen sayıda basamak kadar sağa kaydırır. Kaydırılacak yerleri olmayan en sağdaki bitler düşerler ve değerleri kaybedilir. Sol taraftan yeni gelen bitler işaretsiz türlerde 0 olur.
Bu örnek bitleri 2 basamak kaydırıyor:
uint değer = 123456789; göster(değer); göster(değer >> 2);
Hem sağdan kaybedilecek olan bitleri hem de soldan yeni gelecek olan bitleri işaretli olarak gösteriyorum:
00000111010110111100110100010101 075bcd15 123456789 00000001110101101111001101000101 01d6f345 30864197
Dikkat ederseniz, alt satırdaki bitler üst satırdaki bitlerin iki bit sağa kaydırılması ile elde edilmiştir.
Bitler sağa kaydırılırken sol tarafa yeni gelenlerin 0 olduklarını gördünüz. Bu, işaretsiz türlerde böyledir. İşaretli türlerde ise işaret genişletilmesi (sign extension) denen bir yöntem uygulanır ve sayının en soldaki biti ne ise soldan hep o bitin değerinde bitler gelir.
Bu etkiyi göstermek için int
türünde ve özellikle üst biti 1 olan bir değer seçelim:
int değer = 0x80010300;
göster(değer);
göster(değer >> 3);
Asıl sayıda üst bit 1 olduğu için yeni gelen bitler de 1 olur:
10000000000000010000001100000000 80010300 2147549952 11110000000000000010000001100000 f0002060 4026540128
Üst bitin 0 olduğu bir değerde yeni gelen bitler de 0 olur:
int değer = 0x40010300;
göster(değer);
göster(değer >> 3);
01000000000000010000001100000000 40010300 1073808128 00001000000000000010000001100000 08002060 134226016
İşaretsiz sağa kaydırma işleci >>>
Bu işleç sağa kaydırma işlecine benzer biçimde çalışır. Tek farkı işaret genişletilmesinin uygulanmamasıdır. Türden ve en soldaki bitten bağımsız olarak soldan her zaman için 0 gelir:
int değer = 0x80010300; göster(değer); göster(değer >>> 3);
10000000000000010000001100000000 80010300 2147549952 00010000000000000010000001100000 10002060 268443744
Sola kaydırma işleci <<
Sağa kaydırma işlecinin tersi olarak bitleri belirtilen basamak kadar sola kaydırır:
uint değer = 123456789; göster(değer); göster(değer << 4);
En soldaki bit değerleri kaybedilir ve sağ taraftan 0 değerli bitler gelir:
00000111010110111100110100010101 075bcd15 123456789 01110101101111001101000101010000 75bcd150 1975308624
Atamalı bit işleçleri
Yukarıdaki işleçlerin ikili olanlarının atamalı karşılıkları da vardır: &=
, |=
, ^=
, >>=
, >>>=
, ve <<=
. Tamsayılar ve Aritmetik İşlemler bölümünde gördüğümüz atamalı aritmetik işleçlerine benzer biçimde, bunlar işlemi gerçekleştirdikten sonra sonucu soldaki ifadeye atarlar.
Örnek olarak &=
işlecini kullanalım:
değer = değer & 123;
değer &= 123; // üsttekiyle aynı şey
Anlamları
Bu işleçlerin bit düzeyinde nasıl işledikleri işlemlerin hangi anlamlarda görülmeleri gerektiği konusunda yeterli olmayabilir. Burada bu anlamlara dikkat çekmek istiyorum.
|
işleci birleşim kümesidir
İki ifadenin 1 olan bitlerinin birleşimini verir. Uç bir örnek olarak, bitleri birer basamak atlayarak 1 olan ve birbirlerini tutmayan iki ifadenin birleşimi, sonucun bütün bitlerinin 1 olmasını sağlar:
uint soldaki = 0xaaaaaaaa; uint sağdaki = 0x55555555; göster(soldaki); göster(sağdaki); writeln("| --------------------------------"); göster(soldaki | sağdaki);
10101010101010101010101010101010 aaaaaaaa 2863311530 01010101010101010101010101010101 55555555 1431655765 | -------------------------------- 11111111111111111111111111111111 ffffffff 4294967295
&
işleci kesişim kümesidir
İki ifadenin 1 olan bitlerinin kesişimini verir. Uç bir örnek olarak, yukarıdaki iki ifadenin 1 olan hiçbir biti diğerini tutmadığı için, kesişimlerinin bütün bitleri 0'dır:
uint soldaki = 0xaaaaaaaa; uint sağdaki = 0x55555555; göster(soldaki); göster(sağdaki); writeln("& --------------------------------"); göster(soldaki & sağdaki);
10101010101010101010101010101010 aaaaaaaa 2863311530 01010101010101010101010101010101 55555555 1431655765 & -------------------------------- 00000000000000000000000000000000 00000000 0
|=
işleci belirli bitleri 1 yapar
İfadelerden bir taraftakini asıl değişken olarak düşünürsek, diğer ifadeyi de 1 yapılacak olan bitleri seçen ifade olarak görebiliriz:
uint ifade = 0x00ff00ff; uint birYapılacakBitler = 0x10001000; write("önce :"); göster(ifade); write("1 olacaklar:"); göster(birYapılacakBitler); ifade |= birYapılacakBitler; write("sonra :"); göster(ifade);
Etkilenen bitlerin önceki ve sonraki durumlarını işaretli olarak gösterdim:
önce : 00000000111111110000000011111111 00ff00ff 16711935 1 olacaklar: 00010000000000000001000000000000 10001000 268439552 sonra : 00010000111111110001000011111111 10ff10ff 285151487
birYapılacakBitler
değeri, bir anlamda hangi bitlerin 1 yapılacakları bilgisini taşımış ve asıl ifadenin o bitlerini 1 yapmış ve diğerlerine dokunmamıştır.
&=
işleci belirli bitleri siler
İfadelerden bir taraftakini asıl değişken olarak düşünürsek, diğer ifadeyi de silinecek olan bitleri seçen ifade olarak görebiliriz:
uint ifade = 0x00ff00ff; uint sıfırYapılacakBitler = 0xffefffef; write("önce :"); göster(ifade); write("silinecekler:"); göster(sıfırYapılacakBitler); ifade &= sıfırYapılacakBitler; write("sonra :"); göster(ifade);
Etkilenen bitlerin önceki ve sonraki durumlarını yine işaretli olarak gösteriyorum:
önce : 00000000111111110000000011111111 00ff00ff 16711935 silinecekler: 11111111111011111111111111101111 ffefffef 4293918703 sonra : 00000000111011110000000011101111 00ef00ef 15663343
sıfırYapılacakBitler
değeri, hangi bitlerin sıfırlanacakları bilgisini taşımış ve asıl ifadenin o bitlerini sıfırlamıştır.
&
işleci belirli bir bitin 1 olup olmadığını sorgular
Eğer ifadelerden birisinin tek bir biti 1 ise diğer ifadede o bitin 1 olup olmadığı sorgulanabilir:
uint ifade = 123456789; uint sorgulananBit = 0x00010000; göster(ifade); göster(sorgulananBit); writeln(ifade & sorgulananBit ? "evet, 1" : "1 değil");
Asıl ifadenin hangi bitinin sorgulandığını işaretli olarak gösteriyorum:
00000111010110111100110100010101 075bcd15 123456789
00000000000000010000000000000000 00010000 65536
evet, 1
Başka bir bitini sorgulayalım:
uint sorgulananBit = 0x00001000;
00000111010110111100110100010101 075bcd15 123456789
00000000000000000001000000000000 00001000 4096
1 değil
Sorgulama ifadesinde birden fazla 1 kullanarak o bitlerin hepsinin birden asıl ifadede 1 olup olmadıkları da sorgulanabilir.
Sağa kaydırmak ikiye bölmektir
Sağa bir bit kaydırmak değerin yarıya inmesine neden olur. Bunu yukarıdaki basamak değerleri tablosunda görebilirsiniz: bir sağdaki bit her zaman için soldakinin yarısı değerdedir.
Sağa birden fazla sayıda kaydırmak o kadar sayıda yarıya bölmek anlamına gelir. Örneğin 3 bit kaydırmak, 3 kere 2'ye bölmek, yani sonuçta 8'e bölmek anlamına gelir:
uint değer = 8000;
writeln(değer >> 3);
1000
Ayrıntısına girmediğim ikiye tümleyen sisteminde sağa kaydırmak işaretli türlerde de ikiye bölmektir:
int değer = -8000;
writeln(değer >> 3);
-1000
Sola kaydırmak iki katını almaktır
Basamaklar tablosundaki her bitin, bir sağındakinin iki katı olması nedeniyle, bir bit sola kaydırmak 2 ile çarpmak anlamına gelir:
uint değer = 10;
writeln(değer << 5);
Beş kere 2 ile çarpmak 32 ile çarpmanın eşdeğeridir:
320
Bazı kullanımları
Bayraklar
Bayraklar birbirlerinden bağımsız olarak bir arada tutulan tek bitlik verilerdir. Tek bitlik oldukları için var/yok, olsun/olmasın, geçerli/geçersiz gibi iki değerli kavramları ifade ederler.
Her ne kadar böyle tek bitlik bilgilerin yaygın olmadıklarını söylemiş olsam da bazen bir arada kullanılırlar. Bayraklar özellikle C kütüphanelerinde yaygındır. C'den uyarlanan D kütüphanelerinde bulunmaları da beklenebilir.
Bayraklar bir enum
türünün birbirleriyle örtüşmeyen tek bitlik değerleri olarak tanımlanırlar.
Bir örnek olarak araba yarışıyla ilgili bir oyun programı düşünelim. Bu programın gerçekçiliği kullanıcı seçimlerine göre belirlensin:
- Benzin, kullanıma göre azalabilsin.
- Çarpışmalar hasar bırakabilsin.
- Lastikler kullanıma göre eskiyebilsin.
- Lastikler yolda iz bırakabilsin.
Oyun sırasında bunlardan hangilerinin etkin olacakları bayrak değerleriyle belirtilebilir:
enum Gerçekçilik {
benzinBiter = 1 << 0,
hasarOluşur = 1 << 1,
lastiklerEskir = 1 << 2,
lastikİzleriOluşur = 1 << 3
}
Dikkat ederseniz, o enum
değerlerinin hepsi de birbirleriyle çakışmayan tek bitten oluşmaktadırlar. Her değer 1'in farklı sayıda sola ötelenmesi ile elde edilmiştir. Bit değerlerinin şöyle olduklarını görebiliriz:
benzinBiter : ...0001 hasarOluşur : ...0010 lastiklerEskir : ...0100 lastikİzleriOluşur : ...1000
Hiçbir bit diğerlerininkiyle çakışmadığı için bu değerler |
ile birleştirilebilir ve hep birden tek bir değişkende bulundurulabilir. Örneğin, yalnızca lastiklerle ilgili ayarların etkin olmaları istendiğinde değer şöyle kurulabilir:
Gerçekçilik ayarlar = Gerçekçilik.lastiklerEskir
|
Gerçekçilik.lastikİzleriOluşur;
writefln("%b", ayarlar);
Bu iki bayrağın bitleri aynı değer içinde yan yana bulunurlar:
1100
Daha sonradan, programın asıl işleyişi sırasında bu bayrakların etkin olup olmadıkları &
işleci ile denetlenir:
if (ayarlar & Gerçekçilik.benzinBiter) { // ... benzinin azalmasıyla ilgili kodlar ... } if (ayarlar & Gerçekçilik.lastiklerEskir) { // ... lastiklerin eskimesiyle ilgili kodlar ... }
&
işlecinin sonucu, ancak belirtilen bayrak ayarlar
içinde de 1 ise 1 sonucunu verir.
if
koşuluyla kullanılabilmesinin bir nedeni de Tür Dönüşümleri bölümünden hatırlayacağınız gibi, sıfır olmayan değerlerin otomatik olarak true
'ya dönüşmesidir. &
işlecinin sonucu 0 olduğunda false
, farklı bir değer olduğunda da true
değerine dönüşür ve bayrağın etkin olup olmadığı böylece anlaşılmış olur.
Maskeleme
Bazı kütüphanelerde ve sistemlerde belirli bir tamsayı değer içine birden fazla bilgi yerleştirilmiş olabilir. Örneğin, 32 bitlik bir değerin üst 3 bitinin belirli bir anlamı ve alt 29 bitinin başka bir anlamı bulunabilir. Bu veriler maskeleme yöntemiyle birbirlerinden ayrılabilirler.
Bunun bir örneğini IPv4 adreslerinde görebiliriz. IPv4 adresleri ağ paketleri içinde 32 bitlik tek bir değer olarak bulunurlar. Bu 32 bitin 8'er bitlik 4 parçası günlük kullanımdan alışık olduğumuz noktalı adres gösterimi değerleridir. Örneğin, 192.168.1.2 gibi bir adres, 32 bit olarak 0xc0a80102 değeridir:
c0 == 12 * 16 + 0 = 192 a8 == 10 * 16 + 8 = 168 01 == 0 * 16 + 1 = 1 02 == 0 * 16 + 2 = 2
Maske, ilgilenilen veri ile örtüşen sayıda 1'lerden oluşur. Asıl değişken bu maske ile "ve"lendiğinde, yani &
işleci ile kullanıldığında verinin değerleri elde edilir. Örneğin, 0x000000ff gibi bir maske değeri ifadenin alt 8 bitini olduğu gibi korur, diğer bitlerini sıfırlar:
uint değer = 123456789; uint maske = 0x000000ff; write("değer:"); göster(değer); write("maske:"); göster(maske); write("sonuç:"); göster(değer & maske);
Maskenin seçerek koruduğu bitleri işaretli olarak gösteriyorum. Diğer bütün bitler sıfırlanmıştır:
değer: 00000111010110111100110100010101 075bcd15 123456789 maske: 00000000000000000000000011111111 000000ff 255 sonuç: 00000000000000000000000000010101 00000015 21
Bu yöntemi 0xc0a80102 IPv4 adresine ve en üst 8 biti seçecek bir maskeyle uyguladığımızda noktalı gösterimdeki ilk adres değerini elde ederiz:
uint değer = 0xc0a80102; uint maske = 0xff000000; write("değer:"); göster(değer); write("maske:"); göster(maske); write("sonuç:"); göster(değer & maske);
Maskenin üst bitleri 1 olduğundan, değerin de üst bitleri seçilmiş olur:
değer: 11000000101010000000000100000010 c0a80102 3232235778 maske: 11111111000000000000000000000000 ff000000 4278190080 sonuç: 11000000000000000000000000000000 c0000000 3221225472
Ancak, sonucun onlu gösterimi beklediğimiz gibi 192 değil, 3221225472 olmuştur. Bunun nedeni, maskelenen 8 bitin değerin en sağ tarafına kaydırılmalarının da gerekmesidir. O 8 biti 24 bit sağa kaydırırsak birlikte ifade ettikleri değeri elde ederiz:
uint değer = 0xc0a80102; uint maske = 0xff000000; write("değer:"); göster(değer); write("maske:"); göster(maske); write("sonuç:"); göster((değer & maske) >> 24);
değer: 11000000101010000000000100000010 c0a80102 3232235778 maske: 11111111000000000000000000000000 ff000000 4278190080 sonuç: 00000000000000000000000011000000 000000c0 192
Problemler
- Verilen IPv4 adresinin noktalı gösterimini döndüren bir işlev yazın:
string noktalıOlarak(uint ipAdresi) { // ... } unittest { assert(noktalıOlarak(0xc0a80102) == "192.168.1.2"); }
- Verilen 4 değeri 32 bitlik IPv4 adresine dönüştüren bir işlev yazın:
uint ipAdresi(ubyte bayt3, // en yüksek değerli bayt ubyte bayt2, ubyte bayt1, ubyte bayt0) { // en düşük değerli bayt // ... } unittest { assert(ipAdresi(192, 168, 1, 2) == 0xc0a80102); }
- Maske oluşturan bir işlev yazın. Belirtilen bit ile başlayan ve belirtilen uzunlukta olan maske oluştursun:
uint maskeYap(int düşükBit, int uzunluk) { // ... } unittest { assert(maskeYap(2, 5) == 0b_0000_0000_0000_0000_0000_0000_0111_1100); // ↑ // başlangıç biti 2 // ve 5 bitten oluşuyor }