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.
Girişklavye, sensörCPUkomutları yürütürBellekkomut + veriÇıkış: ekran, yazıcıyazoku
Şekil 1.1: Bilgisayarın temel parçaları. Giriş birimleri CPU'ya veri verir. CPU belleğe değer yazar ve bellekten değer okur. Sonuç çıkış birimine gider.

Ş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.

Sık yapılan hata

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.

Tanım 1.1

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.

Bilgiharf “A”KodlamaASCII 65Bit dizisi01000001Programküçük harfeçevir: +32Çıktıekranda “a”01000001 (65) + 00100000 (32) = 01100001 (97) = “a”
Şekil 1.2: “A” harfinin yolculuğu. Harf ASCII ile 65 sayısına, 65 de 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.

Tanım 1.2

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.

Tablo 1.1: Sık kullanılan sayı sistemleri. Dört örnek de aynı sayıyı (on üçü) gösterir.
SistemTabanRakamlarÖrnekDeğeri
İkili20–1110121 · 8 + 1 · 4 + 0 · 2 + 1 · 1 = 13
Sekizli80–71581 · 8 + 5 · 1 = 13
Onluk100–913101 · 10 + 3 · 1 = 13
Onaltılık160–9, A–FD1613 · 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.

Örnek 1.1

45 sayısını ikili tabana çevirelim.

Bölünen÷ 2 bölümKalan (bit)Basamak ağırlığı
452211
221102
11514
5218
21016
10132

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
90

Java 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.

45₁₀ =101101₂(32 + 8 + 4 + 1)3'lü grup:101= 5101= 5→ 55₈(5·8 + 5 = 45)4'lü grup:0010= 2 = 21101= 13 = D→ 2D₁₆(2·16 + 13 = 45)
Şekil 1.3: 4510 = 1011012'nin bit gruplamasıyla sekizli ve onaltılığa çevrilmesi. Üçerli gruplar 101 ve 101'dir: 558. Dörderli gruplar 0010 ve 1101'dir: 2D16.

Ş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.

Örnek 1.2

156 sayısını ikili, sekizli ve onaltılık tabanda yazalım.

  1. İ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.
  2. 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.
  3. 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.
Sık yapılan hata

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.

Tanım 1.3

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.

