Skip to content

Bölüm 1 / 16

Sayı sistemleri ve veri gösterimi

İkilik, onaltılık, ASCII/Unicode, kayan nokta.

10 dakikalık okuma

Bilgisayar; sayıyı, metni, resmi, sesi — her şeyi — yalnızca 0 ve 1 dizileriyle tutar. Bu ders o dizilerin gramerini öğretir: ikilik ve onaltılık sistemler, negatif sayıların ikiye tümleyenle gösterimi, kayan noktanın neden 0.1 + 0.2 ≠ 0.3 dediği ve metnin ASCII'den UTF-8'e uzanan hikâyesi. Yazılı sınavların açılış konusudur; taşma ve ondalık hassasiyeti ise üretimde para kaybettiren gerçek hatalardır.

01Ne öğreneceksin

  • Basamak değeri mantığıyla ikilik ↔ onluk ↔ onaltılık çevirme
  • Bit, bayt ve tiplerin bit genişliği; taşma (overflow) davranışı
  • Negatif sayılar: ikiye tümleyen (two's complement)
  • IEEE 754 kayan nokta ve float/double/decimal seçim kararı
  • Metin kodlama: ASCII, Unicode kod noktası, UTF-8/UTF-16 farkı ve Türkçe tuzakları

02Basamak değeri: tüm sistemlerin ortak kuralı

Günlük hayatta onluk (decimal) sistemi kullanırsın: 10 rakam (0-9) vardır ve her basamak, sağdan sola 10'un kuvvetleriyle çarpılır. 4072 sayısı aslında şudur:

4072 = 4·10³ + 0·10² + 7·10¹ + 2·10⁰
     = 4000 + 0   + 70   + 2

Bu kural her tabana genellenir: taban b ise b adet rakam vardır ve basamaklar b'nin kuvvetleridir. Bilgisayar için doğal taban 2'dir, çünkü donanımın güvenilir ayırt edebildiği yalnızca iki durum vardır: gerilim var / yok, 1 / 0. Tek bir 0-veya-1 hanesine bit (binary digit) denir; 8 bitlik gruba bayt (byte). Bellek, dosya, ağ paketi — hepsinin ölçü birimi bayttır.

Gösterim karışmasın diye taban alta yazılır: (1101)₂, (13)₁₀, (D)₁₆. Kod dünyasında ise önekler kullanılır: C#'ta 0b1101 ikilik, 0xD onaltılık yazımdır — ikisi de 13 değerini taşır. Sayının kendisi tek; değişen yalnızca yazımıdır. Bu cümle dersin bel kemiğidir: 13, D ve 1101 aynı niceliğin üç ayrı kostümüdür.

03İkilik ↔ onluk çevirme

İkilikten onluğa: basamak değerlerini topla. Sağdan sola kuvvetler 1, 2, 4, 8, 16, 32, 64, 128... diye ikişer katlanır — bu diziyi ezbere bil, sınavda zaman kazandırır:

(1 0 1 1 0 1)₂
 32 16  8  4  2  1        ← basamak değerleri
 32 + 0 + 8 + 4 + 0 + 1 = (45)₁₀

Onluktan ikiliğe: sayıyı sürekli 2'ye böl, kalanları sakla; kalanlar sondan başa okunur:

45 ÷ 2 = 22  kalan 1   ↑ okuma yönü
22 ÷ 2 = 11  kalan 0   |
11 ÷ 2 = 5   kalan 1   |
 5 ÷ 2 = 2   kalan 1   |
 2 ÷ 2 = 1   kalan 0   |
 1 ÷ 2 = 0   kalan 1   |
sonuç: (101101)₂

İkinci bir yol, büyük sayılarda daha hızlıdır: sayıdan küçük en büyük 2 kuvvetini bul, çıkar, kalanla devam et (45 = 32 + 8 + 4 + 1 → 32, 16 yok, 8, 4, 2 yok, 1 → 101101). Hangisi rahatsa onu otomatikleştir; yazılıda iki yöntem de tam puandır.

Bit sayısı, tutabileceğin değer aralığını belirler: n bit ile 2ⁿ farklı değer gösterilir. 8 bit → 256 değer (işaretsizse 0-255), 32 bit → yaklaşık 4,29 milyar. C#'taki tipleri bu gözle yeniden oku: byte 8 bit, int 32 bit, long 64 bit. 2. derste (prg-degisken) "int'in sınırı var" demiştik; sınırın kaynağı budur — bit sayısı biter, sayı bitmez.

04Onaltılık: ikiliğin steno yazımı

32 bitlik bir değeri ikilik yazmak 32 karakter ister — okunmaz, yazarken şaşılır. Onaltılık (hexadecimal) sistem bu derde çözümdür: 16 rakam vardır (0-9, sonra A=10, B=11, C=12, D=13, E=14, F=15) ve 16 = 2⁴ olduğu için bir onaltılık hane, tam olarak 4 bite karşılık gelir. Çevirme bu yüzden mekaniktir — ikilik sayıyı sağdan dörderli gruplara böl, her grubu tek haneye çevir:

(1011 0100 1111)₂
  B    4    F     →  (B4F)₁₆  =  0xB4F

tersi: 0x2E → 2=0010, E=1110 → (0010 1110)₂ → 32+8+4+2 = (46)₁₀

Onaltılığı gerçek hayatta sandığından sık görürsün: CSS renkleri (#2dd4bf — kanal başına 1 bayt), bellek adresleri (0x7FFE...), MAC adresleri, hata kodları (0x80070005), bir dosyanın ham bayt dökümü (hex dump). Amaç hiçbir zaman "onaltılık aritmetik yapmak" değildir; amaç baytları insan gözüyle okunur kılmaktır — 1 bayt her zaman tam 2 hex hanedir: FF = 255, 00 = 0, 7F = 127.

Ara not — sekizlik (octal, taban 8) tarihte kaldı ama iki yerde yaşar: yazılı sınav soruları ve Linux dosya izinleri (chmod 755 — Hat 17'de). Bir sekizlik hane 3 bittir; mantık onaltılıkla birebir aynıdır.

05Negatif sayılar: ikiye tümleyen ve taşma

Bitlerde eksi işareti yoktur; negatifi de 0-1'lerle göstermek gerekir. İlk akla gelen "en soldaki biti işaret yap" (işaret-büyüklük) yöntemi iki sorun çıkarır: +0 ve -0 diye iki sıfır oluşur ve toplama devresi özel durum ister. Modern tüm işlemciler ikiye tümleyen (two's complement) kullanır. Kural, 8 bit üzerinde:

+13 =  0000 1101
 -13:  1111 0010   ← tüm bitleri tersle (birin tümleyeni)
     + 0000 0001   ← 1 ekle
     = 1111 0011   ← -13'ün ikiye tümleyen gösterimi

sağlama: 13 + (-13) = 0000 1101 + 1111 0011 = 1 0000 0000
         → 9. bit 8 bitlik alana sığmaz, atılır → 0000 0000 ✓

Güzelliği sağlamada görünüyor: toplama devresi negatif sayıyı bilmez, normal toplar, taşan bit düşer ve sonuç kendiliğinden doğru çıkar. En soldaki bit hâlâ işareti söyler (1 ise negatif) ama artık ayrı bir "işaret biti" değil, sayının parçasıdır. 8 bitlik işaretli aralık bu yüzden simetrik değildir: -128 ... +127 (2⁸ = 256 değer; sıfır pozitif tarafta sayılır). Aynı mantıkla int: -2 147 483 648 ... +2 147 483 647.

Taşma (overflow): aralığın dışına çıkarsan bitler sessizce sarar (wrap-around). C#'ta int.MaxValue + 1 hata fırlatmaz, int.MinValue verir — pozitif iki sayının toplamı negatif çıkar. Üretimde para, sayaç ve kimlik alanlarında gerçek hata kaynağıdır; savunma araçların: aralığı bol tip seçmek (long), kritik hesabı checked { } bloğuna almak (taşma anında exception fırlatır) ya da decimal kullanmak. Yazılıda tipik soru: "8 bit işaretli tam sayının alabileceği en küçük değer nedir?" — cevap -128, ve artık nedenini de biliyorsun.

06Kesirli sayılar: IEEE 754 ve 0.1 sorunu

Kesirli sayılar için evrensel standart IEEE 754 kayan noktadır (floating point). Fikir, bilimsel gösterimin ikilik hâlidir: sayı işaret · 1,mantis · 2^üs biçiminde saklanır. float 32 biti (1 işaret + 8 üs + 23 mantis), double 64 biti (1 + 11 + 52) böler. "Kayan" adı, üs sayesinde virgülün konumunun kaymasından gelir — aynı format hem 0,000001'i hem 10¹⁵'i tutabilir.

Bedeli hassasiyettir. Onlukta 1/3'ü sonlu yazamazsın (0,333...); ikilikte de 1/10 sonsuz kesirdir: 0,1 = (0,0001100110011...)₂. Mantis 52 bitte kesilir, sayı en yakın temsil edilebilir değere yuvarlanır. Sonuç, her dilde aynı meşhur sürpriz:

double a = 0.1 + 0.2;
Console.WriteLine(a == 0.3);        // False!
Console.WriteLine(a.ToString("G17")); // 0.30000000000000004

decimal b = 0.1m + 0.2m;
Console.WriteLine(b == 0.3m);       // True — decimal onluk tabanda saklar

Bu bir dil hatası değil, formatın matematiğidir. Kıdemli refleksleri: (1) double'ları == ile karşılaştırma — fark toleransla ölçülür: Math.Abs(a - b) < 1e-9. (2) Para ve muhasebe daima decimal — onluk tabanda sakladığı için 0,1'i tam tutar; yavaştır ama doğrudur (prg-degisken'deki kuralın gerekçesi buydu). (3) Bilimsel hesap, grafik, oyun fiziği double/float ister — hız ve aralık önceliklidir, en son basamağın sapması kabul edilir. Formatın özel değerlerini de tanı: Infinity (1.0/0.0 — exception değil!), NaN (0.0/0.0; NaN == NaN bile False'tur, kontrol double.IsNaN(x) ile yapılır).

07Metin: ASCII'den UTF-8'e

Bitler sayı tutar; metin tutmak için "hangi sayı hangi karakter" anlaşması — bir kodlama — gerekir. 1963'ün anlaşması ASCII 7 bitle 128 karakter tanımlar: İngiliz alfabesi, rakamlar, noktalama, kontrol karakterleri (örn. 65=A, 97=a, 48='0', 10=satır sonu \n). 128 karaktere ç, ğ, İ sığmaz; 90'larda her dil 8. biti kendine göre kullandı (Türkçe için ISO-8859-9 / Windows-1254 kod sayfaları) ve bir kodlamayla yazılan dosya başka kodlamayla açılınca çalýþma türü bozuk metinler doğdu. Bu soruna mojibake denir ve kökü hep aynıdır: baytlar doğrudur, yorumlayan tablo yanlıştır.

Kalıcı çözüm iki katmanlıdır ve ikisini ayırt etmek dersin en kritik ayrımıdır:

  • Unicode bir karakter kataloğudur: dünyadaki her karaktere bir numara — kod noktası — verir. A → U+0041, ç → U+00E7, İ → U+0130, 😀 → U+1F600. Unicode baytlardan söz etmez.
  • UTF-8 / UTF-16 / UTF-32 ise kodlamalardır: kod noktasını bayta çevirme kurallarıdır. UTF-8 değişken uzunludur (1-4 bayt): ASCII karakterleri 1 baytta tutar (eski ASCII dosyaları geçerli UTF-8'dir — tasarımın dehası budur), ç 2 bayt, 😀 4 bayt olur. Web'in ve Linux'un varsayılanıdır. UTF-16 çoğu karakteri 2 baytta tutar; C#/.NET string'i ve Windows API'si bellekte UTF-16 kullanır.
string s = "İzmir";
Console.WriteLine(s.Length);                          // 5 (UTF-16 birimi)
Console.WriteLine(Encoding.UTF8.GetBytes(s).Length);  // 6 — İ, UTF-8'de 2 bayt

Türkçeye özel iki üretim tuzağını şimdiden not et. (1) Nokta/noktasız i: Türkçe kültürde "i".ToUpper() → "İ" olur; dosya adı, protokol komutu gibi kültürden bağımsız karşılaştırmalar ToUpperInvariant() / StringComparison.OrdinalIgnoreCase ister — yoksa Türkçe Windows'ta çalışıp İngilizce sunucuda patlayan kod yazarsın. (2) Kodlama beyanı: dosyayı hangi kodlamayla yazdıysan o kodlamayla okunmalı; HTML'de <meta charset="utf-8">, veritabanı bağlantısında ve File.ReadAllText çağrısında kodlama bilinçli seçilmeli. "Türkçe karakterler bozuk geliyor" ticket'ının cevabı %99 bu paragraftadır.

08Mini alıştırma — Çevirmen

Yaklaşık 20 dakika · ilk yarısı kâğıt-kalem, ikinci yarısı dotnet new console -o sayilar

  1. Kâğıtta çevir, sonra C#'ta doğrula: (10110)₂ → ?₁₀ · (77)₁₀ → ?₂ · (2F)₁₆ → ?₁₀ · (200)₁₀ → ?₁₆. Doğrulama satırların: Convert.ToInt32("10110", 2) ve Convert.ToString(200, 16).
  2. 8 bitle -77'yi ikiye tümleyen yaz (77'yi ikiliğe çevir → tersle → 1 ekle). Sağlamasını 77 + (-77) toplamasıyla kâğıtta yap: 9. bitin düştüğünü gör.
  3. Taşmayı gözle: int x = int.MaxValue; x = x + 1; yazdır. Sonra aynı toplamayı checked { } bloğuna al ve OverflowException'ı gör. Hangi durumda hangisini istersin, bir cümle not düş.
  4. Kayan nokta: 0.1 + 0.2 == 0.3 ve 0.1m + 0.2m == 0.3m sonuçlarını yazdır. double sürümünü Math.Abs(a - 0.3) < 1e-9 toleransıyla düzelt.
  5. Kodlama: Encoding.UTF8.GetBytes("çağrı") dizisini BitConverter.ToString(...) ile hex olarak yazdır. Kaç bayt çıktı, hangi karakterler 2 bayt tuttu? "quiz".ToUpper() ile "quiz".ToUpperInvariant()'ı Türkçe kültürde karşılaştır (üstte CultureInfo.CurrentCulture = new CultureInfo("tr-TR");).

09Kendini sına

(1101)₂ sayısını onluğa, (156)₁₀ sayısını onaltılığa çevir; adımlarını göster.

(1101)₂ = 8+4+0+1 = 13. (156)₁₀: 156÷16 = 9 kalan 12(C) → 9÷16 = 0 kalan 9 → sondan başa (9C)₁₆. Sağlama: 9·16 + 12 = 144 + 12 = 156. İstersen ikilik üzerinden: 156 = (1001 1100)₂ → dörtlü gruplar 1001=9, 1100=C.

Onaltılık sistem neden kullanılır? "Bir bayt her zaman iki hex hanedir" cümlesini gerekçelendir.

16 = 2⁴ olduğundan bir onaltılık hane tam 4 bite karşılık gelir; ikilikle arasındaki çevirme gruplama kadar basittir. 1 bayt = 8 bit = 2 × 4 bit olduğundan her bayt tam iki hex haneyle yazılır (00-FF, 0-255). Bu yüzden bellek adresleri, renk kodları ve hex dump'lar onaltılıktır: amaç aritmetik değil, uzun bit dizilerini insan gözüyle okunur kılmaktır.

İkiye tümleyen nedir, neden işaret-büyüklük yerine tercih edilir? 8 bit işaretli aralık neden -128...+127'dir?

Negatif sayı gösterimi: sayının bitleri terslenir, 1 eklenir. Tercih nedeni: tek sıfır vardır ve toplama devresi işaretle ilgilenmeden normal toplama yapar — taşan bit atıldığında sonuç doğru çıkar. 8 bit 256 değer tutar; sıfır pozitif tarafta sayıldığından aralık simetrik değildir: -128...+127. Aralık aşılırsa değer sessizce sarar (overflow) — C#'ta int.MaxValue + 1, int.MinValue verir; checked bloğu bunu exception'a çevirir.

0.1 + 0.2 == 0.3 neden False'tur? Unicode ile UTF-8 arasındaki fark nedir?

Kayan nokta ikilik tabanda saklar ve 1/10 ikilikte sonsuz kesirdir; mantis kesilince 0.1 ve 0.2 yaklaşık değerlerle tutulur, toplamları 0.3'ün yaklaşığından farklı bir yaklaşık çıkar. Çözüm: toleranslı karşılaştırma ya da parada decimal. Unicode karakter kataloğudur — her karaktere kod noktası numarası verir; UTF-8 ise o numaraları bayta çeviren değişken uzunluklu (1-4 bayt) kodlamadır. Katalog tek, kodlamalar birden çoktur (UTF-8, UTF-16...); .NET string bellekte UTF-16 tutar, dosya ve web'de yaygın olan UTF-8'dir.

10Sınav köşesi

Yazılıda nasıl sorulur

Bu konu yazılının garantili sorusudur ve üç kalıpta gelir. (1) Düz çevirme: "(101101)₂ sayısının onluk karşılığı?", "(AB)₁₆ kaçtır?" (171), "(58)₁₀'un ikilik gösterimi?" — dörtlü gruplama ve 1-2-4-8-16-32 dizisiyle 30 saniyede çözülür. (2) Kavram: "1 bayt kaç bittir?", "n bit ile kaç farklı değer gösterilir?" (2ⁿ), "8 bit işaretli tam sayının en küçük değeri?" (-128), "ASCII kaç bitlik kodlamadır?" (7). (3) Tuzaklı: aynı sayının farklı tabanlardaki yazımları arasında büyük-küçük karşılaştırması — önce hepsini onluğa çevir, sonra karşılaştır. Şıklarda en sık yanılgı, ikiye tümleyende "tersle" deyip 1 eklemeyi unutturan seçenektir.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

"Bilgisayar veriyi nasıl tutar?" sorusuna katmanlı cevap: (1) Donanım iki durumu ayırt eder, bu yüzden taban ikidir; bit ve bayt tanımı. (2) Tam sayılar sabit bit genişliğinde, negatifler ikiye tümleyenle — "toplama devresi işareti bilmeden doğru sonuç üretir" cümlesi konuya hâkimiyeti gösterir. (3) Kesirliler IEEE 754 ile; "0.1 ikilikte sonsuz kesir olduğundan parada decimal kullanırım" örneği pratik tecrübe kanıtıdır. (4) Metin, karakter kataloğu (Unicode) + kodlama (UTF-8) ayrımıyla; Türkçe İ/i ve Invariant karşılaştırma tuzağını anlatmak, konuyu sahada yaşamış birinin işaretidir.