Bilgisayar ve Programlamaya Giriş · Ders Notu
Bilgisayarlar
Bilgisayar yalnız iki durumu ayırt eden milyarlarca küçük anahtardan oluşur. Buna karşın aynı makine hesap yapar, metin yazar, resim gösterir, müzik çalar. Bu not, aradaki boşluğun nasıl kapandığını anlatır. Önce sayıların bit dizileriyle nasıl yazıldığı ele alınır: tabanlar, negatif sayılar, taşma ve kayan noktalı sayılar. Ardından harflerin ASCII, Unicode ve UTF-8 ile nasıl sayıya çevrildiği gösterilir. Not, mantık kapılarından kurulan toplayıcılarla devam eder ve işlemcinin bellekteki bir programı getir–çöz–yürüt döngüsüyle adım adım nasıl çalıştırdığını izler. Son kesimler bilgisayar mimarisinin temel tasarım seçimlerini ve bir Java satırının makine komutlarına kadar inen yolunu konu alır.
Öğrenme çıktıları
- Sayı temsili
- Sayıları ikili, sekizli, onluk ve onaltılık tabanlar arasında çevirmek; sınırlı bit sayısının sonuçlarını (negatif sayılar, taşma, kayan nokta) açıklamak.
- Metin temsili
- ASCII, Unicode ve dizgi kavramlarını ayırt etmek; bir karakterin UTF-8 baytlarını elle bulmak.
- Program yürütme
- Depolanan program kavramını ve getir–çöz–yürüt döngüsünü açıklamak; küçük bir makine programını adım adım izlemek.
- Mimari
- CPU, bellek, yazmaç ve veri yolu ilişkisini yorumlamak; von Neumann ve Harvard mimarilerini, RISC ve CISC yaklaşımlarını karşılaştırmak.
1.1Bilgisayar ve bilgi
Bir hesap makinesi yalnız hesap yapar; bir müzik çalar yalnız müzik çalar. Bilgisayar ise aynı donanımla bunların hepsini ve çok daha fazlasını yapabilir. Bu esnekliğin iki kaynağı vardır. Birincisi, her türlü bilginin sayılara, sayıların da bitlere çevrilebilmesidir. İkincisi, makinenin ne yapacağını söyleyen komutların da birer sayı olarak bellekte durmasıdır. Bu kesim, bilgisayarın ana parçalarını ve bilginin bitlere dönüşme fikrini tanıtır.
1.1.1Bilgisayarın temel parçaları
Bir masaüstü bilgisayar, bir telefon ya da bir çamaşır makinesinin denetim kartı ilk bakışta çok farklı görünür. Yine de üçünde de aynı üç temel parça bulunur.
- Merkezi işlem birimi (CPU, central processing unit), kısaca işlemci, komutları yürütür. Toplama, karşılaştırma, bir değeri bir yerden başka bir yere taşıma gibi işleri o yapar.
- Bellek (memory), hem işlenecek verileri hem de işlemcinin yürüteceği komutları saklar. Bu notta “bellek” sözcüğü, aksi söylenmedikçe ana belleği (RAM) anlatır.
- Giriş ve çıkış birimleri (G/Ç), bilgisayarın dış dünyayla veri alışverişini sağlar. Klavye, fare, mikrofon ve sıcaklık sensörü giriş birimidir; ekran, yazıcı ve hoparlör çıkış birimidir.
Şekil 1.1, parçalar arasındaki veri akışını gösterir. Klavyeden basılan bir tuş, giriş birimi üzerinden CPU'ya ulaşır. CPU bu değeri belleğe yazabilir, daha sonra bellekten geri okuyabilir, üzerinde işlem yapabilir. Ortaya çıkan sonuç ekrana gönderilir. CPU ile bellek arasındaki iki ok, bu ikisinin sürekli konuştuğunu anlatır: işlemci çalıştığı sürece bellekten komut ve veri okur, belleğe sonuç yazar. CPU'nun kendi içindeki bölümler ve bellekle bağlantısı 1.7'de ayrıntılı işlenir.
Belleği, dosyaların kalıcı olarak saklandığı disk ile karıştırmak. Ana bellek (RAM) hızlıdır, ama elektrik kesilince içeriği silinir. Disk ya da SSD daha yavaştır, ama içeriğini korur. Bir program çalışmadan önce diskten belleğe yüklenir; işlemci yalnız bellekteki komutları yürütebilir.
1.1.2Bilgiden bitlere
Bilgisayarın içindeki her devre, temelde iki durumu ayırt eder: gerilim var ya da yok, anahtar açık ya da kapalı. Bu iki durum 1 ve 0 rakamlarıyla gösterilir. Tek bir 0 ya da 1'e bit (binary digit, ikili basamak) denir. Sekiz bitlik gruba bayt (byte) denir.
Tek bit yalnız iki durum anlatabilir. Bitler yan yana gelince olasılıklar katlanarak artar. İki bitle 00, 01, 10, 11 olmak üzere 4 farklı dizi yazılır; üç bitle 8, sekiz bitle 28 = 256 farklı dizi. Genel kural şudur: n bitle 2n farklı bit dizisi yazılabilir. Sayılar, harfler, resimdeki noktaların renkleri ve programın komutları bu dizilerden biriyle eşleştirilerek saklanır.
Kodlama (encoding), bir bilgi türünün öğelerini bit dizileriyle eşleştiren kurallar bütünüdür. Veri tipi (data type) ise bellekteki bir bit dizisinin hangi kodlamayla okunacağını belirler.
Bu tanımın önemli bir sonucu vardır: bit dizisi tek başına bir anlam taşımaz. 01000001 dizisi tam sayı olarak okunursa 65'tir; ASCII karakteri olarak okunursa “A” harfidir. Anlamı, diziyi okuyan programın seçtiği veri tipi verir.
01000001 bit dizisine çevrilir. Program bu sayıya 32 ekler ve sonuç 01100001 = 97 olur; ekranda “a” görünür.Şekil 1.2 bütün zinciri bir arada gösterir. Klavyede “A” tuşuna basılır. Kodlama kuralı bu harfi 65 sayısıyla eşleştirir. Bellekte 65, sekiz bitlik 01000001 dizisi olarak durur. Bir program bu sayıya 32 eklerse 01100001, yani 97 elde edilir. Ekran bu sayıyı yine aynı kodlamayla okur ve “a” harfini çizer. Bilgisayar harfi “küçültmeyi” bilmez; yalnız bir sayıya 32 ekler. Anlamı kodlama ve veri tipi kurar. Bu küçük örnek 1.4.2'de bit işlemleriyle yeniden ele alınır.
1.2Tam sayıların temsili
Bilgisayarın en temel işi sayılarla uğraşmaktır. Bu kesim, tam sayıların bitlerle nasıl yazıldığını anlatır. Önce gündelik onluk sistemin nasıl çalıştığı hatırlanır; aynı fikir ikili sisteme taşınır. Ardından negatif sayılar ve sınırlı bit sayısının doğurduğu taşma sorunu ele alınır.
1.2.1Konumsal sayı sistemleri ve taban
Onluk sistemde 345 yazıldığında üç rakamın her biri farklı bir değer taşır. 5 birler basamağında, 4 onlar basamağında, 3 yüzler basamağındadır:
345 = 3 · 100 + 4 · 10 + 5 · 1 = 3 · 102 + 4 · 101 + 5 · 100
Rakamın değeri, bulunduğu konuma göre değişir. Bu yüzden böyle sistemlere konumsal sayı sistemi denir. Her basamağın ağırlığı, bir sağındakinin 10 katıdır. Buradaki 10 sayısına sistemin tabanı (base, radix) denir.
Tabanı b olan konumsal sistemde 0'dan b − 1'e kadar b tane rakam kullanılır. Sağdan sola 0, 1, 2, … diye numaralanan basamaklardan i. basamağın ağırlığı bi'dir. Sayının değeri, her rakamın kendi ağırlığıyla çarpımlarının toplamıdır.
Bilgisayar biliminde dört taban sık kullanılır. Tablo 1.1, hepsinde aynı sayıyı, 13'ü gösterir. Hangi tabanda yazıldığı karışmasın diye sayının sağ altına tabanı küçük harfle eklenir: 11012, 158, 1310, D16.
| Sistem | Taban | Rakamlar | Örnek | Değeri |
|---|---|---|---|---|
| İkili | 2 | 0–1 | 11012 | 1 · 8 + 1 · 4 + 0 · 2 + 1 · 1 = 13 |
| Sekizli | 8 | 0–7 | 158 | 1 · 8 + 5 · 1 = 13 |
| Onluk | 10 | 0–9 | 1310 | 1 · 10 + 3 · 1 = 13 |
| Onaltılık | 16 | 0–9, A–F | D16 | 13 · 1 = 13 |
Onaltılık sistemde on altı rakam gerekir, ama gündelik yazıda yalnız on rakam vardır. Eksik altı rakam için A–F harfleri kullanılır: A = 10, B = 11, C = 12, D = 13, E = 14, F = 15. Programlama dillerinde taban çoğu zaman bir önekle belirtilir. Java'da 0b1101 ikili, 0xD onaltılık bir sabittir.
1.2.2Onluktan ikiliye, ikiliden onluğa
İkiliden onluğa çevirmek, tanımı doğrudan uygulamaktır. İkili sistemde basamak ağırlıkları sağdan sola 1, 2, 4, 8, 16, 32, 64, 128, … diye ikişer katına çıkar. Hangi basamakta 1 varsa o ağırlık toplama eklenir. Örneğin 1011012'de 1'ler 32, 8, 4 ve 1 basamaklarındadır: 32 + 8 + 4 + 1 = 45.
Onluktan ikiliye çevirmenin en düzenli yolu, sayıyı art arda 2'ye bölmektir. Her bölmenin kalanı (0 ya da 1) bir bittir. Bölüm 0 olunca durulur. Kalanlar sondan başa doğru okunur, çünkü ilk kalan en sağdaki, yani birler basamağıdır.
45 sayısını ikili tabana çevirelim.
| Bölünen | ÷ 2 bölüm | Kalan (bit) | Basamak ağırlığı |
|---|---|---|---|
| 45 | 22 | 1 | 1 |
| 22 | 11 | 0 | 2 |
| 11 | 5 | 1 | 4 |
| 5 | 2 | 1 | 8 |
| 2 | 1 | 0 | 16 |
| 1 | 0 | 1 | 32 |
Kalanlar alttan üste okunur: 4510 = 1011012. Denetim: 32 + 8 + 4 + 1 = 45.
Küçük sayılarda ikinci bir yol daha pratiktir: sayıdan sığan en büyük ikinin kuvvetini çıkarmak. 45'e sığan en büyük kuvvet 32'dir; kalan 13'tür. 13'e 8 sığar, kalan 5; 5'e 4 sığar, kalan 1; 1'e 1 sığar. Kullanılan kuvvetler 32, 8, 4, 1'dir ve bunların basamaklarına 1, ötekilere 0 yazılır: 101101.
Java'nın hazır metotları aynı dönüşümleri yapar. Aşağıdaki kod 45'i üç tabanda yazar, sonra ikili bir yazıyı yeniden sayıya çevirir. Son satır, 0b ve 0x önekleriyle yazılmış iki sabitin toplamını yazdırır; ikisi de 45 olduğu için sonuç 90'dır.
int n = 45;
System.out.println(Integer.toBinaryString(n));
System.out.println(Integer.toOctalString(n));
System.out.println(Integer.toHexString(n));
System.out.println(Integer.parseInt("101101", 2));
System.out.println(0b101101 + 0x2D);Çıktı:
101101
55
2d
45
90Java onaltılık rakamları küçük harfle yazdığı için 45 “2d” olarak görünür. 2D16 ile 2d16 aynı sayıdır.
1.2.3Sekizli ve onaltılık: bitleri gruplamak
İkili yazım bilgisayar için doğaldır, ama insan için uzun ve okunaksızdır. 32 bitlik bir sayıyı otuz iki rakamla yazmak ve okumak hataya açıktır. Sekizli ve onaltılık tabanlar, uzun ikili sayıları kısa yazmanın yoludur. Bu iki tabanın seçilmesinin nedeni basittir: 8 = 23 ve 16 = 24'tür.
Bu eşitlikler sayesinde dönüşüm için hesap yapmaya gerek kalmaz. Bitler sağdan başlayarak gruplanır: sekizli için üçerli, onaltılık için dörderli. Her grup tek bir rakama çevrilir. En soldaki grup eksik kalırsa soluna 0 eklenir; baştaki sıfırlar değeri değiştirmez.
Şekil 1.3'te 101101 önce üçerli gruplanır: 101 | 101. Her grup 5'tir ve sonuç 558 olur. Denetim: 5 · 8 + 5 = 45. Dörderli gruplamada sağdaki grup 1101'dir; soldaki yalnız iki bit kalır (10) ve başına iki 0 eklenerek 0010 yapılır. 0010 = 2 ve 1101 = 13 = D olduğundan sonuç 2D16'dır. Denetim: 2 · 16 + 13 = 45.
Ters yön de aynı kolaylıktadır. Onaltılık bir sayıyı ikiliye çevirmek için her rakam dört bitle yazılır. Örneğin A716'da A = 1010, 7 = 0111'dir; sonuç 101001112'dir. Onluk değeri 10 · 16 + 7 = 167'dir. Bu kolaylık yüzünden bellek adresleri, renk kodları ve karakter kodları çoğunlukla onaltılık yazılır. Bir bayt her zaman tam iki onaltılık rakama karşılık gelir.
156 sayısını ikili, sekizli ve onaltılık tabanda yazalım.
- İkili: 156'ya sığan kuvvetler 128, 16, 8 ve 4'tür (128 + 16 + 8 + 4 = 156). Bu basamaklara 1 yazılır: 100111002.
- Sekizli: Sağdan üçerli gruplar 100, 011 ve 10'dur. Son grup 010 yapılır. Gruplar 2, 3, 4 olduğundan sonuç 2348'dir. Denetim: 2 · 64 + 3 · 8 + 4 = 156.
- Onaltılık: Dörderli gruplar 1001 ve 1100'dür, yani 9 ve 12 (C). Sonuç 9C16'dır. Denetim: 9 · 16 + 12 = 156.
Tabanı yazılmamış bir sayıyı onluk okumak. “1101” bin yüz bir de olabilir, on üç de. Tabanı belirsiz bırakmamak için alt simge (11012) ya da önek (0b1101, 0x1101) kullanılır. Benzer biçimde 1016 on değil 16'dır; onaltılıkta on, A ile yazılır.
1.2.4Sınırlı bit sayısı ve işaretsiz sayılar
Kâğıt üzerinde bir sayının basamak sayısı sınırsızdır; gerektiği kadar basamak eklenir. Bilgisayarda durum farklıdır. İşlemcinin içindeki küçük ve hızlı saklama alanlarına yazmaç (register) denir ve her yazmacın bit sayısı sabittir: 8, 16, 32 ya da 64. Bellekteki her değişken de belli sayıda bit kaplar. Java'da byte 8, short 16, int 32, long 64 bittir.
Bitlerin hepsi sayının büyüklüğü için kullanılırsa işaretsiz (unsigned) sayı elde edilir. n bitle 0'dan 2n − 1'e kadar sayılar yazılabilir. 8 bitte en büyük sayı 111111112 = 255'tir. Negatif sayılar ise bu yöntemle yazılamaz. Onları yazmak için bitlerden birine “işaret” görevi vermek gerekir.
1.2.5Negatif sayılar ve ikiye tümleyen
Negatif sayıları yazmanın akla ilk gelen yolu, en soldaki biti işaret için ayırmaktır: 0 artı, 1 eksi. Bu yönteme işaret-büyüklük gösterimi denir ve iki kusuru vardır. Birincisi, sıfırın iki ayrı yazımı oluşur: 00000000 (+0) ve 10000000 (−0). İkincisi, toplama devresi işaretlere bakıp farklı durumlar için farklı işler yapmak zorunda kalır. Modern bilgisayarlar bu yüzden negatif tam sayıları çoğunlukla ikiye tümleyen (two's complement) gösterimle saklar.
n bitlik ikiye tümleyen gösterimde en soldaki bitin ağırlığı +2n−1 yerine −2n−1'dir; öteki bitlerin ağırlıkları değişmez. Bu yüzden en soldaki bit 1 ise sayı negatiftir ve bu bite işaret biti denir. Gösterilebilen aralık −2n−1 ile 2n−1 − 1 arasıdır; 8 bitte −128 … 127.
Tanım ilk okumada soyut görünebilir. Bir örnekle somutlaşır. 8 bitte 11111011 dizisinin değeri, en soldaki bitin ağırlığı −128 alınarak bulunur: −128 + 64 + 32 + 16 + 8 + 2 + 1 = −5. Aynı dizi işaretsiz okunsaydı 128 + 64 + 32 + 16 + 8 + 2 + 1 = 251 olurdu. İki okuma arasındaki fark tam 256'dır, yani 28. Bu ilişki her negatif sayı için geçerlidir: 8 bitte −k'nın bit dizisi, işaretsiz 256 − k'nın bit dizisiyle aynıdır.
Şekil 1.4, 4 bitlik bütün dizileri bir saat kadranı gibi dizer. Saat yönünde ilerlemek 1 eklemek demektir. 0000'dan başlanıp 1 eklendikçe 1, 2, …, 7'ye ulaşılır. 0111'e bir daha 1 eklenince 1000 elde edilir ve bu dizi −8'dir. Kadran böylece bir “uçurum” gösterir: en büyük pozitif sayının hemen ardından en küçük negatif sayı gelir. Saat yönünün tersine gidildiğinde ise 0000'dan 1111'e, yani −1'e geçilir. −1'in bütün bitlerinin 1 olması bu yüzden şaşırtıcı değildir.
İkiye tümleyenin üç önemli üstünlüğü vardır.
- Tek sıfır. Sıfırın yalnız bir yazımı vardır (0000); artı sıfır ve eksi sıfır ayrımı oluşmaz.
- Ortak aritmetik. Toplama devresi, pozitif ve negatif sayılarda aynı kuralları kullanır. Sayının işaretine bakmaz, bitleri sıradan ikili toplamayla toplar.
- Kolay olumsuzlama. Bir sayının negatifi iki basit adımla bulunur (1.2.6).
| Bit | İşaretli aralık | İşaretsiz aralık | Java tipi |
|---|---|---|---|
| 8 | −128 … 127 | 0 … 255 | byte |
| 16 | −32.768 … 32.767 | 0 … 65.535 | short |
| 32 | −2.147.483.648 … 2.147.483.647 | 0 … 4.294.967.295 | int |
| 64 | yaklaşık ±9,22 · 1018 | 0 … yaklaşık 1,84 · 1019 | long |
Aralığın simetrik olmadığına dikkat edilmelidir. 8 bitte −128 yazılabilir, ama +128 yazılamaz. Bunun nedeni, 256 dizinin birinin sıfıra ayrılmasıdır. Geriye kalan 255 dizi, 128 negatif ve 127 pozitif sayı arasında paylaşılır.
1.2.6−5 nasıl yazılır?
Bir sayının ikiye tümleyen gösterimdeki negatifini bulmak için iki adım yeterlidir:
- Bütün bitler ters çevrilir (0 ↔ 1). Ortaya çıkan diziye birler tümleyeni (one's complement) denir.
- Sonuca 1 eklenir.
8 bitte −5'i yazalım ve sonucu denetleyelim.
- +5'i yaz: 5 = 4 + 1 olduğundan +5 = 00000101.
- Bitleri ters çevir: 00000101 → 11111010. Bu dizi birler tümleyenidir ve henüz −5 değildir.
- 1 ekle: 11111010 + 1 = 11111011. En soldaki 1, sayının negatif olduğunu gösterir.
- Denetle: 5 + (−5) = 0 olmalıdır. 00000101 + 11111011 = 100000000 bulunur. Sonuç 9 bittir; 9. bit 8 bitlik yazmaca sığmaz ve atılır. Kalan 00000000 = 0'dır.
Şekil 1.5'teki denetim, ikiye tümleyenin asıl gücünü gösterir. Toplama devresi −5'in negatif olduğunu bilmez. Bitleri her zamanki gibi toplar, taşan biti atar ve doğru sonucu bulur. Negatif sayılar için ayrı bir devre gerekmez.
Aynı iki adım ters yönde de çalışır. Negatif bir dizinin büyüklüğünü bulmak için yine bitler çevrilir ve 1 eklenir. 11111011 → 00000100 → 00000101 = 5; demek ki dizi −5'tir. Java'da tilde (~) bütün bitleri çeviren işlemdir; ~5 + 1 bu yüzden −5'tir:
byte x = -5;
System.out.println(Integer.toBinaryString(x & 0xFF));
System.out.println(~5 + 1);
System.out.println(Byte.MIN_VALUE + " " + Byte.MAX_VALUE);Çıktı:
11111011
-5
-128 127İlk satırdaki x & 0xFF, byte değerinin yalnız son 8 bitini almak içindir. Böylece −5'in bellekteki dizisi 11111011 olarak görünür. Son satır, byte tipinin sınırlarını Tablo 1.2'deki gibi verir.
İkiye tümleyende yalnız bitleri çevirip 1 eklemeyi unutmak. 11111010 birler tümleyenidir ve ikiye tümleyen gösterimde −6'dır, −5 değil. Bitleri çevirdikten sonra mutlaka 1 eklenir: 11111011 = −5.
1.2.7Çıkarma aslında toplamadır: 5 − 9
İkiye tümleyenin bir başka sonucu, işlemcide ayrı bir çıkarma devresine gerek kalmamasıdır. Çıkarma şu eşitlikle toplamaya çevrilir:
a − b = a + (−b)
Önce b'nin ikiye tümleyeni bulunur, sonra a ile toplanır. Sunumdaki adımlar aşağıdaki sözde kodla yazılmıştır. Her satırın sağında, noktalı virgülden sonra açıklaması vardır.
a ← 00000101 ; +5
b ← 00001001 ; +9
ters ← NOT b ; bitleri çevir
eksi ← ters + 1 ; −9
s ← a + eksi ; 5 + (−9)
yaz(s) ; işaretli oku| Satır | Değişken | Bitler | Açıklama |
|---|---|---|---|
| 1 | a | 00000101 | +5 |
| 2 | b | 00001001 | +9 = 8 + 1 |
| 3 | ters | 11110110 | bütün bitler çevrildi |
| 4 | eksi | 11110111 | 1 eklendi: −9 (işaretsiz okunursa 247 = 256 − 9) |
| 5 | s | 11111100 | 5 + 247 = 252; 256'ya ulaşmadığı için elde çıkmaz |
| 6 | yazılan | −4 | işaret biti 1, büyüklük: çevir + 1 → 00000100 = 4 |
Tablonun son satırı sonucun nasıl okunduğunu gösterir. s'nin en soldaki biti 1 olduğu için sayı negatiftir. Büyüklüğünü bulmak için bitler yine çevrilir (00000011) ve 1 eklenir (00000100 = 4). Demek ki s = −4'tür ve 5 − 9 = −4 doğrudur.
s = 11111100'ün işaret biti 1'dir, sayı negatiftir. Bitler çevrilip 1 eklenince 00000100 = 4 bulunur; s = −4.Burada dikkat çekici bir nokta vardır: işlemci aynı 11111100 dizisini üretir ve onu nasıl yorumlayacağını bilmez. İşaretsiz yorumda bu dizi 252'dir; işaretli yorumda −4'tür. Hangisinin doğru olduğuna program, değişkenin veri tipine bakarak karar verir.
1.2.8Taşma: 8 bitte 200 + 100
Bit sayısının sınırlı olması, bazı işlemlerin sonucunun yazmaca sığmaması demektir. 8 bitlik işaretsiz bir yazmaç en fazla 255 tutar. 200 ile 100 toplanırsa ne olur?
- İşlenenler: 200 = 128 + 64 + 8 → 11001000; 100 = 64 + 32 + 4 → 01100100.
- Gerçek toplam: 300 = 256 + 32 + 8 + 4 → 1001011002. Sonuç 9 bit gerektirir.
- Yazmaçta kalan: Yazmaç yalnız 8 bit tutar. Ağırlığı 256 olan 9. bit kaybolur ve geriye 00101100 = 44 kalır.
- Kısa yol: 300 − 256 = 44.
Taşma (overflow), bir aritmetik işlemin gerçek sonucunun, sonucu tutacak yazmacın ya da değişkenin aralığına sığmamasıdır. Taşma olunca sığmayan yüksek bitler atılır ve yazmaçta yanlış bir değer kalır.
Taşma işaretli sayılarda da olur. 8 bitte 127 + 1 hesaplanırsa 01111111 + 1 = 10000000 bulunur ve bu dizi −128'dir. Şekil 1.4'teki kadranda 7'den −8'e geçiş, aynı olayın 4 bitlik karşılığıdır.
Taşma programlarda sessiz bir hatadır. Java tam sayı taşmasında hata vermez; sonuç sessizce yanlış çıkar:
byte b = (byte) (100 + 50);
System.out.println(b);
int buyuk = Integer.MAX_VALUE;
System.out.println(buyuk);
System.out.println(buyuk + 1);Çıktı:
-106
2147483647
-2147483648İlk satırda 100 + 50 = 150 hesaplanır ve sonuç (byte) ile 8 bite sıkıştırılır. 150, byte aralığına sığmaz; 8 bitlik dizisi 10010110'dır ve bu dizi işaretli okunduğunda 150 − 256 = −106'dır. Sonraki satırlarda int'in en büyük değerine 1 eklenir ve en küçük değer elde edilir. Bu yüzden bir değişkenin tipi seçilirken alabileceği en büyük değer düşünülmelidir. Örneğin bir ülkenin nüfusu short'a sığmaz; dünya nüfusu int'e bile sığmaz (yaklaşık 8 milyar > 2,1 milyar).
1.2.9Elde ile işaretli taşmanın farkı
Toplama devresi iki ayrı uyarı üretebilir ve ikisi sık karıştırılır. Birincisi, en soldaki basamaktan dışarı taşan elde (carry), yani 9. bittir. İkincisi işaretli taşmadır (signed overflow). İkisi farklı yorumların sorunudur.
- Elde, sayılar işaretsiz yorumlandığında sonucun 8 bite sığmadığını gösterir. İşaretli toplamada elde atılır ve sonuç yine doğru olabilir.
- İşaretli taşma, aynı işaretli iki sayının toplamı ters işaretli çıkınca oluşur. İki pozitifin toplamı negatif ya da iki negatifin toplamı pozitif görünüyorsa sonuç yanlıştır. Farklı işaretli iki sayının toplamı hiçbir zaman taşmaz, çünkü toplamın büyüklüğü iki sayıdan büyüğünü aşamaz.
| İşlem | İkili toplam | Elde | İşaretli taşma | Sonuç |
|---|---|---|---|---|
| 5 + (−9) | 00000101 + 11110111 = 11111100 | 0 | yok | −4 ✓ |
| (−1) + (−1) | 11111111 + 11111111 = 1 11111110 | 1 | yok | −2 ✓ |
| 100 + 50 | 01100100 + 00110010 = 10010110 | 0 | var | −106 ✗ |
| (−100) + (−50) | 10011100 + 11001110 = 1 01101010 | 1 | var | +106 ✗ |
Tablo 1.4, dört olası durumun hepsini gösterir. İlk satırda ne elde ne taşma vardır. İkinci satırda elde vardır, ama sonuç doğrudur: −1 + (−1) = −2. Elde yalnız işaretsiz yorumda sorundur; işaretsiz okunursa bu işlem 255 + 255 = 510'dur ve 8 bite sığmaz. Üçüncü satırda iki pozitif sayının toplamı negatif çıkmıştır; elde yoktur ama işaretli taşma vardır. Dördüncü satırda iki negatifin toplamı pozitif çıkmıştır; hem elde hem taşma vardır.
8 bitte 120 + 10 ve (−3) + (−5) işlemlerinde elde ve işaretli taşma var mıdır?
- 120 + 10: 01111000 + 00001010 = 10000010. Toplam 8 bite sığdığı için elde 0'dır. Ama iki pozitif sayının toplamının işaret biti 1 çıkmıştır: işaretli taşma vardır. Dizi işaretli okunursa −126'dır; doğru sonuç olan 130, 8 bitlik işaretli aralığa sığmaz.
- (−3) + (−5): −3 = 11111101, −5 = 11111011. Toplam 1 11111000'dir; elde 1'dir ve atılır. Kalan 11111000 negatiftir ve büyüklüğü 00001000 = 8'dir: sonuç −8, doğrudur. İki negatifin toplamı negatif çıktığı için işaretli taşma yoktur.
Elde ile işaretli taşmayı aynı şey sanmak. Elde işaretsiz yorumun, taşma işaretli yorumun sorunudur. Biri olurken öteki olmayabilir: (−1) + (−1)'de elde vardır ama sonuç doğrudur; 100 + 50'de elde yoktur ama işaretli sonuç yanlıştır.
1.3Kayan noktalı sayılar
Tam sayılar sayma işlerinde yeterlidir, ama mühendislik hesaplarının çoğu kesirli değerlerle yapılır: bir direncin üzerindeki gerilim 3,3 V, bir sinyalin periyodu 0,000016 s, bir yıldızın uzaklığı 4 · 1016 m olabilir. Bu kesim, böyle sayıların sınırlı bitle nasıl saklandığını ve bunun bedelini anlatır.
1.3.1Neden kayan nokta?
Bilimsel gösterimde çok büyük ve çok küçük sayılar aynı kalıpla yazılır: 4 · 1016, 1,6 · 10−5. Sayı iki parçaya ayrılır: anlamlı basamaklar ve virgülün yerini söyleyen üs. Üs değiştikçe virgül sola ya da sağa “kayar”. Bilgisayardaki kayan nokta (floating point) gösterimi aynı fikri 10 yerine 2 tabanıyla uygular.
Bu gösterimin dört özelliği vardır.
- Geniş aralık. Çok büyük ve çok küçük değerler aynı sayıda bitle saklanır. 32 bitlik
floatyaklaşık 1,2 · 10−38 ile 3,4 · 1038 arasındaki değerleri tutabilir. - Sınırlı hassasiyet. Anlamlı basamak sayısı sabittir.
floatyaklaşık 7,doubleyaklaşık 15–16 onluk basamak taşır. Her ondalık değer sonlu bitlerle tam temsil edilemez. - Yuvarlama. Saklanamayan basamaklar yuvarlanır; hesaplama sonuçlarında küçük farklar oluşabilir.
- Karşılaştırma. Bu küçük farklar yüzünden kayan noktalı sonuçlar tam eşitlikle değil, bir tolerans değeriyle karşılaştırılır.
double değeri 0,29999999999999998890'dır. 0,1 + 0,2'nin sonucu bir sonraki double'dır: 0,30000000000000004441. Gerçek 0,3 bu ikisinin arasındadır ve double ile tam yazılamaz. İki komşu double arasındaki uzaklık yaklaşık 5,55 · 10−17'dir.Şekil 1.8, sınırlı hassasiyetin ne demek olduğunu somut olarak gösterir. Sayı doğrusundaki her nokta bir double ile yazılamaz; yalnız belirli noktalar yazılabilir ve bunların arasında boşluklar vardır. 0,3 bu noktalardan biri değildir. Program 0,3 yazınca ona en yakın nokta saklanır. 0,1 ve 0,2 de tam saklanamaz; yuvarlanmış hâlleri toplanınca sonuç, 0,3'ün yuvarlanmış hâli yerine bir sonraki noktaya düşer. Bu yüzden Java'da 0.1 + 0.2 ekrana 0.30000000000000004 olarak yazılır ve 0.1 + 0.2 == 0.3 karşılaştırması false verir.
1.3.2Ondalık kesri ikiliye çevirmek
İkili sistemde virgülden sonraki basamakların ağırlıkları 1/2, 1/4, 1/8, 1/16, … diye yarıya iner. Örneğin 0,1012 = 1/2 + 1/8 = 0,625'tir. Ondalık bir kesri ikiliye çevirmek için tam sayılardaki bölme yönteminin tersi kullanılır.
Kesri ikiliye çevirme yöntemi: Kesir 2 ile çarpılır. Çarpımın tam kısmı (0 ya da 1) virgülden sonraki bir sonraki bittir. Tam kısım atılır ve kalan kesirle devam edilir. Kesir 0 olunca açılım biter. Daha önce görülmüş bir kesir yeniden çıkarsa açılım döngüye girmiştir ve hiç bitmez.
0,375 sayısını ikiliye çevirelim.
- 0,375 × 2 = 0,75 → tam kısım 0, ilk bit 0.
- 0,75 × 2 = 1,5 → tam kısım 1, ikinci bit 1; kesir 0,5 ile devam edilir.
- 0,5 × 2 = 1,0 → tam kısım 1, üçüncü bit 1; kesir 0 oldu, açılım bitti.
Sonuç 0,375 = 0,0112'dir. Denetim: 1/4 + 1/8 = 0,25 + 0,125 = 0,375.
0,375'in ikili açılımı sonlu olduğu için bu sayı bilgisayarda tam saklanır. Bunun nedeni, 0,375'in 3/8 olması ve paydasının 2'nin bir kuvveti olmasıdır. Genel kural şudur: sadeleştirilmiş kesrin paydası yalnız 2'nin kuvvetlerinden oluşuyorsa ikili açılım sonludur; aksi hâlde sonsuza dek tekrarlar. 0,5, 0,25, 0,75 ve 0,375 tam saklanır. 0,1 = 1/10, 0,2 = 1/5 ve 0,3 = 3/10 ise paydalarında 5 çarpanı bulunduğu için tam saklanamaz.
1.3.30,1 neden tam saklanamaz?
Aynı yöntem 0,1'e uygulandığında ne olduğunu adım adım görelim.
| Adım | İşlem | Bit | Kalan kesir |
|---|---|---|---|
| 1 | 0,1 × 2 = 0,2 | 0 | 0,2 |
| 2 | 0,2 × 2 = 0,4 | 0 | 0,4 |
| 3 | 0,4 × 2 = 0,8 | 0 | 0,8 |
| 4 | 0,8 × 2 = 1,6 | 1 | 0,6 |
| 5 | 0,6 × 2 = 1,2 | 1 | 0,2 (2. adımdaki kesir) |
Beşinci adımın sonunda kesir yine 0,2 olmuştur. 0,2 ikinci adımda zaten işlenmişti; bundan sonra 2.–5. adımlar aynen tekrarlanır ve 0011 bitleri sonsuza dek üretilir:
0,110 = 0,0 0011 0011 0011 …2
Bilgisayar sonsuz bit saklayamaz. Açılım belli bir yerde kesilir ve son bit yuvarlanır. Saklanan sayı 0,1'e çok yakındır ama ona eşit değildir. Aynı durum, onluk sistemde 1/3'ün 0,3333… diye yazılmasına benzer: kaç basamak yazılırsa yazılsın 1/3'e tam ulaşılmaz. Onluk sistemde sorun çıkarmayan 0,1, ikili sistemde 1/3 gibi davranır.
Küçük yuvarlama hataları art arda yapılan işlemlerde birikebilir. Aşağıdaki kod 0,1'i on kez toplar:
double toplam = 0.0;
for (int i = 0; i < 10; i++) {
toplam = toplam + 0.1;
}
System.out.println(toplam);
System.out.println(toplam == 1.0);
System.out.println(Math.abs(toplam - 1.0) < 1e-9);Çıktı:
0.9999999999999999
false
trueMatematikte 10 · 0,1 = 1'dir, ama döngü 0,9999999999999999 üretir. Bu yüzden ikinci satırdaki == karşılaştırması false verir. Son satır doğru yöntemi gösterir: iki sayının farkının mutlak değeri, küçük bir eşikten (burada 10−9) küçükse sayılar “eşit” kabul edilir.
Kayan noktalı sonuçları == ile karşılaştırmak. 0,1 + 0,2 ≠ 0,3 olduğu için if (x == 0.3) beklenmedik biçimde atlanabilir. Doğrusu, Math.abs(a - b) < eşik biçiminde bir toleransla karşılaştırmaktır. Eşik, hesaplanan büyüklüklerin ölçeğine göre seçilir.
1.3.4IEEE 754 biçimi
Kayan noktalı sayıların bitlere nasıl yerleşeceği, bütün işlemcilerin uyduğu IEEE 754 standardıyla belirlenir. Standart, sayıyı ikili bilimsel gösterime getirir:
değer = (−1)işaret × 1,kesir × 2(üs − sapma)
Bit dizisi üç alana bölünür: işaret, üs ve kesir (sign, exponent, fraction). Tablo 1.6, Java'daki iki kayan noktalı tipin alanlarını gösterir.
| Tip | Toplam | İşaret | Üs | Kesir | Sapma | Onluk basamak |
|---|---|---|---|---|---|---|
float | 32 bit | 1 | 8 | 23 | 127 | yaklaşık 7 |
double | 64 bit | 1 | 11 | 52 | 1023 | yaklaşık 15–16 |
Alanların görevleri şöyledir.
- İşaret biti 0 ise sayı pozitif, 1 ise negatiftir. Kayan noktalı sayılarda ikiye tümleyen kullanılmaz; büyüklük her zaman pozitif saklanır.
- Üs alanına gerçek üs değil, ona sapma (bias) eklenmiş hâli yazılır.
float'ta sapma 127'dir. Böylece negatif üsler de işaretsiz bir sayı olarak saklanır: üs −4 ise alana 123 yazılır. - Kesir alanı, virgülden sonraki bitleri tutar. Normal sayılarda virgülün solunda her zaman tek bir 1 bulunur. Bu 1 hiç değişmediği için saklanmaz; standart bir bit kazanmış olur.
float gösterimi. İşaret 0; üs alanı 011110112 = 123, yani gerçek üs 123 − 127 = −4; kesir 1001 1001 … 1101. Değer 1,6000000238 × 2−4 ≈ 0,10000000149'dur ve onaltılık yazımı 3DCCCCCD'dir.Şekil 1.10, 0,1'in float olarak nasıl saklandığını gösterir. 0,1 = 1,1001 1001 1001…2 × 2−4'tür. Kesirdeki 1001 deseni sonsuza dek tekrarlar. 23 bite sığmayınca son bit yuvarlanır; bu yüzden kesir alanının sonunda 1100 yerine 1101 görülür. Saklanan değer tam olarak 0,100000001490116119384765625'tir: 0,1'e çok yakın, ama eşit değil. Java bu değeri BigDecimal ile bütün basamaklarıyla gösterebilir:
System.out.println(Integer.toHexString(Float.floatToIntBits(-6.25f)));
System.out.println(Integer.toHexString(Float.floatToIntBits(0.1f)));
System.out.println(new java.math.BigDecimal(0.1f));Çıktı:
c0c80000
3dcccccd
0.100000001490116119384765625Float.floatToIntBits metodu, bir float'ın 32 bitini olduğu gibi bir int içine kopyalar; Integer.toHexString de bu bitleri onaltılık yazar. İlk satırdaki c0c80000, bir sonraki kesimde elle bulunacak.
1.3.5−6,25'i float olarak kodlama
IEEE 754'ü anlamanın en iyi yolu, bir sayıyı elle kodlamaktır. Sunumdaki adımlar aşağıdaki sözde kodla özetlenmiştir:
x ← −6,25
işaret ← 1 ; negatif
ikili ← 110,01 ; 6 = 110, 0,25 = 0,01
norm ← 1,1001 × 2² ; virgül 2 sola
üs ← 2 + 127 = 129 ; 10000001
kesir ← 1001 0000… ; baştaki 1 yazılmaz
yaz(onaltılık(bitler))−6,25 sayısının 32 bitlik float karşılığını bulalım.
- İşaret: Sayı negatif olduğundan işaret biti 1'dir. Geri kalan işlemler 6,25 üzerinde yapılır.
- İkiliye çevirme: Tam kısım 6 = 1102. Kesir 0,25 için 0,25 × 2 = 0,5 → 0; 0,5 × 2 = 1,0 → 1 ve kesir 0'a iner: 0,012. Demek ki 6,25 = 110,012'dir. 0,1'in aksine açılım sonludur ve sayı tam saklanır.
- Normalleştirme: Virgül, solunda tek bir 1 kalacak kadar kaydırılır. 110,01'de virgül 2 basamak sola gider: 1,1001 × 22. Gerçek üs 2'dir.
- Üs alanı: Sapma eklenir: 2 + 127 = 129 = 100000012.
- Kesir alanı: Baştaki 1 yazılmaz; yalnız virgülden sonraki 1001 yazılır. Kalan 19 bit 0 ile doldurulur: 1001 0000 0000 0000 0000 000.
- Bitleri birleştirme: 1 | 10000001 | 10010000000000000000000. 32 bit dörderli gruplanır: 1100 0000 1100 1000 0000 0000 0000 0000 → C0C8000016.
Elle bulunan C0C80000, Java'nın Float.floatToIntBits(-6.25f) sonucuyla (c0c80000) aynıdır. Ters yönde okuma da aynı adımların tersidir. Örneğin 4128000016'ın bitleri 0 | 10000010 | 0101000…'dır. İşaret 0, gerçek üs 130 − 127 = 3, sayı 1,01012 × 23 = 1010,12 = 10,5'tir.
Üs alanına gerçek üssü yazmak ya da kesir alanına baştaki 1'i de koymak. −6,25 örneğinde üs alanına 2 (00000010) değil 129 (10000001) yazılır. Kesir alanı 11001 ile değil 1001 ile başlar; virgülün solundaki 1 saklanmaz.
1.3.6Kayan noktalı sayılarla çalışırken
Kayan noktalı sayıların sınırlarını bilmek, gündelik programlamada üç pratik kurala dönüşür.
- Eşitlik yerine tolerans. Hesaplanmış iki değer
==ile karşılaştırılmaz; farklarının mutlak değeri küçük bir eşikle karşılaştırılır. - Hassasiyet için
double.float'ın yaklaşık 7 basamağı çoğu mühendislik hesabı için azdır. Java'da kesirli sabitler varsayılan olarakdouble'dır ve genellikle o tercih edilir. - Para için tam sayı. Kuruş hatası kabul edilmeyen hesaplarda tutarlar kuruş cinsinden tam sayı olarak ya da onluk tabanda tam hesap yapan bir tiple (Java'da
BigDecimal) tutulur.
Tam sayılarla kayan noktalı sayıların farkı burada netleşir. İkili gösterim, aralığa sığan her tam sayı için kesindir: 13, 13 olarak saklanır. Kayan nokta ise bazı ondalık değerler için yaklaşıktır. Taşma tam sayıların, yuvarlama kayan noktalı sayıların tipik sorunudur; ikisinin de kaynağı sınırlı bit sayısıdır.
1.4Metnin temsili
Bilgisayar harfleri doğrudan saklayamaz; yalnız sayıları saklar. Metni saklamak için her karaktere bir numara verilir ve bu numara bitlerle yazılır. Hangi karaktere hangi numaranın verileceğini bir kodlama tablosu belirler. Bu kesim, en eski ve en basit tablo olan ASCII'den başlar, bütün dünya yazılarını kapsayan Unicode'a ve onu baytlara yazan UTF-8'e ulaşır.
1.4.1ASCII
ASCII (American Standard Code for Information Interchange), temel Latin harflerini, rakamları, noktalama işaretlerini ve kontrol karakterlerini sayısal kodlarla eşleştirir. 1960'larda tanımlanmış 7 bitlik bir koddur ve 27 = 128 karakter (0–127) içerir. Bir bayt içinde saklandığında en yüksek bit 0'dır.
| Karakter | Onluk | Onaltılık | İkili (8 bit) |
|---|---|---|---|
| “A” | 65 | 41 | 01000001 |
| “Z” | 90 | 5A | 01011010 |
| “a” | 97 | 61 | 01100001 |
| “z” | 122 | 7A | 01111010 |
| “0” | 48 | 30 | 00110000 |
| “7” | 55 | 37 | 00110111 |
| boşluk | 32 | 20 | 00100000 |
| satır sonu (LF) | 10 | 0A | 00001010 |
Tablodaki son satır bir kontrol karakteridir (control character). Kontrol karakterleri ekranda bir şekil olarak görünmez; metnin nasıl düzenleneceğini söyler. Satır sonu (LF, line feed), imlecin alt satıra geçmesi gerektiğini bildirir. ASCII'nin ilk 32 kodu (0–31) ve 127 bu türdendir.
ASCII tablosunun düzeni rastgele değildir; iki özelliği hesap yapmayı kolaylaştırır.
- Harfler ve rakamlar ardışıktır. “B” = 66, “C” = 67 diye gider. Bir rakam karakterinin sayısal değeri, kodundan “0”ın kodu çıkarılarak bulunur: “7” − “0” = 55 − 48 = 7.
- Büyük ve küçük harf arasında hep 32 fark vardır. “a” − “A” = 97 − 65 = 32. 32 = 25 olduğu için iki harf yalnız tek bir bitte ayrılır.
Rakam karakteri ile sayıyı karıştırmak. Klavyeden girilen “7” bir karakterdir ve kodu 55'tir. Sayısal değeri 7 değil 55 olarak hesaba girerse sonuç yanlış çıkar. Doğrusu, “7” − “0” = 7 dönüşümünü yapmaktır.
ASCII'nin büyük eksiği kapsamıdır. 128 karakter İngilizce için yeterlidir, ama “ç, ğ, ı, ö, ş, ü” gibi Türkçe harfler, Yunan ve Arap alfabeleri, Çince karakterler ve matematik simgeleri tabloda yoktur. 1980'ler ve 90'larda her dil için ayrı 8 bitlik tablolar yapıldı; aynı bayt bir tabloda “ğ”, ötekinde başka bir harf demekti. Türkçe metinlerin başka bir bilgisayarda bozuk görünmesinin nedeni çoğunlukla budur. Unicode bu karmaşayı çözmek için geliştirildi (1.4.3).
1.4.2Bit işlemleri: harfin büyüklüğünü değiştirme
Büyük ve küçük harfin tek bir bitte ayrılması, bit işlemlerini (bitwise operation) tanıtmak için iyi bir örnektir. Bit işlemleri, iki sayının aynı konumdaki bitlerini tek tek işler. Bir sayının yalnız bazı bitlerini değiştirmek için bit maskesi (bit mask) kullanılır: maskede 1 olan konumlar işleme girer, 0 olanlar korunur.
“G” = 71 ile “g” = 103 arasındaki fark 32 = 25'tir. Demek ki iki harf 5. bitte ayrılır (bitler sağdan ve 0'dan başlanarak numaralanır). Maske olarak yalnız bu biti 1 olan 00100000 seçilir. Sunumdaki sözde kod şöyledir:
c ← 'G' ; 71 = 01000111
m ← 00100000 ; maske: 5. bit (32)
k ← c OR m ; biti 1 yap
b ← k AND (NOT m) ; biti 0 yap
t ← b XOR m ; biti ters çevir
s ← c << 1 ; sola kaydır| İşlem | Bitler | Değer | Kural |
|---|---|---|---|
c = “G” | 01000111 | 71 = “G” | başlangıç |
c OR m | 01100111 | 103 = “g” | maskede 1 olan bit 1 olur |
k AND (NOT m) | 01000111 | 71 = “G” | NOT m = 11011111; maskedeki bit 0'a çekilir |
b XOR m | 01100111 | 103 = “g” | maskedeki bit ters çevrilir |
c << 1 | 10001110 | 142 = 71 × 2 | bitler bir basamak sola kayar, sağdan 0 girer |
c >> 1 | 00100011 | 35 = 71 ÷ 2 (tam kısım) | bitler bir basamak sağa kayar |
Her işlemin ne işe yaradığı tablonun son sütununda görülür. OR bir biti kesinlikle 1 yapar: harf zaten küçükse değişmez, büyükse küçülür. AND ters maskeyle bir biti kesinlikle 0 yapar: harf büyüğe döner. XOR biti ters çevirir: büyük harf küçülür, küçük harf büyür. Aynı maskeyle ikinci kez XOR yapılırsa eski değer geri gelir; bu özellik basit şifreleme yöntemlerinde de kullanılır.
Bit kaydırma (bit shift) ise bütün bitleri birlikte yerinden oynatır. Sola bir kaydırmada her bit bir basamak yükselir ve ağırlığı iki katına çıkar; değer 2 ile çarpılmış olur. Sağa kaydırma 2'ye böler ve kesirli kısım atılır: 71 >> 1 = 35.
Java'da bit işlemleri | (OR), & (AND), ~ (NOT), ^ (XOR), << ve >> işleçleriyle yazılır. char tipi Java'da sayısal bir koddur; bu yüzden harflerle doğrudan bit işlemi yapılabilir:
char c = 'G';
System.out.println((int) c);
System.out.println((char) (c | 32));
System.out.println((char) ('g' & ~32));
System.out.println((char) (c ^ 32));
System.out.println(c << 1);
System.out.println(c >> 1);Çıktı:
71
g
G
g
142
35Bit işleminin sonucu int'tir. Ekranda harf görmek için sonuç (char) ile yeniden karaktere çevrilir. Son iki satırda bu çevirme yapılmadığı için sayılar yazılır. Aynı bitlerin bir yerde harf, bir yerde sayı olarak görünmesi, 1.1.2'deki ilkenin, yani anlamı veri tipinin verdiğinin bir başka örneğidir.
Harf büyüklüğünü değiştirmek için hep 32 eklemek ya da çıkarmak. “G” + 32 doğru sonucu verir, ama “g” + 32 = 135 tanımsız bir koddur. OR ile küçültme ve AND ile büyütme, harf zaten istenen hâldeyse onu değiştirmez; bu yüzden daha güvenlidir. Bu yöntem yalnız ASCII'deki İngilizce harflerde geçerlidir; Türkçedeki “ı/I” ve “i/İ” çiftleri bu kurala uymaz.
1.4.3Unicode ve kod noktası
Unicode, dünyadaki yazı sistemlerinin karakterlerine tek ve evrensel bir numara verir. Bu numaraya kod noktası (code point) denir ve “U+” önekiyle onaltılık yazılır. “A”nın kod noktası U+0041'dir; bu, onluk 65 eder ve ASCII koduyla aynıdır. Unicode ilk 128 kod noktasını bilerek ASCII ile aynı tutar. “ğ” U+011F, “€” U+20AC, gülen yüz emojisi U+1F600'dür. Unicode bugün Türkçe harfleri, farklı alfabeleri, matematik simgelerini ve emojiyi kapsayan yüz binlerce karakter içerir.
Kod noktası yalnız bir numaradır. Numaranın dosyada ya da bellekte hangi baytlarla yazılacağı ayrı bir karardır. Aynı kod noktası farklı Unicode dönüşüm biçimleriyle saklanabilir: UTF-8, UTF-16 ve UTF-32 bunların en bilinenleridir. Bir metin dosyası açılırken hangi biçimle yazıldığı bilinmezse karakterler bozuk görünür.
1.4.4UTF-8 kodlama: “ğ”
UTF-8, Unicode kod noktalarını değişken uzunlukta bayt dizileriyle yazar. Küçük kod noktaları az, büyükleri çok bayt kaplar. Hangi baytın hangi görevde olduğu, baytın başındaki bitlerden anlaşılır. Tablo 1.9, kuralları özetler.
| Kod noktası aralığı | Bit sayısı | Bayt şablonu | Örnek |
|---|---|---|---|
| U+0000 – U+007F | 7 | 0xxxxxxx | “A” → 41 |
| U+0080 – U+07FF | 11 | 110xxxxx 10xxxxxx | “ğ” → C4 9F |
| U+0800 – U+FFFF | 16 | 1110xxxx 10xxxxxx 10xxxxxx | “€” → E2 82 AC |
| U+10000 – U+10FFFF | 21 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | emoji → F0 9F 98 80 |
Şablonların başındaki bitler birer etikettir. 0 ile başlayan bayt tek başına bir ASCII karakteridir. 110 “iki baytlık karakterin ilk baytı”, 1110 “üç baytlık karakterin ilk baytı”, 11110 “dört baytlık karakterin ilk baytı” demektir. 10 ile başlayan bayt ise bir devam baytıdır. Bu etiketler sayesinde metnin ortasından okumaya başlayan bir program bile karakter sınırlarını bulabilir.
“ğ” harfinin UTF-8 baytlarını bulalım.
- Kod noktası: Unicode, “ğ”ya U+011F'yi atar. 011F16 = 1 · 256 + 1 · 16 + 15 = 287.
- İkili yazım: 287 = 256 + 16 + 8 + 4 + 2 + 1 = 1000111112. Sayı 7 bite sığmaz (ASCII değildir), ama 11 bite sığar. Demek ki 2 bayt gerekir. 11 bite tamamlamak için başa iki 0 eklenir: 00100011111.
- Bölme: İki baytlık şablon
110xxxxx 10xxxxxx'tir ve 5 + 6 = 11 boş yer vardır. 11 bit soldan 5 + 6 olarak bölünür: 00100 | 011111. - Yerleştirme: İlk bayt 110 + 00100 = 11000100 = C416. İkinci bayt 10 + 011111 = 10011111 = 9F16.
- Sonuç: “ğ” = C4 9F, 2 bayt.
110xxxxx 10xxxxxx şablonuna yerleştirilir: 11000100 10011111 = C4 9F.Aynı yöntem “ç” için de çalışır. “ç”nin kod noktası U+00E7 = 231'dir; 11 bitle 00011100111 yazılır. 5 + 6 bölmesiyle 00011 | 100111 elde edilir ve baytlar 11000011 10100111 = C3 A7 olur. Türkçeye özgü harflerin hepsi U+0080 – U+07FF aralığındadır; bu yüzden UTF-8'de her biri 2 bayt tutar. ASCII karakterleri 1, “€” 3, emoji 4 bayt tutar.
UTF-8'in en büyük üstünlüğü ASCII ile uyumudur. ASCII karakterleri tek baytla ve ASCII'deki kodlarıyla yazıldığı için eski ASCII metinler aynı zamanda geçerli UTF-8'dir. Web sayfalarının büyük çoğunluğu bugün UTF-8 ile yazılır. Bir UTF-8 dosyası yanlışlıkla eski bir 8 bitlik tabloyla okunursa “ğ”nin iki baytı iki ayrı karakter gibi görünür; ekranda “ÄŸ” gibi anlamsız harflerin çıkmasının nedeni budur.
1.4.5Dizgiler
Dizgi (string), sıralı karakterlerden oluşan veri tipidir. Bir ad, bir cümle ya da bir dosyanın bütün içeriği birer dizgidir. Dizgiler üzerinde yapılan temel işlemler şunlardır:
- Uzunluk: karakter ya da kod birimi sayısı.
- Birleştirme: iki dizgiyi uç uca ekleme.
- Arama: bir karakteri ya da alt dizgiyi bulma.
- Karşılaştırma: içerik eşitliğini ve alfabetik sıralama ilişkisini denetleme.
Şekil 1.16, önemli bir ayrımı gösterir: ekranda görünen karakter sayısı ile bellekteki bayt sayısı her zaman aynı değildir. “Merhaba Dünya” 13 karakterdir, ama UTF-8'de 14 bayt tutar. Belleği ayırırken, bir dosyanın boyutunu hesaplarken ya da bir veritabanı alanının uzunluğunu seçerken hangisinin sayıldığı bilinmelidir. Bellekteki saklama biriminin adı kod birimidir (code unit): UTF-8'de kod birimi 1 bayt, Java'nın kullandığı UTF-16'da 2 bayttır.
String s = "çağ";
System.out.println(s.length());
byte[] b = s.getBytes(java.nio.charset.StandardCharsets.UTF_8);
System.out.println(b.length);
for (byte x : b) {
System.out.printf("%02X ", x);
}
System.out.println();Çıktı:
3
5
C3 A7 61 C4 9F Java'da length() metodu UTF-16 kod birimlerini sayar; “çağ” için 3 verir. getBytes ise dizgiyi UTF-8 baytlarına çevirir: “ç” C3 A7, “a” 61, “ğ” C4 9F. Toplam 5 bayttır. Bir emoji UTF-16'da iki kod birimi tuttuğu için onun length()'i 2 çıkar; “karakter sayısı” kavramı Unicode'da göründüğünden daha inceliklidir.
Karakter sayısını bayt sayısı sanmak. UTF-8'de “çağ” 3 karakter, 5 bayttır (ç 2 + a 1 + ğ 2). 10 baytlık bir alana 10 Türkçe harf sığmayabilir.
1.5Yapılandırılmış bilgi ve sık yapılan hatalar
1.5.1Bitten dosyaya
Bitler tek başına anlam taşımaz; daha büyük yapılar içinde düzenlenerek anlam kazanır. Bu yapılar katman katman kurulur.
- Bit ve bayt: en küçük birimler. Sekiz bit bir bayt eder.
- Karakter: ASCII'de bir, UTF-8'de bir ile dört arası bayt.
- Alan (field): anlamlı tek bir bilgi; örneğin bir ad (“Ayşe”) ya da bir not (85).
- Kayıt (record): birbiriyle ilgili alanların topluluğu. Bir öğrenci kaydı numara, ad ve not alanlarından oluşabilir.
- Dosya: kayıtların ve başka verilerin diskte saklanan dizisi.
Bir dosyanın içindeki baytların nasıl okunacağını dosya biçimi (file format) tanımlar: alanların sırası, her alanın kaç bayt olduğu ve hangi kodlamayla yazıldığı. Aynı baytlar bir program için öğrenci listesi, başka bir program için anlamsız sayılardır. Bir resim dosyasını metin düzenleyiciyle açınca karmakarışık karakterler görülmesinin nedeni budur: baytlar yanlış biçimle yorumlanmıştır.
1.5.2Veri temsilinde sık yapılan hatalar
Veri temsiliyle ilgili kesimlerde dağınık olarak geçen hatalar, toplu bir başvuru için Tablo 1.10'da bir araya getirilmiştir.
| Sık yapılan hata | Doğrusu |
|---|---|
| Tabanı yazmadan okumak: “1101 bin yüz birdir” | 11012 = 13. Taban alt simgeyle ya da 0b/0x önekiyle belirtilir. |
| 1016'yı on sanmak | 1016 = 16; onaltılıkta on, A ile yazılır. |
| İkiye tümleyende yalnız bitleri çevirmek | 11111010 birler tümleyenidir (−5 değil); 1 eklenince 11111011 = −5. |
Kayan noktalı sonuçları == ile karşılaştırmak | 0,1 + 0,2 ≠ 0,3; |a − b| küçük bir toleranstan küçük mü diye bakılır. |
| Rakam karakteri ile sayıyı karıştırmak | “7” karakterinin kodu 55'tir; sayısal değeri “7” − “0” = 7. |
| Karakter sayısını bayt sayısı sanmak | UTF-8'de “çağ” 3 karakter, 5 bayttır (ç 2 + a 1 + ğ 2). |
| Elde ile işaretli taşmayı karıştırmak | Elde işaretsiz yorumun, taşma işaretli yorumun sorunudur; biri olurken öteki olmayabilir. |
1.6Mantık kapıları ve toplayıcılar
Önceki kesimlerde toplama hep kâğıt üzerinde yapıldı. İşlemci ise toplamayı elektronik devrelerle yapar. Bu devrelerin yapı taşı mantık kapılarıdır. Bu kesim, birkaç kapının bir araya gelerek iki biti, sonra da çok bitli sayıları nasıl topladığını gösterir.
1.6.1Mantık kapıları ve doğruluk tabloları
Mantık kapısı (logic gate), bir ya da iki bitlik girdiden tek bitlik bir çıktı üreten temel devredir. Bir kapının davranışı, bütün girdi bileşimleri için çıktıyı listeleyen doğruluk tablosuyla (truth table) tam olarak tanımlanır.
Dört temel kapı vardır:
- AND (VE): iki girdi de 1 ise 1 verir.
- OR (VEYA): girdilerden en az biri 1 ise 1 verir.
- XOR (ÖZEL VEYA): girdiler farklıysa 1 verir.
- NOT (DEĞİL): tek girdisini tersine çevirir.
| A | B | AND | OR | XOR | NOT A |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 0 | 0 |
OR ile XOR arasındaki tek fark son satırdadır: iki girdi de 1 olunca OR 1, XOR 0 verir. XOR'un bu davranışı ikili toplamada sonuç basamağını üretir, çünkü 1 + 1'in basamağı 0'dır. AND ise 1 + 1'de oluşan eldeyi verir.
Kapılar birkaç transistörden kurulur. Transistör, bir gerilimle açılıp kapanan küçük bir elektronik anahtardır. Bugünkü bir işlemcide milyarlarca transistör bulunur ve işlemcideki bütün işlemler bu temel kapıların birleşiminden oluşur. Kapılar birbirine bağlanarak daha karmaşık mantık ifadeleri hesaplanır. Örneğin X = NOT (A AND B) OR C ifadesinde önce parantez, sonra NOT, en son OR hesaplanır. A = 1, B = 1, C = 0 için A AND B = 1, NOT 1 = 0 ve 0 OR 0 = 0 bulunur. C = 1 olsaydı OR'da tek bir 1 yeteceği için sonuç A ve B ne olursa olsun 1 olurdu.
1.6.2Yarım toplayıcı
İki tek bitlik sayının toplamı en çok 1 + 1 = 2 = 102 olabilir; sonuç iki bitle yazılır. Sağdaki bit sonuç basamağı (SUM), soldaki bit eldedir (CARRY). Tablo 1.12, dört durumu listeler.
| A | B | A + B | CARRY | SUM |
|---|---|---|---|---|
| 0 | 0 | 0 = 002 | 0 | 0 |
| 0 | 1 | 1 = 012 | 0 | 1 |
| 1 | 0 | 1 = 012 | 0 | 1 |
| 1 | 1 | 2 = 102 | 1 | 0 |
Tabloya dikkatle bakınca SUM sütununun XOR'un, CARRY sütununun AND'in tablosuyla aynı olduğu görülür. Demek ki iki biti toplamak için iki kapı yeterlidir.
Yarım toplayıcı (half adder), iki tek bitlik girdiyi (A, B) toplayan devredir: SUM = A XOR B sonuç basamağını, CARRY = A AND B eldeyi verir.
Şekil 1.19, devrenin en ilginç durumunu gösterir. 1 + 1 için XOR 0, AND 1 verir. Sonuç (CARRY, SUM) sırasıyla 102 = 2'dir. Onluk sistemdeki 9 + 1 = 10 gibi, basamak taşar ve elde bir üst basamağa gider. Yarım toplayıcının adı da buradan gelir: gelen bir eldeyi alacak girişi yoktur, toplama işinin yalnız yarısını yapar.
1.6.3Tam toplayıcı
Çok bitli sayılar toplanırken en sağdaki sütun dışında her sütuna, sağındaki sütundan bir elde gelebilir. Bu yüzden her sütunda üç bit toplanır: A, B ve gelen elde (Cin). Üç bitin toplamı en çok 3 = 112'dir; yine iki bitle yazılır.
Tam toplayıcı (full adder), üç bitlik girdiyi (A, B, Cin) toplayıp sonuç basamağını (SUM) ve çıkan eldeyi (Cout) üreten devredir. İki yarım toplayıcı ve bir OR kapısıyla kurulur:
t = A XOR B, SUM = t XOR Cin, Cout = (A AND B) OR (t AND Cin)
İlk yarım toplayıcı A ile B'yi toplar ve ara sonuç t'yi üretir. İkinci yarım toplayıcı t'ye gelen eldeyi ekler. Elde iki yarım toplayıcının birinden çıkabilir; ikisinden birden çıkamaz. OR kapısı bu iki olası eldeyi birleştirir.
A = 1, B = 1, Cin = 1 için tam toplayıcının çıktılarını bulalım.
- İlk yarım toplayıcı: t = 1 XOR 1 = 0; ilk elde 1 AND 1 = 1.
- İkinci yarım toplayıcı: SUM = 0 XOR 1 = 1; ikinci elde 0 AND 1 = 0.
- OR: Cout = 1 OR 0 = 1.
- Denetim: (Cout, SUM) = 112 = 3 = 1 + 1 + 1.
1.6.4Dört bitlik toplayıcı: 0110 + 0111
Dört tam toplayıcı yan yana konur ve her birinin elde çıkışı solundakinin elde girişine bağlanır. Böylece dört bitlik sayıları toplayan bir devre elde edilir. En sağdaki toplayıcının gelen eldesi 0'dır. Sunumdaki sözde kod, devrenin sağdan sola yaptığı işi anlatır:
a ← 0110 ; 6
b ← 0111 ; 7
c ← 0 ; ilk elde
i = 0, 1, 2, 3 için: ; sağdan sola
t ← a[i] XOR b[i]
s[i] ← t XOR c
c ← (a[i] AND b[i]) OR (t AND c)
yaz(c, s)| i | a[i] | b[i] | gelen c | t | s[i] | çıkan c |
|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 1 | 1 | 0 |
| 1 | 1 | 1 | 0 | 0 | 0 | 1 |
| 2 | 1 | 1 | 1 | 0 | 1 | 1 |
| 3 | 0 | 0 | 1 | 0 | 1 | 0 |
Tablo satır satır şöyle okunur. i = 0'da 0 + 1 + 0 toplanır: t = 1, s0 = 1; iki giriş birden 1 olmadığı için elde 0'dır. i = 1'de 1 + 1 + 0 = 2 = 102'dir: basamak 0, elde 1. Bu, onluktaki 5 + 5 = 10'un ikili karşılığıdır. i = 2'de gelen elde de eklenir: 1 + 1 + 1 = 3 = 112, basamak 1, elde yine 1. i = 3'te 0 + 0 + 1 = 1'dir; basamak 1, elde 0. Son elde 0 olduğu için sonuç 4 bite sığmıştır.
Sonuç 11012 = 13 = 6 + 7'dir. Bu düzende elde, sağdan sola bir dalga gibi ilerler; devrenin adı da buradan gelir: dalgalı elde toplayıcı (ripple-carry adder). Her tam toplayıcı, sağındakinin eldesini beklemek zorundadır. Bit sayısı arttıkça bekleme süresi, yani gecikme de büyür: 64 bitlik bir dalgalı elde toplayıcıda en soldaki basamak, 63 toplayıcının sırayla bitirmesini bekler. Gerçek işlemciler bu yüzden eldeyi önceden hesaplayan daha karmaşık toplayıcılar kullanır; ama hepsi aynı kapılardan kurulur.
Bu devre, 1.2'deki kavramlarla doğrudan bağlantılıdır. Son tam toplayıcıdan çıkan elde, 1.2.9'daki elde bitidir. Örneğin 1011 + 0111 (11 + 7) toplanırsa her sütunda elde 1 çıkar; 4 bitte kalan 0010 = 2'dir ve son elde 1, sonucun 4 bite sığmadığını bildirir. İkiye tümleyen sayesinde aynı devre çıkarma da yapar: a − b için b'nin bitleri NOT kapılarıyla çevrilir ve en sağdaki gelen elde 1 yapılır. Böylece “bitleri çevir, 1 ekle” kuralı donanımda tek bir devreyle uygulanmış olur.
Çok bitli toplamayı yarım toplayıcılarla kurmaya çalışmak. Yarım toplayıcının gelen elde girişi yoktur; yalnız en sağdaki sütunda kullanılabilir. Öteki sütunlarda üç bit toplandığı için tam toplayıcı gerekir.
1.7Depolanan program ve CPU
Bir toplayıcı yalnız toplar. Bilgisayarı genel amaçlı yapan şey, hangi işlemin hangi sırayla yapılacağını söyleyen bir programın varlığıdır. Bu kesim, programın bellekte nasıl durduğunu ve işlemcinin onu nasıl adım adım yürüttüğünü anlatır.
1.7.1Depolanan program kavramı
İlk elektronik hesap makinelerinden bazıları, yeni bir hesap için kabloları elle yeniden bağlamayı gerektiriyordu. Bir programı değiştirmek günler sürebiliyordu. 1940'ların ortasında ortaya konan bir fikir bu durumu kökten değiştirdi: komutlar da birer sayı olarak verilerle aynı belleğe konabilirdi.
Depolanan program (stored program) yaklaşımında program komutları ve işlenecek veriler aynı bellekte sayısal kodlar olarak saklanır. İşlemci komutları bellekten sırayla okur ve yürütür.
Bu yaklaşımın üç önemli sonucu vardır.
- Esneklik: Aynı donanım farklı programlarla farklı görevler yapar. Bir bilgisayarda hesap tablosu, oyun ve tarayıcı aynı işlemcide çalışır.
- Güncelleme: Davranış, donanımı değiştirmeden yazılımla değiştirilebilir. Bir telefonun güncellenmesi, belleğe yeni komutların yazılmasıdır.
- Genel amaçlılık: Tek bir sistem hesaplama, iletişim ve medya görevlerini birlikte yürütebilir. Hatta bir program, başka bir programı veri olarak okuyabilir, üretebilir ya da çevirebilir; derleyiciler tam olarak bunu yapar.
1.7.2Getir–çöz–yürüt döngüsü
İşlemci, bellekteki programı hiç durmadan tekrarlanan üç aşamalı bir döngüyle yürütür. Bu döngüyü anlatmadan önce iki yazmacı tanımak gerekir.
- Program sayacı (PC, program counter): sıradaki komutun bellekteki adresini tutar.
- Komut yazmacı (IR, instruction register): bellekten getirilen ve o an yürütülen komutu tutar.
Getir–çöz–yürüt döngüsü (fetch-decode-execute cycle) üç aşamadan oluşur:
- Getir: PC'deki adresten komut okunur ve IR'ye konur; PC bir artırılır.
- Çöz: Kontrol birimi IR'deki komutu ayırır: işlem kodu (opcode) ne yapılacağını, işlenen (operand) neyle yapılacağını söyler.
- Yürüt: ALU ya da başka bir birim işlemi yapar; sonuç bir yazmaca ya da belleğe yazılır.
Ardından döngü yeniden getir aşamasıyla başlar.
PC'nin getir aşamasında, yani komut daha yürütülmeden artırılmasının bir nedeni vardır. Komutların büyük çoğunluğu bir sonraki komuta geçmeyi gerektirir; PC'yi önceden artırmak bunu otomatik yapar. Dallanma komutları ise yürütme aşamasında PC'nin üzerine yeni bir adres yazarak bu sırayı değiştirir (1.7.8).
Döngü anlatılırken bellekten okuma diye söz edilse de komut ve veri çoğu zaman RAM'den değil, işlemcinin içindeki önbellekten (cache) gelir. Yazmaçlar ve önbellek, RAM'den çok daha hızlıdır. Bu ayrıntı döngünün mantığını değiştirmez, yalnız hızını artırır.
1.7.3CPU'nun temel bölümleri
Şekil 1.23, bir işlemcinin dört ana bölümünü gösterir.
- Kontrol birimi (control unit): komutların sırasını ve veri hareketini yönetir. Çözme aşamasını yapar ve öteki birimlere ne zaman ne yapacaklarını bildiren sinyaller gönderir.
- Aritmetik-mantık birimi (ALU, arithmetic logic unit): aritmetik ve mantıksal işlemleri yapar. 1.6.4'teki toplayıcı ALU'nun bir parçasıdır.
- Yazmaçlar: işlem sırasında kullanılan küçük ve hızlı saklama alanlarıdır. PC ve IR'nin yanında, bu nottaki örnek işlemcide hesap sonuçlarının biriktiği bir akümülatör (ACC, accumulator) vardır.
- Önbellek: sık kullanılan veri ve komutları işlemciye yakın tutar.
1.7.4Veri yolları: bellekten okuma
CPU ile bellek ve G/Ç birimleri, yol (bus) adı verilen paralel tel demetleriyle bağlıdır. Üç ayrı yol vardır ve her birinin görevi farklıdır.
| Yol | Görev | Yön |
|---|---|---|
| Adres yolu | İşlem yapılacak bellek ya da aygıt konumunu belirtir. | tek yönlü: CPU'dan çıkar |
| Kontrol yolu | Okuma, yazma ve zamanlama sinyallerini taşır. | çoğunlukla CPU'dan çıkar |
| Veri yolu | CPU, bellek ve aygıtlar arasında veriyi taşır. | çift yönlü |
CPU, 1004 adresindeki değeri okuyacaktır; hücrede 12 vardır. Üç yol sırayla şöyle kullanılır:
- Adres yolu: CPU hangi hücreyi istediğini gönderir: 1004.
- Kontrol yolu: CPU OKU sinyalini gönderir. Yazma olsaydı YAZ sinyali giderdi.
- Veri yolu: Bellek hücrenin içeriğini, yani 12'yi, CPU'ya gönderir. Değer ACC'ye yüklenir.
Yazma işleminde adres ve kontrol yolları yine CPU'dan belleğe doğru kullanılır, ama veri bu kez CPU'dan belleğe akar. Veri yolunun çift yönlü olması bundandır. Adres yolunun genişliği, gösterilebilecek adres sayısını belirler: 16 bitlik bir adres yolu 216 = 65.536 farklı adres gösterebilir.
Adresin de veri yoluyla gittiğini sanmak. “1004” sayısı belleğe adres yoluyla gider; veri yolu yalnız hücrenin içeriğini (12) taşır. Kontrol yolu ise ne yapılacağını (OKU ya da YAZ) bildirir.
1.7.5Bellek adresleri
Bellek, numaralanmış hücrelerden oluşan uzun bir dizi gibi düşünülebilir. Her hücrenin bir adresi vardır ve komutlar veriye bu adresle ulaşır. Bu notta kullanılan küçük örnek bellek Tablo 1.15'te görülür.
| Adres | İçerik | Anlamı |
|---|---|---|
| 1000 | LOAD 1004 | ACC ← bellek[1004] |
| 1001 | ADD 1005 | ACC ← ACC + bellek[1005] |
| 1002 | STORE 1006 | bellek[1006] ← ACC |
| 1003 | HALT | dur |
| 1004 | 12 | veri |
| 1005 | 8 | veri |
| 1006 | 0 | sonuç yeri |
Tablodaki “bellek[1004]” yazımı, “1004 adresindeki hücrenin içeriği” demektir. Adres ile içerik farklı şeylerdir: 1004 adresinde 12 vardır. Bir ev adresi ile o evde oturan kişi arasındaki fark gibidir.
Bellekte komutlar ile veriler arasında fiziksel bir ayrım yoktur. Komutlar da bellekte birer sayı olarak durur; aynı bit dizisi komut ya da veri olarak yorumlanabilir. Bir hücrenin hangisi olduğunu, PC'nin o hücreye gelip gelmediği belirler. HALT komutu bu yüzden gereklidir. HALT olmasaydı CPU 1003'ten sonra 1004'e geçer ve oradaki 12'yi de bir komut sanıp çalıştırmaya çalışırdı.
1.7.6Çok baytlı değerler ve bayt sırası
Gerçek bilgisayarlarda bellek bayt adreslidir: her adres 1 bayt tutar. 32 bitlik bir int ise 4 bayttır ve 4 ardışık adrese yayılır. Bu durumda bir soru doğar: dört baytın hangisi en küçük adrese yazılacak? Bu sıraya bayt sırası (endianness) denir.
0x12345678 değerinde en anlamlı bayt 12, en az anlamlı bayt 78'dir; tıpkı 1234 sayısında 1'in binler, 4'ün birler basamağında olması gibi. İki yaygın düzen vardır.
- Büyük uçlu (big-endian): en anlamlı bayt en küçük adrese yazılır. Adres 100'den 103'e 12 34 56 78 okunur; sayı kâğıtta yazıldığı sırayla dizilir. Ağ protokolleri bu sırayı kullanır; bu yüzden ona ağ bayt sırası da denir.
- Küçük uçlu (little-endian): en az anlamlı bayt en küçük adrese yazılır. Adres 100'den 103'e 78 56 34 12 okunur. x86-64 ve yaygın ARM sistemleri bu sırayla çalışır.
İki düzen de doğru çalışır; sorun yalnız iki sistem arasında ham ikili veri taşındığında çıkar. Küçük uçlu bir makinenin yazdığı 78 56 34 12 baytları, büyük uçlu bir makinede tek seferde okunursa 0x78563412 olarak görülür. Dosya biçimleri ve ağ protokolleri bu yüzden bayt sırasını açıkça belirtir. Java'nın ByteBuffer sınıfıyla sıra seçilebilir:
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(4);
bb.order(java.nio.ByteOrder.LITTLE_ENDIAN);
bb.putInt(0x12345678);
for (byte x : bb.array()) {
System.out.printf("%02X ", x);
}
System.out.println();Çıktı:
78 56 34 12 Tek baytlık değerlerde, örneğin bir ASCII karakterinde, sıra sorunu yoktur. UTF-8 de bayt bayt okunduğu için bayt sırasından etkilenmez; “ğ” her makinede C4 9F sırasıyla yazılır.
1.7.7Programı adım adım çalıştırma
Şimdiye kadar tanıtılan parçalar, Tablo 1.15'teki küçük programın yürütülmesinde bir araya gelir. Program iki sayıyı toplar ve sonucu belleğe yazar. Her satırın sağında, noktalı virgülden sonra komutun anlamı yazılıdır:
1000: LOAD 1004 ; ACC ← bellek[1004]
1001: ADD 1005 ; ACC ← ACC + bellek[1005]
1002: STORE 1006 ; bellek[1006] ← ACC
1003: HALT ; dur
1004: 12 ; veri
1005: 8 ; veri
1006: 0 ; sonuçBu örnek işlemcide hesaplar tek bir yazmaçta, ACC'de yapılır. Komutların anlamları şöyledir: LOAD a bellekteki a adresinin içeriğini ACC'ye yükler; ADD a o içeriği ACC'ye ekler; STORE a ACC'yi a adresine yazar; HALT işlemciyi durdurur. Başlangıçta PC = 1000, yani ilk komutun adresi, ve ACC = 0'dır.
| Adım | Aşama | Ne olur? | PC | IR | ACC | bellek[1006] |
|---|---|---|---|---|---|---|
| 0 | başlangıç | — | 1000 | — | 0 | 0 |
| 1 | getir | 1000 adres yoluna çıkar, LOAD 1004 veri yoluyla IR'ye gelir | 1001 | LOAD 1004 | 0 | 0 |
| 2 | çöz | opcode LOAD, adres 1004 | 1001 | LOAD 1004 | 0 | 0 |
| 3 | yürüt | 1004 okunur, 12 ACC'ye yüklenir | 1001 | LOAD 1004 | 12 | 0 |
| 4 | getir | ADD 1005 IR'ye gelir | 1002 | ADD 1005 | 12 | 0 |
| 5 | çöz | opcode ADD, adres 1005 | 1002 | ADD 1005 | 12 | 0 |
| 6 | yürüt | 1005'teki 8 okunur, ALU 12 + 8 = 20 hesaplar | 1002 | ADD 1005 | 20 | 0 |
| 7 | getir | STORE 1006 IR'ye gelir | 1003 | STORE 1006 | 20 | 0 |
| 8 | çöz | opcode STORE, adres 1006 | 1003 | STORE 1006 | 20 | 0 |
| 9 | yürüt | ACC'deki 20 veri yoluyla 1006'ya yazılır | 1003 | STORE 1006 | 20 | 20 |
| 10 | getir | HALT IR'ye gelir | 1004 | HALT | 20 | 20 |
| 11 | çöz | opcode HALT → CPU durur | 1004 | HALT | 20 | 20 |
İzlemede birkaç düzenli örüntü göze çarpar. PC yalnız getir aşamalarında değişir ve her seferinde bir artar. IR, her getirde yeni komutu alır ve o komut bitene kadar değişmez. ACC yalnız LOAD ve ADD'in yürütme aşamalarında değişir; bellek ise yalnız STORE'un yürütmesinde değişir. Program bittiğinde bellek[1006] = 20'dir. Dört komut, dört getir–çöz–yürüt turuyla yürütülmüştür; HALT'ın yürütülecek bir işi olmadığı için onun turu çözme aşamasında biter.
Programda STORE ile ADD'in yeri değiştirilirse (1001: STORE 1006, 1002: ADD 1005) sonuç ne olur?
- LOAD 1004: ACC = 12.
- STORE 1006: o anki ACC yazılır; bellek[1006] = 12.
- ADD 1005: ACC = 12 + 8 = 20; ama bu değer belleğe hiç yazılmaz.
- HALT: durulur. Sonuç: ACC = 20, bellek[1006] = 12; yine 4 komut getirilmiştir.
CPU komutları yazıldıkları sırayla yürütür; sıra değişince sonuç değişir.
1.7.8Dallanma: döngüde PC nasıl değişir?
Önceki program komutları baştan sona bir kez yürüttü. Gerçek programlar ise karar verir ve işleri tekrarlar. Bunun için dallanma (branch) komutları kullanılır. Dallanma komutu, yürütme aşamasında PC'nin üzerine yeni bir adres yazar; böylece bir sonraki getir başka bir yerden yapılır. Örnek işlemcide iki dallanma komutu vardır:
JUMP a: koşulsuz dallanma; PC ← a.JUMPZ a: koşullu dallanma; ACC = 0 ise PC ← a, değilse PC olduğu gibi kalır ve sıradaki komuta geçilir.
Aşağıdaki program, sayaç değişkenini 2'den geriye sayarak toplam'a 2 + 1 ekler. 1010, 1011 ve 1012 adresleri sırasıyla sayaç, toplam ve sabit 1'i tutar:
1000: LOAD 1010 ; ACC ← sayaç
1001: JUMPZ 1008 ; ACC = 0 ise atla
1002: ADD 1011 ; ACC ← ACC + toplam
1003: STORE 1011 ; toplam ← ACC
1004: LOAD 1010
1005: SUB 1012 ; ACC ← ACC − 1
1006: STORE 1010 ; sayaç ← ACC
1007: JUMP 1000 ; PC ← 1000
1008: HALT
1010: 2 1011: 0 1012: 1Program Java'daki şu döngünün makine düzeyindeki karşılığıdır: while (sayac != 0) { toplam = toplam + sayac; sayac = sayac - 1; }. 1000–1001 satırları döngü koşulunu, 1002–1006 satırları döngü gövdesini, 1007 satırı başa dönüşü yapar.
| Komut no | Yürütülen | PC | ACC | sayaç | toplam | Açıklama |
|---|---|---|---|---|---|---|
| — | başlangıç | 1000 | 0 | 2 | 0 | |
| 1 | 1000: LOAD 1010 | 1001 | 2 | 2 | 0 | ACC ← sayaç |
| 2 | 1001: JUMPZ 1008 | 1002 | 2 | 2 | 0 | ACC ≠ 0, atlama yok |
| 3–4 | ADD 1011, STORE 1011 | 1004 | 2 | 2 | 2 | toplam = 2 + 0 |
| 5–7 | LOAD, SUB, STORE 1010 | 1007 | 1 | 1 | 2 | sayaç = 2 − 1 |
| 8 | 1007: JUMP 1000 | 1000 | 1 | 1 | 2 | PC'nin 1008 değeri 1000 ile değişir |
| 9–16 | ikinci tur (aynı 8 komut) | 1000 | 0 | 0 | 3 | toplam = 1 + 2, sayaç = 0 |
| 17 | 1000: LOAD 1010 | 1001 | 0 | 0 | 3 | ACC ← 0 |
| 18 | 1001: JUMPZ 1008 | 1008 | 0 | 0 | 3 | ACC = 0, atlanır |
| 19 | 1008: HALT | 1009 | 0 | 0 | 3 | CPU durur |
İzlemenin kritik anları iki dallanma komutudur. 8. komutta JUMP 1000 yürütülür. Getir aşamasında PC zaten 1008'e artmıştı; yürütme bu değerin üzerine 1000 yazar ve döngü başa döner. 18. komutta ise JUMPZ bu kez atlar, çünkü ACC'ye yüklenen sayaç 0'dır. PC 1008 olur ve döngü gövdesi atlanır. Toplam 19 komut getirilmiştir: iki tam tur için 8 + 8 ve çıkış için 3 (LOAD, JUMPZ, HALT).
JUMP 1000: PC 1000 olur, sayaç 1, toplam 2'dir. (b) Son turda JUMPZ atlar: ACC = 0 olduğu için PC 1008'e geçer.Komut sayısının genel formülü de izlemeden çıkarılabilir. Sayaç k'dan başlarsa döngü k tur döner ve her tur 8 komuttur; çıkışta 3 komut daha yürütülür. Toplam 8k + 3 komuttur: k = 2 için 19, k = 5 için 43. Toplam ise k + (k − 1) + … + 1 olur; k = 5 için 15. Bu sayım, bir programın süresinin girdinin büyüklüğüyle nasıl arttığını gösteren ilk örnektir.
Dallanma komutunun PC'yi getir aşamasında değiştirdiğini sanmak. Getirde PC her komutta olduğu gibi bir artar (1007 → 1008). Atlama, yürütme aşamasında bu yeni değerin üzerine yazılarak yapılır. Koşullu dallanmada koşul tutmazsa PC'ye dokunulmaz ve artırılmış değer geçerli kalır.
1.8Bilgisayar mimarisi
Bir bilgisayarın parçalarının nasıl düzenlendiğine ve programcının gördüğü komutların nasıl tasarlandığına mimari (architecture) denir. Bu kesim, bugünkü bilgisayarların temelindeki düzeni, onun bir alternatifini, komutların bitlere nasıl yazıldığını ve performansı belirleyen etkenleri ele alır.
1.8.1von Neumann mimarisi
Bugünkü genel amaçlı bilgisayarlar, depolanan program fikrini kullanan bir düzene dayanır. Bu düzen, John von Neumann'ın 1945'te yazdığı bir rapordan sonra von Neumann mimarisi (von Neumann architecture) adıyla anılır.
Mimarinin üç belirleyici özelliği vardır.
- Program komutları ve veriler ortak bellekte tutulur.
- CPU komutları sırayla işler; dallanma komutları bu sırayı değiştirebilir.
- Komut ve veri aynı yoldan gelir.
Son özellik bir bedel doğurur. CPU bir komutu getirirken aynı yoldan veri okuyamaz; ikisi sırayla taşınır. Üstelik işlemciler yıllar içinde bellekten çok daha hızlı gelişmiştir. Sonuçta CPU–bellek aktarımı performansı sınırlar. Bu duruma von Neumann darboğazı (von Neumann bottleneck) denir. Önbellek, bu darboğazı hafifletmek için vardır.
1.8.2von Neumann ve Harvard
Darboğaza karşı farklı bir yol, komutları ve verileri ayrı belleklerde tutmaktır. Bu düzene Harvard mimarisi (Harvard architecture) denir; adı, bu düzenle kurulan Harvard Mark I bilgisayarından gelir.
| Özellik | von Neumann | Harvard |
|---|---|---|
| Bellek | Komut ve veri ortak bellekte | Komut ve veri ayrı belleklerde |
| Yol | Tek yol: komut ve veri sırayla taşınır | İki yol: komut ve veri aynı anda okunur |
| Esneklik | Program, veri gibi yüklenip değiştirilebilir | Program belleği çoğunlukla sabittir (flash) |
| Yaygın kullanım | Genel amaçlı bilgisayarlar | Mikrodenetleyiciler, sinyal işlemciler |
Harvard düzeninin üstünlüğü hızdır: bir komut getirilirken önceki komutun verisi aynı anda okunabilir. Bedeli ise esnekliktir. Bir mikrodenetleyicide program, flash belleğe bir kez yazılır ve cihaz çalışırken değişmez; bir çamaşır makinesi için bu yeterlidir. Genel amaçlı bir bilgisayarın ise her gün yeni programlar yüklemesi gerekir.
Günümüz işlemcileri iki düzeni birleştirir. Ana bellek ortaktır; programlar ve veriler aynı RAM'e yüklenir. Çekirdeğe en yakın birinci düzey (L1) önbellek ise komut ve veri için ayrıdır. Bu karma düzene değiştirilmiş Harvard mimarisi denir. Programcı ortak belleği görür, işlemci ise içeride iki ayrı yoldan yararlanır.
1.8.3Komutların kodlanması
Depolanan program fikrinin bir gereği olarak komutlar da bitlerle yazılır. Bir makine komutu genellikle iki bilgi taşır: yapılacak işlem ve kullanılacak değer ya da adres.
- Opcode (işlem kodu): işlemin türünü tanımlar; örneğin ADD.
- İşlenen (operand): işlemde kullanılacak yazmaç, değer ya da adres; örneğin 1005 ya da R1, R2.
- Komut biçimi (instruction format): bit alanlarının sayısını ve yerini belirler. Her mimarinin kendi biçimi vardır.
- Komut kümesi (instruction set): işlemcinin doğrudan anlayabildiği komutların tamamıdır.
Bu notun örnek işlemcisi için varsayımsal bir biçim kullanılır: komut 16 bittir, ilk 4 bit opcode, kalan 12 bit adrestir. Opcode'lar HALT = 0000, LOAD = 0001, ADD = 0010, STORE = 0011'dir.
Tablo 1.15'teki programın dört komutunu bu biçimle kodlayalım.
- Adresler: 1004 = 3EC16 = 0011 1110 1100; 1005 = 3ED16 = 0011 1110 1101; 1006 = 3EE16 = 0011 1110 1110. (Denetim: 3 · 256 + 14 · 16 + 12 = 1004.)
- LOAD 1004: 0001 0011 1110 1100 = 13EC16.
- ADD 1005: 0010 0011 1110 1101 = 23ED16.
- STORE 1006: 0011 0011 1110 1110 = 33EE16.
- HALT: 0000 0000 0000 0000 = 000016; adres alanı kullanılmaz.
12 bitlik adres alanıyla 212 = 4.096 farklı adres (0–4095) gösterilebilir. Daha büyük bir bellek için adres alanı genişletilmelidir.
Bu kodlama, 1.7.5'teki gözlemi somutlaştırır. 1004 adresindeki 12 sayısı 16 bitle 0000 0000 0000 1100 olarak yazılır. Bu dizinin ilk dört biti 0000'dır, yani HALT'ın opcode'u. Aynı bit dizisi veri olarak okunursa 12, komut olarak okunursa bir HALT komutudur.
1.8.4Komut kümesi tasarımı: RISC ve CISC
Komut kümesinin nasıl tasarlanacağı konusunda iki ana yaklaşım vardır.
| Özellik | RISC | CISC |
|---|---|---|
| Komut kümesi | Az sayıda, basit komut | Çok sayıda, karmaşık komut |
| Komut uzunluğu | Sabit (ör. 32 bit) | Değişken (x86-64'te 1–15 bayt) |
| Bellek erişimi | Yalnız LOAD / STORE komutları | İşlem komutları doğrudan bellek işleneni alabilir |
| Örnek | ARM, RISC-V | x86-64 |
RISC (reduced instruction set computer, azaltılmış komut kümeli bilgisayar) yaklaşımında komutlar basit ve eşit uzunluktadır. Belleğe yalnız LOAD ve STORE erişir; hesaplar yalnız yazmaçlar üzerinde yapılır. CISC (complex instruction set computer, karmaşık komut kümeli bilgisayar) yaklaşımında ise tek bir komut bellekten okuyup hesaplayıp belleğe yazabilir. Fark, bellekteki a = a + b işlemi üzerinde görülür.
RISC:
LOAD R1, a
LOAD R2, b
ADD R1, R1, R2
STORE R1, aCISC (x86-64):
MOV EAX, [b]
ADD [a], EAXRISC'te dört komut gerekir: iki okuma, bir toplama, bir yazma. x86-64'te iş iki komutla biter: ilk komut b'yi EAX yazmacına alır, ikincisi doğrudan bellekteki a'ya ekler. Köşeli parantez “bu adresteki bellek içeriği” demektir. Ama komut sayısının azalması işin azaldığı anlamına gelmez. İkinci komut, işlemcinin içinde yine okuma, toplama ve yazma adımlarına bölünür. Bugün x86-64 işlemciler de karmaşık komutları içeride RISC benzeri küçük adımlara çevirerek yürütür. Telefonların büyük çoğunluğu ARM, kişisel bilgisayarların çoğu x86-64 kullanır; son yıllarda ARM tabanlı dizüstü bilgisayarlar da yaygınlaşmıştır.
1.8.5Mimari sınırlar
Bilgisayar ne kadar hızlı olursa olsun bazı sınırlarla çalışır. Bu sınırların çoğu, bu notta ayrı ayrı karşılaşılan olguların genel adlarıdır.
- Sonlu bellek: Aynı anda saklanabilecek veri miktarı sınırlıdır.
- Sonlu hız: Her komut zaman ve enerji tüketir.
- Temsil hataları: Sınırlı bit sayısı taşma (1.2.8) ve yuvarlama (1.3.3) üretebilir.
- Veri darboğazı: CPU, bellekteki veriyi beklerken boş kalabilir.
- Paralellik sınırı: Her problem eşit ölçüde paralelleştirilemez. Bir işin bir adımı öncekinin sonucuna bağlıysa, dalgalı elde toplayıcıdaki gibi, adımlar aynı anda yapılamaz.
- Güvenlik: Program ve veri aynı sistem kaynaklarını paylaştığı için programların birbirinden yalıtılması gerekir. Depolanan program fikrinin gücü, yani verinin komut gibi çalıştırılabilmesi, kötü amaçlı yazılımların da kapısıdır.
Şekil 1.33'teki sayılar darboğazın boyutunu gösterir. 3 GHz'lik bir işlemci saniyede 3 · 109 tur atar; bir tur 1 / (3 · 109) s ≈ 0,33 ns sürer. 100 ns'lik bir RAM erişimi bu yüzden yaklaşık 100 ÷ 0,33 ≈ 300 tura denk gelir. Önbellek olmasaydı işlemci zamanının büyük bölümünü bekleyerek geçirirdi.
1.8.6Performans: komut sayısı, CPI ve saat hızı
Bir işlemcinin ne kadar hızlı olduğu sorusu tek bir sayıyla cevaplanamaz. Bir programın CPU'da geçirdiği süre üç etkenin çarpımıdır:
CPU süresi = komut sayısı × CPI ÷ saat hızı
- Komut sayısı: programın yürüttüğü makine komutlarının sayısı. Algoritmaya, derleyiciye ve komut kümesine bağlıdır.
- CPI (cycles per instruction, komut başına saat turu): bir komutun ortalama kaç saat turu sürdüğü. İşlemcinin iç tasarımına ve bellek beklemelerine (önbellek ıskaları) bağlıdır.
- Saat hızı: işlemcinin saniyede attığı tur sayısı; 3 GHz, saniyede 3 · 109 tur demektir. Üretim teknolojisine, güç ve ısı sınırlarına bağlıdır.
| Durum | Saat hızı | CPI | Süre |
|---|---|---|---|
| İşlemci A | 3 GHz | 1,5 | 2 · 109 × 1,5 ÷ (3 · 109) = 1,0 s |
| İşlemci B | 4 GHz | 2,5 | 2 · 109 × 2,5 ÷ (4 · 109) = 1,25 s |
| A, daha iyi derleyiciyle (1,6 · 109 komut) | 3 GHz | 1,5 | 1,6 · 109 × 1,5 ÷ (3 · 109) = 0,8 s |
A ve B işlemcilerinin süresini adım adım hesaplayalım.
- Toplam tur (A): 2 · 109 komut × 1,5 tur = 3 · 109 tur.
- Süre (A): 3 · 109 tur ÷ (3 · 109 tur/s) = 1,0 s.
- Toplam tur (B): 2 · 109 × 2,5 = 5 · 109 tur.
- Süre (B): 5 · 109 ÷ (4 · 109) = 1,25 s.
- Karşılaştırma: 1,25 ÷ 1,0 = 1,25. B'nin saat hızı daha yüksek olduğu hâlde A 1,25 kat hızlıdır.
Tablonun üçüncü satırı, donanım değişmeden de hızlanılabileceğini gösterir. Daha iyi bir derleyici aynı programı 1,6 · 109 komuta indirirse A'nın süresi 0,8 s'ye düşer. Demek ki tek bir sayı, örneğin yalnız saat hızı, performansı belirlemez. Bir işlemciyi değerlendirmenin en güvenilir yolu, gerçek programları üzerinde çalıştırıp süreyi ölçmektir.
GHz değeri yüksek olan işlemcinin her zaman daha hızlı olduğunu sanmak. Saat hızı yalnız denklemin bir çarpanıdır. Bir tur daha kısa sürse de komut başına daha çok tur gerekiyorsa (yüksek CPI) toplam süre uzayabilir.
1.9Programlama soyutlama düzeyleri
1.9.1Problemden devreye
Bir programcı Java'da total = a + b; yazarken transistörleri, yazmaçları ya da opcode'ları düşünmez. Bunu mümkün kılan, bilgisayar sistemlerinin katmanlar hâlinde kurulmasıdır. Her katman, altındakinin ayrıntısını gizler ve üstündekine daha kullanışlı bir görünüm sunar. Ayrıntıyı gizleyip yalnız gerekeni göstermeye soyutlama (abstraction) denir.
total = a + b;), derleyici ya da sanal makine (Java bayt kodu iadd), assembly (ADD R1, b), makine komutları (0010 0011 1110 1101) ve sayısal devreler (toplayıcı, transistörler). Örnek satırlar gösterim amaçlıdır.Şekil 1.34, aynı “iki sayıyı topla” fikrinin katmanlar boyunca aldığı biçimleri gösterir.
- Problem ve algoritma: İnsan dilinde ne yapılacağı ve hangi adımlarla yapılacağı.
- Yüksek seviyeli dil: Java, Python, C gibi diller. Okunabilir ve taşınabilirdir: aynı Java kodu farklı işlemcilerde çalışır.
- Derleyici ya da sanal makine: Yüksek seviyeli kodu alt düzeye çevirir. Java derleyicisi kodu önce bayt koduna (ara koda) çevirir; Java sanal makinesi bu kodu çalıştığı işlemcinin komutlarına dönüştürür.
- Assembly: Makine komutlarının insanın okuyabileceği adlarla (LOAD, ADD) yazılmış hâlidir. Her assembly komutu genellikle tek bir makine komutuna karşılık gelir.
- Makine komutları: İşlemcinin doğrudan yürüttüğü bit dizileri. Assembly ve makine kodu belirli bir işlemci mimarisine özgüdür: ARM için yazılmış makine kodu x86-64'te çalışmaz.
- Sayısal devreler: Komutu gerçekten yürüten kapılar, toplayıcılar ve transistörler.
Soyutlamanın değeri, her katmanda çalışan kişinin yalnız kendi katmanını ve hemen altındakinin sunduğu görünümü bilmesinin yetmesidir. Bir uygulama programcısı toplayıcının iç yapısını bilmeden doğru program yazabilir. Yine de alt katmanları tanımak, taşma ve yuvarlama gibi “beklenmedik” davranışların nedenini anlamayı sağlar.
1.9.2Kod izleme: total = a + b
Tek bir Java satırının işlemcide neye dönüştüğü, yazmaçlı bir assembly diliyle gösterilebilir. Bellekte a = 12 ve b = 8 vardır:
// Java: int total = a + b;
LOAD R1, a ; R1 ← a
ADD R1, b ; R1 ← R1 + b
STORE R1, total ; total ← R1| Komut | Ne olur? | R1 | a | b | total |
|---|---|---|---|---|---|
| başlangıç | — | 0 | 12 | 8 | 0 |
LOAD R1, a | a'nın değeri bellekten yazmaca taşınır | 12 | 12 | 8 | 0 |
ADD R1, b | b okunur, ALU 12 + 8 = 20 hesaplar, sonuç R1'e yazılır | 20 | 12 | 8 | 0 |
STORE R1, total | R1'deki 20, total değişkeninin yerine yazılır | 20 | 12 | 8 | 20 |
total değişkeninin yerine yazılır; Java satırı tamamlanmıştır.Tek bir Java satırı üç makine komutuna dönüşmüştür. Önce değer bellekten yazmaca yüklenir, çünkü ALU yalnız yazmaçlarla çalışır. Sonra hesap yapılır, en son sonuç belleğe geri yazılır. Bu yükle–hesapla–yaz örüntüsü, RISC tarzı işlemcilerde neredeyse her atama satırında görülür. Aynı şablonla int fark = a - b; satırı da LOAD R1, a, SUB R1, b, STORE R1, fark komutlarına dönüşür. Çıkarmada işlenenlerin sırası önemlidir: önce a yüklenir, sonra ondan b çıkarılır.
Bu küçük örnek, notun başındaki bütün kavramları bir araya getirir. a ve b bellekte ikiye tümleyen bit dizileridir; ADD komutu 16 bitlik bir sayı olarak bellekte durur; işlemci onu getir–çöz–yürüt döngüsüyle alır; toplamayı tam toplayıcı zinciri yapar; sonuç veri yoluyla belleğe yazılır. Programcının yazdığı tek satır, bu katmanların hepsinden geçerek çalışır.
1.10Özet
- Bilgisayar bütün verileri bit dizileriyle temsil eder. n bitle 2n farklı dizi yazılır. Bir dizinin anlamını kodlama ve veri tipi belirler; aynı 01000001 hem 65 hem “A”dır.
- Konumsal sistemde basamak ağırlıkları tabanın kuvvetleridir. Onluktan ikiliye art arda 2'ye bölünerek geçilir. 8 = 23 ve 16 = 24 olduğu için sekizli ve onaltılığa bitler sağdan üçerli ve dörderli gruplanarak çevrilir.
- Negatif tam sayılar ikiye tümleyen ile saklanır: bitler çevrilir, 1 eklenir. n bitlik aralık −2n−1 … 2n−1 − 1'dir; 8 bitte −128 … 127. Çıkarma, a + (−b) olarak toplama devresiyle yapılır.
- Sonuç yazmaca sığmazsa taşma olur ve yüksek bitler atılır: 8 bitte 200 + 100 = 44. Elde işaretsiz, işaretli taşma işaretli yorumun sorunudur; aynı işaretli iki sayının toplamı ters işaretli çıkarsa işaretli taşma vardır.
- Kayan noktalı sayılar IEEE 754'e göre işaret, üs (sapmalı) ve kesir alanlarıyla saklanır:
float1 + 8 + 23,double1 + 11 + 52 bit. Paydası 2'nin kuvveti olmayan kesirler (0,1 gibi) tam saklanamaz; kayan noktalı sonuçlar toleransla karşılaştırılır. İkili gösterim tam sayılar için kesin, kayan nokta bazı ondalıklar için yaklaşıktır. - ASCII 7 bitlik bir koddur (128 karakter). Unicode her karaktere bir kod noktası verir; UTF-8 bu numarayı 1–4 baytla yazar: ASCII 1, Türkçe harfler 2, € 3, emoji 4 bayt. Karakter sayısı ile bayt sayısı aynı olmayabilir.
- Bit işlemleri (AND, OR, XOR, NOT, kaydırma) maskelerle tek tek bitleri değiştirir; sola kaydırma 2 ile çarpar, sağa kaydırma 2'ye böler.
- Mantık kapıları toplayıcı gibi devrelerin yapı taşıdır. Yarım toplayıcı SUM = A XOR B, CARRY = A AND B üretir; tam toplayıcı gelen eldeyi de toplar. Çok bitli toplama, tam toplayıcı zinciriyle yapılır.
- Depolanan program yaklaşımında komutlar ve veriler aynı bellekte sayılar olarak tutulur. CPU, komutları PC ve IR yazmaçlarının yardımıyla getir–çöz–yürüt döngüsünde yürütür. Adres, kontrol ve veri yolları CPU'yu belleğe bağlar. Dallanma komutları yürütme aşamasında PC'yi değiştirerek döngü ve karar yapılarını kurar.
- Bellek bayt adreslidir; çok baytlı değerler büyük uçlu ya da küçük uçlu sırayla yazılır.
- von Neumann mimarisinde komut ve veri ortak bellekte ve ortak yoldadır; bu, von Neumann darboğazını doğurur. Harvard mimarisi bellekleri ayırır; günümüz işlemcileri ikisini birleştirir. RISC az ve basit, CISC çok ve karmaşık komutla çalışır.
- CPU süresi = komut sayısı × CPI ÷ saat hızı; tek bir sayı performansı belirlemez.
- Yüksek seviyeli diller donanım ayrıntılarını soyutlar: tek bir Java satırı birkaç makine komutuna dönüşür.
| Hesap | Yöntem | Örnek |
|---|---|---|
| Onluk → ikili | art arda 2'ye böl, kalanları sondan oku | 45 = 1011012 |
| İkili → sekizli / onaltılık | sağdan 3'erli / 4'erli grupla | 101101 = 558 = 2D16 |
| Negatif (ikiye tümleyen) | bitleri çevir, 1 ekle | −5 = 11111011 |
| Taşma (8 bit işaretsiz) | sonuçtan 256 çıkar | 200 + 100 → 44 |
| Kesir → ikili | art arda 2 ile çarp, tam kısımları al | 0,375 = 0,0112 |
IEEE 754 float | işaret, üs + 127, baştaki 1'siz kesir | −6,25 = C0C8000016 |
| UTF-8 (2 bayt) | 11 bit → 110xxxxx 10xxxxxx | “ğ” = C4 9F |
| CPU süresi | komut sayısı × CPI ÷ saat hızı | 2 · 109 × 1,5 ÷ 3 GHz = 1 s |
1.11Kavram listesi
Terimlerin İngilizce karşılıkları ve öteki sunumlardaki kullanımları terim sözlüğünde bulunur.
- bit / bayt bit / byte
- kodlama encoding
- veri tipi data type
- taban base (radix)
- ikili / sekizli / onluk / onaltılık binary / octal / decimal / hexadecimal
- işaretli / işaretsiz signed / unsigned
- birler tümleyeni one's complement
- ikiye tümleyen two's complement
- taşma overflow
- işaretli taşma signed overflow
- elde carry
- kayan nokta floating point
- IEEE 754
- işaret / üs / kesir sign / exponent / fraction
- sapma bias
- hassasiyet precision
- yuvarlama rounding
- ASCII
- kontrol karakteri control character
- bit işlemi bitwise operation
- bit maskesi bit mask
- bit kaydırma bit shift
- Unicode
- kod noktası code point
- UTF-8
- dizgi string
- kod birimi code unit
- alan / kayıt / dosya biçimi field / record / file format
- mantık kapısı logic gate
- yarım toplayıcı half adder
- tam toplayıcı full adder
- dalgalı elde toplayıcı ripple-carry adder
- depolanan program stored program
- getir–çöz–yürüt döngüsü fetch-decode-execute cycle
- program sayacı (PC) program counter
- komut yazmacı (IR) instruction register
- akümülatör (ACC) accumulator
- yazmaç register
- kontrol birimi control unit
- önbellek cache
- veri yolu / adres yolu / kontrol yolu data / address / control bus
- bellek adresi memory address
- bayt sırası endianness
- dallanma branch
- von Neumann mimarisi von Neumann architecture
- von Neumann darboğazı von Neumann bottleneck
- Harvard mimarisi Harvard architecture
- opcode (işlem kodu) opcode
- komut biçimi instruction format
- RISC / CISC
- CPI (komut başına saat turu) cycles per instruction
- soyutlama abstraction
- assembly assembly language
- makine komutu machine instruction
1.12Kendini Dene
156 sayısı ikili, sekizli ve onaltılık tabanda nasıl yazılır?
Cevap
156 = 128 + 16 + 8 + 4 olduğundan 100111002'dir. Üçerli gruplar 010 | 011 | 100 → 2348. Dörderli gruplar 1001 | 1100 → 9C16. Denetim: 9 · 16 + 12 = 156.
8 bit ikiye tümleyen gösterimde −20 nasıl yazılır? 10110100 dizisi hangi sayıdır?
Cevap
+20 = 00010100; bitler çevrilir: 11101011; 1 eklenir: 11101100 = −20. 10110100'ün işaret biti 1'dir, sayı negatiftir. Bitler çevrilip 1 eklenince 01001100 = 76 bulunur; dizi −76'dır. Denetim: −128 + 32 + 16 + 4 = −76.
8 bitte 90 + (−30) ve 70 + 80 işlemlerinde elde ve işaretli taşma var mıdır? Sonuçlar doğru mudur?
Cevap
90 + (−30): 01011010 + 11100010 = 1 00111100. Elde 1'dir ve atılır; kalan 00111100 = 60 doğrudur. İşaretleri farklı iki sayının toplamı taşmaz. 70 + 80: 01000110 + 01010000 = 10010110. Elde 0'dır, ama iki pozitifin toplamı negatif görünür: işaretli taşma vardır. Dizi −106 okunur; doğru sonuç 150, işaretli 8 bitlik aralığa sığmaz.
0,625 ve 0,2 sayılarının ikili açılımları nedir? Hangisi
doubleile tam saklanır?Cevap
0,625 × 2 = 1,25 → 1; 0,25 × 2 = 0,5 → 0; 0,5 × 2 = 1,0 → 1 ve kesir 0'dır: 0,1012, tam saklanır (5/8, payda 2'nin kuvveti). 0,2 × 2 = 0,4 → 0; 0,8 → 0; 1,6 → 1; 1,2 → 1; sonra kesir yine 0,2 olur: 0,0011 0011…2, sonsuza dek tekrarlar ve tam saklanamaz.
0,75 sayısının 32 bitlik
floatkarşılığı onaltılık olarak nedir?Cevap
0,75 = 0,112 = 1,1 × 2−1. İşaret 0; üs −1 + 127 = 126 = 01111110; kesir 1 ve yirmi iki 0. Bitler: 0 01111110 1000…0 → 0011 1111 0100 0000 … → 3F40000016.
“ü” (U+00FC) harfinin UTF-8 baytları nedir? “Gün” kelimesi kaç karakter, kaç bayttır?
Cevap
00FC16 = 252 = 111111002; 7 bite sığmaz, 11 bitle 00011111100 yazılır. 5 + 6 bölmesi: 00011 | 111100. Baytlar 110 00011 = C3 ve 10 111100 = BC: “ü” = C3 BC. “Gün” 3 karakterdir; G ve n 1'er, ü 2 bayt olduğundan 4 bayttır.
Dört tam toplayıcılı zincirde 0101 + 0011 toplanıyor. Her basamakta çıkan elde nedir, sonuç ve son elde ne olur?
Cevap
i = 0: 1 + 1 + 0 = 102, basamak 0, elde 1. i = 1: 0 + 1 + 1 = 102, basamak 0, elde 1. i = 2: 1 + 0 + 1 = 102, basamak 0, elde 1. i = 3: 0 + 0 + 1 = 1, basamak 1, elde 0. Sonuç 10002 = 8 = 5 + 3; son elde 0 olduğu için 4 bite sığmıştır.
Dallanma programında 1010 adresindeki sayaç 2 yerine 3 olsaydı program bitince
toplamne olurdu? Kaç komut getirilirdi?Cevap
Döngü 3 tur döner ve toplam = 3 + 2 + 1 = 6 olur. Her tur 8 komuttur; çıkışta LOAD, JUMPZ ve HALT getirilir: 8 · 3 + 3 = 27 komut.
3 · 109 komutluk bir program, CPI'si 2 olan 2 GHz'lik bir işlemcide ve CPI'si 1,2 olan 3 GHz'lik bir işlemcide kaç saniye sürer? İkincisi kaç kat hızlıdır?
Cevap
Birinci: 3 · 109 × 2 ÷ (2 · 109) = 3 s. İkinci: 3 · 109 × 1,2 ÷ (3 · 109) = 1,2 s. 3 ÷ 1,2 = 2,5; ikinci işlemci 2,5 kat hızlıdır.
32 bitlik 0x0A0B0C0D değeri, küçük uçlu bir makinede 200–203 adreslerine nasıl yazılır? Büyük uçlu bir makine bu dört baytı tek seferde okursa hangi değeri görür?
Cevap
Küçük uçlu düzende en az anlamlı bayt en küçük adrese gider: 200: 0D, 201: 0C, 202: 0B, 203: 0A. Büyük uçlu makine ilk baytı en anlamlı sayar ve 0x0D0C0B0A görür.
1.13İleri okuma
- C. Petzold, Code: The Hidden Language of Computer Hardware and Software (2. baskı, Microsoft Press, 2022): Mors alfabesinden ve ampullü devrelerden başlayarak kapıları, toplayıcıları, belleği ve basit bir işlemciyi matematik bilgisi gerektirmeden adım adım kurar.
- N. Nisan ve S. Schocken, The Elements of Computing Systems (2. baskı, MIT Press, 2021): NAND kapısından başlayıp ALU, CPU, assembler ve derleyiciye kadar bütün katmanları kurdurarak bu nottaki soyutlama düzeylerini uygulamalı olarak işler.
- R. E. Bryant ve D. R. O'Hallaron, Computer Systems: A Programmer's Perspective (3. baskı, 2016), Bölüm 2: tam sayıların ikiye tümleyen gösterimini, taşmayı ve IEEE 754'ü programcı gözüyle ve çok sayıda örnekle anlatır.
- D. Goldberg, “What Every Computer Scientist Should Know About Floating-Point Arithmetic”, ACM Computing Surveys 23(1), 1991: kayan noktalı hesaptaki yuvarlama hatalarının nedenlerini ve sonuçlarını ayrıntılı ele alan klasik makale.
1.14Kaynaklar
- D. A. Patterson, J. L. Hennessy, Computer Organization and Design: The Hardware/Software Interface, Morgan Kaufmann.
- R. E. Bryant, D. R. O'Hallaron, Computer Systems: A Programmer's Perspective, 3. baskı, Pearson, 2016.
- The Unicode Consortium, The Unicode Standard, https://www.unicode.org/standard/standard.html.
- IEEE, IEEE Standard for Floating-Point Arithmetic (IEEE Std 754-2019), 2019.
- C. Petzold, Code: The Hidden Language of Computer Hardware and Software, 2. baskı, Microsoft Press, 2022.
- N. Nisan, S. Schocken, The Elements of Computing Systems: Building a Modern Computer from First Principles, 2. baskı, MIT Press, 2021.
- D. Goldberg, “What Every Computer Scientist Should Know About Floating-Point Arithmetic”, ACM Computing Surveys, 23(1), 5–48, 1991.