00000000110010200113010040101501106011171000−81001−71010−61011−51100−41101−31110−21111−14 bitilk bit 1 → negatif7 + 1 → −8 (taşma)
Şekil 1.4: 4 bitlik ikiye tümleyen çemberi. 0000'dan 0111'e kadar diziler 0 … 7 pozitif sayılarıdır; 1000'dan 1111'e kadar diziler −8 … −1 negatif sayılarıdır. İlk biti 1 olan her dizi negatiftir. 7'ye 1 eklenince çemberde −8'e geçilir: bu bir taşmadı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).
Tablo 1.2: Bit sayısına göre işaretli (ikiye tümleyen) ve işaretsiz aralıklar. Java'daki karşılığı olan tipler son sütundadır.
Bitİşaretli aralıkİşaretsiz aralıkJava tipi
8−128 … 1270 … 255byte
16−32.768 … 32.7670 … 65.535short
32−2.147.483.648 … 2.147.483.6470 … 4.294.967.295int
64yaklaşık ±9,22 · 10180 … yaklaşık 1,84 · 1019long

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:

  1. Bütün bitler ters çevrilir (0 ↔ 1). Ortaya çıkan diziye birler tümleyeni (one's complement) denir.
  2. Sonuca 1 eklenir.
Örnek 1.3

8 bitte −5'i yazalım ve sonucu denetleyelim.

  1. +5'i yaz: 5 = 4 + 1 olduğundan +5 = 00000101.
  2. Bitleri ters çevir: 00000101 → 11111010. Bu dizi birler tümleyenidir ve henüz −5 değildir.
  3. 1 ekle: 11111010 + 1 = 11111011. En soldaki 1, sayının negatif olduğunu gösterir.
  4. 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.
+500000101−511111011toplam100000000taşan elde atılır8 bitte kalan: 00000000 = 0 ✓
Şekil 1.5: +5 ile −5'in 8 bitlik toplamı. Toplam 9 bit çıkar. Taşan 9. bit (elde) atılır, 8 bitte 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.

Sık yapılan hata

İ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
Tablo 1.3: 5 − 9 işleminin izlemesi. Her satır, sözde kodun bir satırı yürütüldükten sonraki durumu gösterir.
SatırDeğişkenBitlerAçıklama
1a00000101+5
2b00001001+9 = 8 + 1
3ters11110110bütün bitler çevrildi
4eksi111101111 eklendi: −9 (işaretsiz okunursa 247 = 256 − 9)
5s111111005 + 247 = 252; 256'ya ulaşmadığı için elde çıkmaz
6yazılan−4iş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.

76543210s11111100işaret biti 1 → negatifNOT s00000011+100000100= 4 → s = −4
Şekil 1.6: 5 − 9 sonucunun okunması. 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?

Örnek 1.4
  1. İşlenenler: 200 = 128 + 64 + 8 → 11001000; 100 = 64 + 32 + 4 → 01100100.
  2. Gerçek toplam: 300 = 256 + 32 + 8 + 4 → 1001011002. Sonuç 9 bit gerektirir.
  3. Yazmaçta kalan: Yazmaç yalnız 8 bit tutar. Ağırlığı 256 olan 9. bit kaybolur ve geriye 00101100 = 44 kalır.
  4. Kısa yol: 300 − 256 = 44.
20011001000+ 10001100100= 3001001011009. bit8 bitte kalan: 00101100 = 44
Şekil 1.7: 8 bitte 200 + 100. Gerçek toplam 300 = 1001011002 9 bit gerektirir. 9. bit (256) yazmaca sığmaz; 8 bitte kalan 00101100 = 44'tür.
Tanım 1.4

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.
Tablo 1.4: 8 bitte dört toplama. Elde ve işaretli taşma birbirinden bağımsız oluşur.
İşlemİkili toplamEldeİşaretli taşmaSonuç
5 + (−9)00000101 + 11110111 = 111111000yok−4 ✓
(−1) + (−1)11111111 + 11111111 = 1 111111101yok−2 ✓
100 + 5001100100 + 00110010 = 100101100var−106 ✗
(−100) + (−50)10011100 + 11001110 = 1 011010101var+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.

Örnek 1.5

8 bitte 120 + 10 ve (−3) + (−5) işlemlerinde elde ve işaretli taşma var mıdır?

  1. 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.
  2. (−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.
Sık yapılan hata

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 float yaklaşık 1,2 · 10−38 ile 3,4 · 1038 arasındaki değerleri tutabilir.
  • Sınırlı hassasiyet. Anlamlı basamak sayısı sabittir. float yaklaşık 7, double yaklaşı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.
0.3 yazınca saklanan değer0.299999999999999988900.1 + 0.2 sonucu0.30000000000000004441gerçek 0,3 (double ile tam yazılamaz)iki komşu double arası ≈ 5,55 × 10⁻¹⁷ → 0.1 + 0.2 == 0.3 sonucu false
Şekil 1.8: 0,3 çevresinde sayı doğrusu. Programda 0,3 yazıldığında saklanan 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.

Tanım 1.5

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.

Örnek 1.6

0,375 sayısını ikiliye çevirelim.

  1. 0,375 × 2 = 0,75 → tam kısım 0, ilk bit 0.
  2. 0,75 × 2 = 1,5 → tam kısım 1, ikinci bit 1; kesir 0,5 ile devam edilir.
  3. 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.

Tablo 1.5: 0,1'in ikili açılımı. Beşinci adımdan sonra kesir yeniden 0,2 olur ve açılım döngüye girer.
AdımİşlemBitKalan kesir
10,1 × 2 = 0,200,2
20,2 × 2 = 0,400,4
30,4 × 2 = 0,800,8
40,8 × 2 = 1,610,6
50,6 × 2 = 1,210,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

Kesri sürekli 2 ile çarp; tam kısım bir sonraki bit0,1 × 2 = 0,2bit 00,2 × 2 = 0,4bit 00,4 × 2 = 0,8bit 00,8 × 2 = 1,6bit 10,6 × 2 = 1,2bit 10,1₁₀ =0,00011…0,2 tekrar geldi → “0011” sonsuza dek sürerSonlu bitle kesilir → küçük yuvarlama hatası
Şekil 1.9: 0,1'in ikiliye çevrilmesi. Beş çarpmadan sonra 0,2 kesri yeniden görülür; “0011” deseni sonsuza dek sürer. Sonlu bitle kesilince küçük bir yuvarlama hatası oluşur.

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
true

Matematikte 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.

Sık yapılan hata

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.

Tablo 1.6: IEEE 754 biçimleri. Sapma, üs alanına yazılmadan önce gerçek üsse eklenen sabittir.
TipToplamİşaretÜsKesirSapmaOnluk basamak
float32 bit1823127yaklaşık 7
double64 bit111521023yaklaşı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.
00111101110011001100110011001101işaret (1)üs (8 bit)kesir (23 bit)0+01111011₂ = 123üs = 123 − 127 = −41,10011001100110011001101₂≈ 1,6000000238değer = (−1)^işaret × 1,kesir × 2^(üs − 127)0,1 (float) = 0x3DCCCCCD≈ 1,6000000238 × 2⁻⁴ ≈ 0,10000000149
Şekil 1.10: 0,1'in 32 bitlik 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.100000001490116119384765625

Float.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))
Örnek 1.7

