19 Mart 2014 Çarşamba

C int ve unsigned int farkı ve dönüşümü

Evet arkadaşlar signed ve unsigned arasındaki en önemli fark signed yani normal int negatif değerleri de tutabilir, ancak unsigned int sadece 0 ve pozitif sayıları tutabilir. Bir önemli fark da onların hafıza da gösterimi ve gösterebilecekleri değer aralıkları.
Mesela sizin makinenizin word boyutu 8 bit olsun, yani şu anki 32 bit ve 64 bit'lik makinelerimizin yerini 8 bitlik mini makinemiz alsın. O zaman sizin makinenizin gösterebileceği en büyük sayı bit gösterimi olarak : 1111 1111 olur değil mi? Yani 2^8 - 1 = 255 (aritmetik hesapla 2^0*1 + 2^1*1 + 2^2*1 + ... ) . O zaman bizim makinemiz 32 bit olduğunda tutabileceği en büyük sayı kaç olur : 2^32 - 1 yani 32 tane 1 yan yana. Tutabileceği sayı derken genel olarak bir int'in hafızadaki boyutu bir pointer yani word boyutuna eşit olur. Yani sizin bilgisayarınız 32 bit ise programlama dilinden diline ve derleyiciden derleyiciye değişir ancak genelde bir int'i tutmak için 4 byte ayrılır. 4 bayt ise 32 bittir. ( sizeof(int) fonksiyonuyla bit int'in kaç byte olarak yorumlandığını görebilirsiniz. )
Bu 32 tane 1'i yan yana koyup ulaşabileceğiniz en büyük rakam unsigned int olarak tanımlayabileceğimiz bir sayıdır. Dikkat ederseniz bütün bitleri kullandık ama bu sayının negatif mi pozitif mi olduğunu nasıl anlayacağız. Böyle bütün bitleri sayının değerini hesaplamak için kullandığımız gösterime unsigned yani işaretsiz int denir. Örneğin bir int'i göstermek için 4 byte yani 32 bit kullanan C'nin gösterebileceği en büyük rakam : 4294967295 yani 2^32 -  1 yani 32 tane birin ikilik düzende aritmetik toplamına eşittir. Bu düzende gösterebileceğimiz en küçük rakam 0'dır yani 32 tane 0 biti. En büyük rakamı zaten söyledik. Ancak negatif sayıları gösteremeyiz. Siz :

unsigned int a = -45 ;

dediğiniz zaman sizin hiç istemediğiniz işler yapar C. Buna değineceğiz.
Peki negatif sayıları nasıl bir bit gösterimiyle gösteririz? Burada two's complement denen ve çoğu sistem tarafından kullanılan bir uygulama var. Örneğin sayımız 8 bitli 8 bitin 8'ini de o sayının değerini hesaplamak yerine en soldaki biti işaretini belirlemek için kullanırız. En soldaki bit eğer 0 ise bu sayı pozitif en soldaki bit 1 ise bu sayı negatiftir. Peki örneğin sayımız :

10001111

bu sayının değerini nasıl hesaplarız? Normalde unsigned olsaydı direk (2^0*1 + 2^1*1 + 2^2*1 + 2^3*1 + 2^4*0 + 2^5*0 + 2^6*0 + 2^7*1 ) yani 143 olarak hesaplardık. Ancak burada durum farklı. Bu sayı bir signed int yani normal int yani negatif sayıları da gösterebilmesi lazım yani ilk bit işaret biti olacak. O zaman işlemimiz şu şekilde :

 10001111  = 2^0*1 + 2^1*1 + 2^2*1 + 2^3*1 + 2^4*0 + 2^5*0 + 2^6*0  -  2^7*1
                    = 15 - 128 = -113