−6,25 sayısının 32 bitlik float karşılığını bulalım.

  1. İşaret: Sayı negatif olduğundan işaret biti 1'dir. Geri kalan işlemler 6,25 üzerinde yapılır.
  2. İ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.
  3. 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.
  4. Üs alanı: Sapma eklenir: 2 + 127 = 129 = 100000012.
  5. 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.
  6. Bitleri birleştirme: 1 | 10000001 | 10010000000000000000000. 32 bit dörderli gruplanır: 1100 0000 1100 1000 0000 0000 0000 0000 → C0C8000016.
−6,25 = C0C80000₁₆işaretüs (8 bit)kesir (23 bit)11000000110010000000000000000000C0C80000
Şekil 1.11: −6,25'in 32 biti. İşaret 1, üs 10000001 (129), kesir 1001 ve on dokuz 0. Dörderli gruplar C, 0, C, 8, 0, 0, 0, 0'dır: −6,25 = 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ık yapılan hata

Ü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 olarak double'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.

Tablo 1.7: Bazı ASCII kodları. Büyük harfler 65'ten, küçük harfler 97'den, rakamlar 48'den başlar ve ardışıktır.
KarakterOnlukOnaltılıkİkili (8 bit)
“A”654101000001
“Z”905A01011010
“a”976101100001
“z”1227A01111010
“0”483000110000
“7”553700110111
boşluk322000100000
satır sonu (LF)100A00001010

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.
“A” = 6501000001“a” = 970110000132'ler basamağıBüyük ve küçük harfyalnızca bir bitte ayrılır:97 − 65 = 32
Şekil 1.12: “A” = 65 = 01000001 ile “a” = 97 = 01100001 yalnız 32'ler basamağındaki bitte farklıdır.
Sık yapılan hata

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
Tablo 1.8: “G” harfi üzerinde bit işlemleri. Maske m = 00100000 (32).
İşlemBitlerDeğerKural
c = “G”0100011171 = “G”başlangıç
c OR m01100111103 = “g”maskede 1 olan bit 1 olur
k AND (NOT m)0100011171 = “G”NOT m = 11011111; maskedeki bit 0'a çekilir
b XOR m01100111103 = “g”maskedeki bit ters çevrilir
c << 110001110142 = 71 × 2bitler bir basamak sola kayar, sağdan 0 girer
c >> 10010001135 = 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.

76543210c01000111= 71<< 1her bit bir basamak sola, sağdan 0 girers10001110= 142 = 71 × 2
Şekil 1.13: “G” = 71'in bir basamak sola kaydırılması. Her bit bir basamak yükselir, sağdan 0 girer: 10001110 = 142 = 71 × 2.

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
35

Bit 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.

Sık yapılan hata

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.

karakterkod noktasıUTF-8 baytları (onaltılık)uzunlukAU+0041411 baytğU+011FC49F2 bayt€U+20ACE282AC3 bayt😀U+1F600F09F98804 bayt
Şekil 1.14: Dört karakterin kod noktası ve UTF-8 baytları. “A” (U+0041) 1 bayt: 41. “ğ” (U+011F) 2 bayt: C4 9F. “€” (U+20AC) 3 bayt: E2 82 AC. Gülen yüz (U+1F600) 4 bayt: F0 9F 98 80.

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.