yani son bite kadar normal toplama işlemimizi yaparken en soldaki biti yani 2^7*1'i işlemimize çıkarma olarak ekliyoruz.  Bilgisayarımız int tutmak için 32 bit ayırıyorsa o zaman ilk 31 biti aritmetik olarak toplayıp 32. biti çıkarırız aynı olay. Signed integer yani normal int' in tutabileceği en küçük değer o zaman eğer 32 bit ise bir int :
10000000000....000000 yani 0+0+0+...+0+0+0 - 2^31
yani -2^31
tutabileceği en büyük değer ise tabiki baştaki bitin 0 olması lazım pozitif olması için :
01111111111111111.....1111   (32 bit var yani)
2^0*1 + 2^1*1 + .....  + 2^30*1   -  2^31*0 (bu değer zaten 0)
ilk 31 terimin toplamı formülümüzden  2^0*1 + 2^1*1 + .....  + 2^30*1 = 2^31 - 1 .
Yani büyüklük olarak unsigned int bize int'e karşı avantaj sağlıyor ancak unsigned int negatif sayıları gösteremiyor.

Peki biz bir yanlış yaptık gittik unsigned int'i int'e eşitledik veya tam tersi :

Bunu yaparken C hata vermez ve sayıların bit gösterimlerini değiştirmez. Bit gösterimleri aynı kaldığı için çok büyük problemlerle karşı karşıya kalabiliriz. Mesela sizeof(int)'i 4 olan yani bit int'i göstermek için 4 byte kullanan C dilimizde  :

unsigned u = 4294967295u; // unsigned olarak gösterebilecek max sayi
int i  = (int) u ; // u'ya casting yaptik
printf("u esittir %u ve i esittir %d ",u,i);  // %u unsigned sayilar icin

yazıp çalıştırırsak :

dersek "u esittir 4294967295 ve i esittir -1" gibi bir sonuç alırız. Amaaa biz i'yi 4294967295'e eşitlemiştik nasıl -1  olduu. Bunun nedeni 4294967295u sayisi 32 tane 1  bitinden oluşuyor ancak unsigned. Unsigned sayinin değerinin bitlerden nasıl hesaplandığından bahsettim. Biz i'yi u'ya esitlediğimizde i'nin bit dizilimi u'ya eşit oluyor yani i'de de 32 tane bir var ancak signed yani two's complement gösterimiyle int. Onun değerini hesaplarken o zaman :
2^0*1 + 2^1*1 + ..... + 2^29*1+ 2^30*1 - 2^31
2^30'a kadar olan değerlerin toplamı 2^31-1 bildiğimiz üzere. 2^31-1-2^31 = -1.
Yani bütün bitleri 1 olan bir integer aslında -1'dir :)
Bunu değişik sayılar üzerinde de deniyebilirsiniz bit dizilimleri aynı olmasına rağmen sayıların türlerinin farklı olmasından dolayı (unsigned int ve int) değerlerinin hesaplanma yönteminin farklı olduğunu görebiliriz.
Ben sadece unsigned int ve int' den bahsettim ancak aynı olay short unsigned short vs. de de geçerli.

Peki

unsigned u1 = -45 ;    
printf("u1 esittir %d",u1);

dediğimiz zaman ne olur? Burada unsigned int negatif sayıları gösteremeyeceğinden u1'in değerini çok büyük bir sayı olarak gösterir -45 ile alakası olmayan bu da bit dizilimlerinin değişmemesi ve unsigned int ve int'de farklı yorumlanmasından kaynaklanmaktadır.
Kısaca algoritma şu şekildedir :
Eğer int'ten unsigned int e çevirme yapıyorsak çevirdiğimiz sayıya x diyelim :
Eğer x 0'dan küçükse yani unsigned sayının gösteremeyeceği bir sayıysa : x'in değerine 2^w değeri eklenir burada w int'in gösterildiği bit sayısıdır bizim örneğimizde 32. Yani yukarında u1'in değeri -45+ 2^32. Eğer x 0'dan büyük ise değeri değişmez.
Eğer unsigned int'ten int'e çevirme yapıyorsak çevirdiğimiz sayıya x diyelim :
Eğer x 2^(w-1)'den küçükse yani int'in gösterebileceği en büyük sayıdan küçükse, x'in değeri değişmez. Eğer x max int'ten büyükse o zaman x'den 2^w'yi çıkartırız ve yeni x in değeri x-2^w olur.
Bunu kendimiz int'in ve unsigned int'in gösterebileceği en büyük ve en küçük sayıları deneyerek kendimiz de görebiliriz. Kendimiz de hesaplayabiliriz unutmamamız gereken nokta bit dizilimlerinin aynı kaldığı.