Tablo 1.9: UTF-8 şablonları. x konumlarına kod noktasının bitleri soldan sağa yerleştirilir.
Kod noktası aralığıBit sayısıBayt şablonuÖrnek
U+0000 – U+007F70xxxxxxx“A” → 41
U+0080 – U+07FF11110xxxxx 10xxxxxx“ğ” → C4 9F
U+0800 – U+FFFF161110xxxx 10xxxxxx 10xxxxxx“€” → E2 82 AC
U+10000 – U+10FFFF2111110xxx 10xxxxxx 10xxxxxx 10xxxxxxemoji → 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.

Örnek 1.8

“ğ” harfinin UTF-8 baytlarını bulalım.

  1. Kod noktası: Unicode, “ğ”ya U+011F'yi atar. 011F16 = 1 · 256 + 1 · 16 + 15 = 287.
  2. İ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.
  3. 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.
  4. Yerleştirme: İlk bayt 110 + 00100 = 11000100 = C416. İkinci bayt 10 + 011111 = 10011111 = 9F16.
  5. Sonuç: “ğ” = C4 9F, 2 bayt.
“ğ” → kod noktası U+011F = 28711 bit:001000111115 bit6 bitşablon:11000100= C4₁₆10011111= 9F₁₆
Şekil 1.15: “ğ” = U+011F = 287'nin 11 biti, 5 + 6 olarak 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.
Merhaba␣+DünyaMerhaba␣Dünyauzunluk: 13 karakter · UTF-8'de 14 bayt (“ü” 2 bayt)
Şekil 1.16: “Merhaba ” ile “Dünya”nın birleştirilmesi. Sonuç 13 karakterlik bir dizgidir; UTF-8'de 14 bayt tutar, çünkü “ü” 2 bayttır.

Ş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.

Sık yapılan hata

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.

Bit1Bayt01000001Karakter“A”Alanad = “Ayşe”Kayıtno · ad · notDosyakayıt 1, 2, 3 …123Ayşe
Şekil 1.17: Bit, bayt, karakter, alan, kayıt ve dosya. Her düzey bir öncekinden oluşur: sekiz bit bir bayt, bir ya da birkaç bayt bir karakter, karakterler bir alan, alanlar bir kayıt, kayıtlar bir dosya oluşturur.
  • 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.

Tablo 1.10: Veri temsilinde sık yapılan hatalar ve doğruları.
Sık yapılan hataDoğrusu
Tabanı yazmadan okumak: “1101 bin yüz birdir”11012 = 13. Taban alt simgeyle ya da 0b/0x önekiyle belirtilir.
1016'yı on sanmak1016 = 16; onaltılıkta on, A ile yazılır.
İkiye tümleyende yalnız bitleri çevirmek11111010 birler tümleyenidir (−5 değil); 1 eklenince 11111011 = −5.
Kayan noktalı sonuçları == ile karşılaştırmak0,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ı sanmakUTF-8'de “çağ” 3 karakter, 5 bayttır (ç 2 + a 1 + ğ 2).
Elde ile işaretli taşmayı karıştırmakElde 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ı

Tanım 1.6

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.
Tablo 1.11: Dört temel kapının doğruluk tablosu.
ABANDORXORNOT A
000001
010111
100110
111100
ABAND (VE)ABOR (VEYA)ABXOR (ÖZEL VEYA)ANOT (DEĞİL)
Şekil 1.18: AND, OR, XOR ve NOT kapılarının devre simgeleri. Girdiler solda, çıktı sağdadır. NOT'un ucundaki küçük daire “tersine çevirme” anlamına gelir.

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.

Tablo 1.12: Yarım toplayıcının doğruluk tablosu. SUM sütunu XOR'un, CARRY sütunu AND'in tablosuyla aynıdır.
ABA + BCARRYSUM
000 = 00200
011 = 01201
101 = 01201
112 = 10210

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.

Tanım 1.7

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.

A1B1XORANDSUM = 0CARRY = 1A XOR BA AND B1 + 1 = 10₂ = 2(CARRY, SUM) sırasıyla yazılır
Şekil 1.19: Yarım toplayıcı, 1 + 1 durumunda. A ve B her iki kapıya da gider. XOR(1, 1) = 0 sonuç basamağını, AND(1, 1) = 1 eldeyi verir: (CARRY, SUM) = 102 = 2.

Ş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.

Tanım 1.8

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.

Örnek 1.9

A = 1, B = 1, Cin = 1 için tam toplayıcının çıktılarını bulalım.

  1. İlk yarım toplayıcı: t = 1 XOR 1 = 0; ilk elde 1 AND 1 = 1.
  2. İkinci yarım toplayıcı: SUM = 0 XOR 1 = 1; ikinci elde 0 AND 1 = 0.
  3. OR: Cout = 1 OR 0 = 1.
  4. 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)
Tablo 1.13: 0110 + 0111 (6 + 7) için tam toplayıcı zincirinin izlemesi. Her satırda c gelen elde, son sütun çıkan eldedir.
ia[i]b[i]gelen cts[i]çıkan c
0010110
1110001
2111011
3001010

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.

TT0tam toplayıcıa0 = 0b0 = 1s0 = 1TT1tam toplayıcıa1 = 1b1 = 1s1 = 0TT2tam toplayıcıa2 = 1b2 = 1s2 = 1TT3tam toplayıcıa3 = 0b3 = 0s3 = 1c=0c=0c=1c=1c=0
Şekil 1.20: Dört tam toplayıcının (TT0–TT3) zinciri, 0110 + 0111 için. Elde sağdan sola ilerler: TT0'dan 0, TT1'den 1, TT2'den 1, TT3'ten 0 çıkar. Sonuç 11012 = 13 = 6 + 7.

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.

Sık yapılan hata

Ç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.

Tanım 1.9

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.

Bellek1000LOAD 10041001ADD 10051002STORE 10061003HALT1004121005810060programveriaynı bellek, aynı adres sistemi
Şekil 1.21: Aynı bellekte program ve veri. 1000–1003 adreslerinde komutlar (LOAD 1004, ADD 1005, STORE 1006, HALT), 1004–1006 adreslerinde veriler (12, 8, 0) durur. İkisi aynı adres sistemini kullanı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.
Tanım 1.10

Getir–çöz–yürüt döngüsü (fetch-decode-execute cycle) üç aşamadan oluşur:

  1. Getir: PC'deki adresten komut okunur ve IR'ye konur; PC bir artırılır.
  2. Çö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.
  3. 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.

1. GetirPC'deki adresten komut → IRPC ← PC + 12. Çözkontrol birimiopcode + işlenen3. YürütALU / bellek işlemisonuç yazmaca / belleğetekrar
Şekil 1.22: Getir, çöz ve yürüt aşamaları bir döngü oluşturur. Getirde PC'deki adresten komut IR'ye gelir ve PC bir artar. Çözde kontrol birimi opcode ile işleneni ayırır. Yürütte ALU ya da bellek işlemi yapılır ve sonuç yazmaca ya da belleğe yazılır.

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

CPUKontrol birimisıra ve sinyallerALU+ − AND OR karşılaştırYazmaçlarPCIRACCÖnbelleksık kullanılan veri ve komutRAM
Şekil 1.23: CPU'nun içi. Kontrol birimi sırayı ve sinyalleri yönetir; ALU toplama, çıkarma, AND, OR ve karşılaştırma yapar; yazmaçlar (PC, IR, ACC) ara değerleri tutar; önbellek sık kullanılan veri ve komutları RAM'den getirip yakında saklar.

Ş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.

Tablo 1.14: CPU'yu bellek ve aygıtlara bağlayan üç yol.
YolGörevYön
Adres yoluİşlem yapılacak bellek ya da aygıt konumunu belirtir.tek yönlü: CPU'dan çıkar
Kontrol yoluOkuma, yazma ve zamanlama sinyallerini taşır.çoğunlukla CPU'dan çıkar
Veri yoluCPU, bellek ve aygıtlar arasında veriyi taşır.çift yönlü
Örnek 1.10

CPU, 1004 adresindeki değeri okuyacaktır; hücrede 12 vardır. Üç yol sırayla şöyle kullanılır:

  1. Adres yolu: CPU hangi hücreyi istediğini gönderir: 1004.
  2. Kontrol yolu: CPU OKU sinyalini gönderir. Yazma olsaydı YAZ sinyali giderdi.
  3. Veri yolu: Bellek hücrenin içeriğini, yani 12'yi, CPU'ya gönderir. Değer ACC'ye yüklenir.
CPUBellek[1004] = 12ACC ← 12Adres yoluKontrol yoluVeri yolu12G/Ç birimleri da aynı yollara bağlı
Şekil 1.24: Bellekten okumanın son adımı. Adres yolundan 1004 ve kontrol yolundan OKU sinyali gitmiştir; bellek, hücrenin içeriği olan 12'yi veri yoluyla CPU'ya gönderir.

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.

Sık yapılan hata

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.

Tablo 1.15: Örnek bellek içeriği. İlk dört hücre komut, son üç hücre veridir.
AdresİçerikAnlamı
1000LOAD 1004ACC ← bellek[1004]
1001ADD 1005ACC ← ACC + bellek[1005]
1002STORE 1006bellek[1006] ← ACC
1003HALTdur
100412veri
10058veri
10060sonuç 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ı.

1000LOAD 10041001ADD 10051002STORE 10061003HALT1004121005810060program (komutlar)veriPC sırayla ilerler: getir → çöz → yürütHALT olmasaydı: 12 komut sanılırdı
Şekil 1.25: PC 1000'den başlayıp 1001, 1002 ve 1003'teki komutları sırayla getirir. 1003'teki HALT CPU'yu durdurur; HALT olmasaydı 1004'teki 12 komut sanılı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.
adres 100adres 101adres 102adres 103Büyük uçlu12345678Küçük uçlu785634120x12345678 =12 34 56 78(12 en anlamlı)
Şekil 1.26: 0x12345678'in 100–103 adreslerine yerleşimi. Büyük uçlu düzende en anlamlı bayt (12) 100 adresinde, küçük uçlu düzende 103 adresindedir.

İ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.

Tablo 1.16: Programın getir–çöz–yürüt izlemesi. Her satır, adım bittikten sonraki yazmaç ve bellek durumunu gösterir.
AdımAşamaNe olur?PCIRACCbellek[1006]
0başlangıç—1000—00
1getir1000 adres yoluna çıkar, LOAD 1004 veri yoluyla IR'ye gelir1001LOAD 100400
2çözopcode LOAD, adres 10041001LOAD 100400
3yürüt1004 okunur, 12 ACC'ye yüklenir1001LOAD 1004120
4getirADD 1005 IR'ye gelir1002ADD 1005120
5çözopcode ADD, adres 10051002ADD 1005120
6yürüt1005'teki 8 okunur, ALU 12 + 8 = 20 hesaplar1002ADD 1005200
7getirSTORE 1006 IR'ye gelir1003STORE 1006200
8çözopcode STORE, adres 10061003STORE 1006200
9yürütACC'deki 20 veri yoluyla 1006'ya yazılır1003STORE 10062020
10getirHALT IR'ye gelir1004HALT2020
11çözopcode HALT → CPU durur1004HALT2020

İ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.

GETİRÇÖZYÜRÜTCPUPC1001IRLOAD 1004ACC0ALUadres yoluveri yolu1000LOAD 10041000LOAD 10041001ADD 10051002STORE 10061003HALT1004121005810060
Şekil 1.27: İlk getir aşaması (adım 1). PC'deki 1000 adres yoluna çıkar, LOAD 1004 komutu veri yoluyla IR'ye gelir ve PC 1001'e artar.
GETİRÇÖZYÜRÜTCPUPC1003IRSTORE 1006ACC20ALUadres yoluveri yolu1006201000LOAD 10041001ADD 10051002STORE 10061003HALT10041210058100620
Şekil 1.28: STORE'un yürütme aşaması (adım 9). ACC'deki 20 veri yoluyla 1006 adresine yazılır.
Örnek 1.11

Programda STORE ile ADD'in yeri değiştirilirse (1001: STORE 1006, 1002: ADD 1005) sonuç ne olur?

  1. LOAD 1004: ACC = 12.
  2. STORE 1006: o anki ACC yazılır; bellek[1006] = 12.
  3. ADD 1005: ACC = 12 + 8 = 20; ama bu değer belleğe hiç yazılmaz.
  4. 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: 1

Program 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.

Tablo 1.17: Dallanma programının izlemesi. Her satır, komut yürütüldükten sonraki durumu gösterir. PC sütunu, bir sonraki getirde kullanılacak adrestir.
Komut noYürütülenPCACCsayaçtoplamAçıklama
—başlangıç1000020
11000: LOAD 10101001220ACC ← sayaç
21001: JUMPZ 10081002220ACC ≠ 0, atlama yok
3–4ADD 1011, STORE 10111004222toplam = 2 + 0
5–7LOAD, SUB, STORE 10101007112sayaç = 2 − 1
81007: JUMP 10001000112PC'nin 1008 değeri 1000 ile değişir
9–16ikinci tur (aynı 8 komut)1000003toplam = 1 + 2, sayaç = 0
171000: LOAD 10101001003ACC ← 0
181001: JUMPZ 10081008003ACC = 0, atlanır
191008: HALT1009003CPU 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).

CPUPC1000ACC1Bellek1010 sayaç11011 toplam21012 bir1PC ← 1000 CPUPC1008ACC0Bellek1010 sayaç01011 toplam31012 bir1PC ← 1008
Şekil 1.29: Dallanmanın iki anı. (a) İlk turun sonunda 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.

Sık yapılan hata

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.

CPUKontrol birimiALUYazmaçlar (PC, IR, ACC)Bellekprogram komutları + veriler(ortak)tek yol: darboğazGirişÇıkış
Şekil 1.30: von Neumann mimarisi. CPU (kontrol birimi, ALU, yazmaçlar) ile program komutlarının ve verilerin birlikte tutulduğu ortak bellek tek bir yolla bağlıdır. Giriş ve çıkış birimleri CPU'ya bağlanı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.

von NeumannCPUBellekkomut + veritek yolHarvardkomut belleğiCPUveri belleği
Şekil 1.31: Solda von Neumann: tek bellek ve tek yol. Sağda Harvard: ayrı komut ve veri bellekleri, ayrı yollar. CPU komut belleğinden yalnız okur; veri belleğinden okur ve ona yazar.
Tablo 1.18: von Neumann ve Harvard mimarilerinin karşılaştırması.
Özellikvon NeumannHarvard
BellekKomut ve veri ortak bellekteKomut ve veri ayrı belleklerde
YolTek yol: komut ve veri sırayla taşınırİki yol: komut ve veri aynı anda okunur
EsneklikProgram, veri gibi yüklenip değiştirilebilirProgram belleği çoğunlukla sabittir (flash)
Yaygın kullanımGenel amaçlı bilgisayarlarMikrodenetleyiciler, 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.

ADD 1005 →0010001111101101opcode (4 bit) = 2 → ADDadres (12 bit) = 1005Örnek (varsayımsal) kodlar: HALT = 0000 · LOAD = 0001 · ADD = 0010 · STORE = 0011
Şekil 1.32: 16 bitlik komut sözcüğü. ADD 1005 için ilk 4 bit opcode 0010 (ADD), kalan 12 bit adres 1005 = 001111101101'dir.
Örnek 1.12

Tablo 1.15'teki programın dört komutunu bu biçimle kodlayalım.

  1. Adresler: 1004 = 3EC16 = 0011 1110 1100; 1005 = 3ED16 = 0011 1110 1101; 1006 = 3EE16 = 0011 1110 1110. (Denetim: 3 · 256 + 14 · 16 + 12 = 1004.)
  2. LOAD 1004: 0001 0011 1110 1100 = 13EC16.
  3. ADD 1005: 0010 0011 1110 1101 = 23ED16.
  4. STORE 1006: 0011 0011 1110 1110 = 33EE16.
  5. 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.

Tablo 1.19: RISC ve CISC yaklaşımlarının karşılaştırması.
ÖzellikRISCCISC
Komut kümesiAz sayıda, basit komutÇok sayıda, karmaşık komut
Komut uzunluğuSabit (ör. 32 bit)Değişken (x86-64'te 1–15 bayt)
Bellek erişimiYalnız LOAD / STORE komutlarıİşlem komutları doğrudan bellek işleneni alabilir
ÖrnekARM, RISC-Vx86-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, a

CISC (x86-64):

MOV EAX, [b]
ADD [a], EAX

RISC'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.
CPU1 tur ≈ 0,3 ns (3 GHz)RAMerişim ≈ 100 nsdar veri yoluveri bekleniyor…100 ns ÷ 0,3 ns ≈ 300 tur: önbellek bu bekleyişi azaltmak için vardır
Şekil 1.33: Veri darboğazı. 3 GHz'lik bir CPU'da bir saat turu yaklaşık 0,3 ns sürer; RAM erişimi ise yaklaşık 100 ns'dir. CPU bir veriyi beklerken yaklaşık 300 tur boşa geçebilir.

Ş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.
Tablo 1.20: 2 · 109 komutluk bir programın üç durumdaki CPU süresi.
DurumSaat hızıCPISüre
İşlemci A3 GHz1,52 · 109 × 1,5 ÷ (3 · 109) = 1,0 s
İşlemci B4 GHz2,52 · 109 × 2,5 ÷ (4 · 109) = 1,25 s
A, daha iyi derleyiciyle (1,6 · 109 komut)3 GHz1,51,6 · 109 × 1,5 ÷ (3 · 109) = 0,8 s
Örnek 1.13

A ve B işlemcilerinin süresini adım adım hesaplayalım.

  1. Toplam tur (A): 2 · 109 komut × 1,5 tur = 3 · 109 tur.
  2. Süre (A): 3 · 109 tur ÷ (3 · 109 tur/s) = 1,0 s.
  3. Toplam tur (B): 2 · 109 × 2,5 = 5 · 109 tur.
  4. Süre (B): 5 · 109 ÷ (4 · 109) = 1,25 s.
  5. 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.

Sık yapılan hata

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.

Problem ve algoritma“iki sayıyı topla”Yüksek seviyeli dil · Javatotal = a + b;Derleyici / sanal makineJava bayt kodu: iaddAssemblyADD R1, bMakine komutları0010 0011 1110 1101Sayısal devrelertoplayıcı devre · transistörlersoyuttan somuta
Şekil 1.34: Soyutlama düzeyleri, yukarıdan aşağıya soyuttan somuta: problem ve algoritma (“iki sayıyı topla”), yüksek seviyeli dil (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
Tablo 1.21: total = a + b satırının assembly izlemesi.
KomutNe olur?R1abtotal
başlangıç—01280
LOAD R1, aa'nın değeri bellekten yazmaca taşınır121280
ADD R1, bb okunur, ALU 12 + 8 = 20 hesaplar, sonuç R1'e yazılır201280
STORE R1, totalR1'deki 20, total değişkeninin yerine yazılır2012820
CPUR120ALUBelleka12b8total20
Şekil 1.35: STORE adımı. R1'deki 20 belleğe, 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: float 1 + 8 + 23, double 1 + 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.
Tablo 1.22: Notta çözülen hesap türleri ve kısa yolları.
HesapYöntemÖrnek
Onluk → ikiliart arda 2'ye böl, kalanları sondan oku45 = 1011012
İkili → sekizli / onaltılıksağdan 3'erli / 4'erli grupla101101 = 558 = 2D16
Negatif (ikiye tümleyen)bitleri çevir, 1 ekle−5 = 11111011
Taşma (8 bit işaretsiz)sonuçtan 256 çıkar200 + 100 → 44
Kesir → ikiliart arda 2 ile çarp, tam kısımları al0,375 = 0,0112
IEEE 754 floatişaret, üs + 127, baştaki 1'siz kesir−6,25 = C0C8000016
UTF-8 (2 bayt)11 bit → 110xxxxx 10xxxxxx“ğ” = C4 9F
CPU süresikomut 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.

1.12Kendini Dene

  1. 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.

  2. 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.

  3. 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.

  4. 0,625 ve 0,2 sayılarının ikili açılımları nedir? Hangisi double ile 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.

  5. 0,75 sayısının 32 bitlik float karşı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.

  6. “ü” (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.

  7. 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.

  8. Dallanma programında 1010 adresindeki sayaç 2 yerine 3 olsaydı program bitince toplam ne 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.

  9. 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.

  10. 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

  1. D. A. Patterson, J. L. Hennessy, Computer Organization and Design: The Hardware/Software Interface, Morgan Kaufmann.
  2. R. E. Bryant, D. R. O'Hallaron, Computer Systems: A Programmer's Perspective, 3. baskı, Pearson, 2016.
  3. The Unicode Consortium, The Unicode Standard, https://www.unicode.org/standard/standard.html.
  4. IEEE, IEEE Standard for Floating-Point Arithmetic (IEEE Std 754-2019), 2019.
  5. C. Petzold, Code: The Hidden Language of Computer Hardware and Software, 2. baskı, Microsoft Press, 2022.
  6. N. Nisan, S. Schocken, The Elements of Computing Systems: Building a Modern Computer from First Principles, 2. baskı, MIT Press, 2021.
  7. D. Goldberg, “What Every Computer Scientist Should Know About Floating-Point Arithmetic”, ACM Computing Surveys, 23(1), 5–48, 1991.