Negatif Sayıların Bitsel Organizasyonu

Eğer daha önce verilerle bitsel düzeyde işlem yaptıysanız yahut ikili sayı sistemine aşinalığınız varsa verilerin bilgisayar sistemlerinin çalıştığı binary (ikili) sistemde nasıl tutulduğunu ve bu verilerin bizlerin anlayabileceği decimal (onluk) yahut bu değerlere karşılık gelen karakter setleriyle olan ilişkilerini biliyorsunuzdur.

Verilerin tutulduğu 8 bitlik öbeklerden oluşan byte’lar işaretsiz veri öbekleridir. Bu sebepten her ne kadar bitsel olarak verilerle çalışılsa da çoğu programcının gündeminde negatif sayıların bitsel organizasyonu bulunmaz.

Örneğin ‘U’ karakteri ASCII tablosunda 85 değerine karşılık gelir. Bu değer pozitif bir decimal değerdir. Ve ikili sistemdeki karşılığı ‭01010101’dir. 8 bit uzunluğundadır. O sebepten ASCII karakterler 8 bit yani 1 byte uzunluğunda olabilir.

Bilgisayar sistemleri bir pozitif tamsayıyı negatif olarak değerlendirmek ve depolamak istediğinde öncelikle bu sayılsal değere karşılık gelen ikili veriye bir işaret (flag) koyması gerekir. Bu işaret sayesinde binary verinin negatif olarak değerlendirileceğini anlamış olur. Bu işaret ise 1 bitlik bir değerdir. Bu 1 bitlik değere adından da anlaşılacağı üzere işaret biti (sign bit) denilir. Bu işaret biti ikili verinin en yüksek anlamlı lokasyonuna atanmaktadır. Ancak sayısal değerin negatif olarak değerlendirilmesinde veri tipi dolayısı ile uzunluğu önemlidir.

Yukarıdaki örneğimizi ele alırsak 01010101 ikili değerinin işaret bitini set ettiğimizde artık bu değer 8 bit sınırında negatif olarak değerlendirilecektir. ‭01010101 değerinin en yüksek anlamı biti (Most significant bit – MSB) en soldaki 8. bittir. Bu biti set ettiğimizde 8 bitlik bir sınır içerisinde veri negatif olacaktır. C dilindeki veri tipleri ve signed (işaretli), unsigned (işaretsiz) veri tipi ayrımı bu amaçladır. Bir byte uzunluğunda işaret biti set edilmiş verinin, 2 byte (short) tipinde tutulması bu verinin derleyici ve en nihayetinde işlemci tarafından en yüksek anlamlı (Most significant bit – MSB) bitinin 8. bit yerine 16. bit olarak değerlendirilmesini sağlayacaktır. Dolayısı ile 8 bitlik alanda tutulan negatif bir değerin 16 bitlik çerçevede pozitif bir sayı olarak değerlendirmesine sebep olacaktır.

Bu bilgiden yola çıkarak bir sayının negatif olup olmadığını if (value < 0)  yerine bitsel düzeyde de kontrol edebiliriz. Değerin en yüksek anlamlı biti set edilmiş değeri karşılaştıracağımız değer ile Mansıksal ve (Logical And) işlemine soktuğumuzda sayının negatif olup olmadığını öğreniriz. 8 bitlik çerçevede en yüsek anlamı biti set edilmiş değer 10000000’dir bunun hex karşılığı ise 0x80 değeridir.

char value = -84;

if (value & 0x80) //hex 10000000 
    printf("değer negatif");
else
    printf("değer pozitif");

Yukarıdaki kod çalıştığında değer negatif çıktısı görünecektir. Yukarıda örneğini verdiğimiz ‘U’ ASCII karakterin desimal değerine ait ikili sayının en yüksek anlamı bitini set en yüksek anlamlı biti (MSB) set ettiğimizde elde ettiğimiz yeni değer 11010101’in karşılığı –43 değeridir. Pozitif değeri negatif olarak değerlendirilmesini sağladık ancak sayısal değer olarak yanlış bir sonuç elde ettik. Çünkü günümüzde geçerli olan tüm bilgisayar mimarilerinde negatif sayı sadece bir işaret bitinden ibaret olarak kodlanmaz. Elbette pek çok farklı şekilde negatif sayıyı kodlayan sistemler olabilir ancak günümüzde kullandığımız bilgisayarlardan, akıllı telefonların işlemci mimarileri biraz sonra değineceğim şekilde negaif sayıyı kodlarlar. Peki negatif sayılar tam olarak nasıl kodlanır?

İkinin Tümleyeni (Two’s Complement)

İkinin tümleyeni bilgisayar mimarilerinin negatif sayıları kodlama biçimidir. Bunun yaygın olarak tercih edilmesinin avantajları vardır. İkinin tümleyeni olarak kodlamak bitsel matematik işlemlerini ekstra kontrollere yer bırakmadan doğal şekliyle gerçekleşmesini sağlar. İkinin tümleyeni alındığında yüksek anlamlı bit hem sayısal değerin işaretini değiştirir hem de sayının değerine doğrudan etki eder. Diğer bir durum ise sadece işaret biti ile sayının pozitif negatif durumunu tutan diğer kodlama metodlarında ortaya çıkan +0 ve –0 sorunu ikinin tümleyeni olarak yapılan kodlamada meydana gelmez. Misal bir pozitif değerin, ikinin tümleyeni ile bitsel toplamı 0’ı doğal olarak verecektir. Matematiksel olarak her ne kadar da sıfır (0) sayısının işaret alması gibi bir durum olmasa da bilgisayar mimarisi açısından 0 sayısının hem pozitif hem de negatif bir değer alabildiği varsayılacak ve sistem bu durumu tolere etmek için ek kontrollere ihtiyaç duyacaktır.

İkinin tümleyeninde -2n-1 ile 2n-1 – 1 aralığı kadar sayı tanımlanabilir. Sadece işaret bitiyle ifade edilen diğer sistemlerde bu durum her iki tarafta da

2n-1 – 1 kadardır. Çünkü ikinin tümleyeninde sayısal değeri işaret biti de etkileyebildiğinden fazladan 1 adet negatif sayısal değer tanımlanabilir. Örneğin ikinin tümleyeni ile kodlanmış 8 bitlik bir uzunlukta -128 ile 127 arasında sayısal değer tanımlanabilir. Veri türlerinin tuttukları değer limitleri tablolarında gördüğünüzde neden bunlar simetrik değil diye içindizden geçirdiyseniz sebebi işte bu :).  Diğerlerinde bu her iki tarafta da aynıdır. Yani -127 ile 127 aralığındadır. Bu tip avantajlar negatif sayının ikinin tümleyeni olarak kodlanmasını yaygın hale getirmiştir.

İkinin tümleyenin tümleyeni yine kendisini verir. Yani negatif bir değerin 2’nin tümleyenini uyguladığımızda yine bize o değerin pozitif halini verecektir.

İkili sayının ikinin tümleyenini almak iki şekilde yapılabilir.

Birinci yol önce Birin tümleyenini alıp bu değere 1 eklemektir. Birin tümleyenini almak demek değerin bitsel olarak tersini (mantıksal değil) almaktır. Bir başka deyişle 0’lar 1, 1’ler 0 yapılır. Ardından 1 eklenir.

Diğer yol ise en sağdan başlanarak 1 bit görene kadar 0’lar aynen yazılır. 1 görüldüğünde 1 de aynen yazılır. Kalan bitlerin ise tersi yazılır. (bit flip)

Yukarıdaki 85 desimal değerinin ikili karşılığı olan 01010101 değerine bu işlemlerden birini uygulayalım.

1. Yöntem

Orijinal Değer:  01010101
Tersi:           10101010

Bu değere 1 ekleyelim. Unutmamanız gereken şey 1 eklemenin bitsel toplama işlemidir. 1 değerinin 8 bitlik uzunlukta 00000001 olarak algılandığını unutmayın. bu 1 eklenmesi sonucunda

Birin Tümleyeni (Yani değerin tersi):                10101010
1 değeri                                             00000001
-------------------------------------------------------------
Sonuç:                                               10101011 (-85)

2. Yöntem

Orijinal Değer: 01010101

En sağdan başlayarak 1 görene kadar tüm sıfırları yazalım. En sağdaki bit 1. o yüzden yazmamız gereken bir 0 yok.

01010101

1 görene kadar yazalım demiştik. En sağdaki bit zaten 1. Onu aynen en sağa yazıyoruz. bir solundaki bite bakıyoruz.

01010101

0. En sağdan 1’i görene kadar 1 de dahil olmak üzere hepsini aynen yazıp, kalanları çevireceğiz demiştik. bir önceki adımda 1’i görüp aynen yazmıştık. Şimdi bu 0 bitinin tersini yazıyoruz.

00000011

Yeni değerimiz bu halde. Bir sonraki (3. bit) 1 değerinde

01010101

Çevirip yazdığımızda 0 olacak. Değerin son hali

00000011

Tüm bu adımları tekrar ettiğimizde aşağıdaki sonuca ulaşırız.

Orijinal Değer: 01010101
Sonuç:          10101011

C dilinde nasıl yapılır?

Yukarıda yaptığımız parmak hesabı adımları olayın mantığını kavramak içindi. Elbette programlama dillerinde bunu böyle yapmyoruz. Programlama açısından en kolay ve hızlı İkinin tümleyenini alma metodu 1. Yöntemdir. Yani önce tersini alıp 1 eklemek. C dilinde bir ikili verinin tersini mantıksal değil (Logical Not) operatörü ile yaparız. Bu operatör de C dilinde ~ işareti (Matematiksel eşitlik belirten işaret) ile simgelenir. Tıpkı and için & (Ampersand) kullanmamız gibi. Tersini aldıktan sonra değere normal + 1 ile işlemi bitirebiliriz.

Örneğin 32 bit uzunluğunda olan long veri türü için bir sayının negatif olup olmadığını eğer negatif değilse negatif yapan C kodu aşağıdaki şekilde yazılabilir.

//LONG veri tipinin derleme zamanı hesaplanan
//uzunluğu
#define LONG_BITSIZE (sizeof(long) *  8)

//Verilen desimal değeri ikili sayı sistemine
//göre konsola basan fonksiyon.
//bitSize argümanı ne kadar bit uzunluğu kadar
//yazılması gerektiğini belirler.
void printAsBinary(long value, int bitSize)
{
    for (int i = bitSize - 1; i >= 0; i--)
    {
        if (value & (1 << i))
            printf("1");
        else
            printf("0");
    }
}

//önce sayının desimal değerini
//ardından parantez içerisinde ikili sistemde
//karşığını ekrana yazan fonksiyon
void printValueAndBinaryFormat(long value)
{
    printf("%ld (", value);
    printAsBinary(value, LONG_BITSIZE);
    printf(") ");
}

int isNegative(long value,int putConsole)
{
    if (putConsole)
        printValueAndBinaryFormat(value);

    //0x80000000 32 bitlik uzunlukta
    //en yüksek anlamı bitin 1 olarak
    //atandığı değerin hex karşılığı
    if (value & 0x80000000)
    {
        if (putConsole)
            printf("negatif\n");
        return 1;
    }
    
    if (putConsole)
        printf("negatif degil\n");

    return 0;
}


//Sayıyı negatif yapan fonksiyon
long makeNegative(long value)
{
    long negValue;

    //Sayı zaten negatifse 
    //İşleme devam edilmesin
    if (isNegative(value,FALSE))
    {
        printf("%ld zaten negatif\n",value);
        return 0;
    }

    //value değişkeni içinn ikinin tümleyenini alıyoruz
    negValue = ~value + 1;

    //Önce orijinal değeri
    printValueAndBinaryFormat(value);
    printf("\n");

    //ardından yeni değeri yazıyoruz
    printValueAndBinaryFormat(negValue);
    printf("\n");

    return negValue;
}

int main()
{
    isNegative(-182,TRUE);
    isNegative(182,TRUE);
    isNegative(65,TRUE);
    isNegative(-99,TRUE);
    isNegative(19234,TRUE);
    isNegative(-19234,TRUE);

    printf("854 > %ld\n", makeNegative(854));
    return 0;
}

Programı çalıştırdığımızda şu çıktıyı alırız.

-182 (11111111111111111111111101001010) negatif
182 (00000000000000000000000010110110) negatif degil
65 (00000000000000000000000001000001) negatif degil
-99 (11111111111111111111111110011101) negatif
19234 (00000000000000000100101100100010) negatif degil
-19234 (11111111111111111011010011011110) negatif
854 (00000000000000000000001101010110)
-854 (11111111111111111111110010101010)
854 > -854

Artık biglisayar sistemlerinin negatif sayılara nasıl yaklaştığını biliyorsunuz. Halen aklınıza takılan noktalar varsa yorum olarak sorabilirsiniz. Uzun makaleler yazmak için şu sıralar yeterli vaktim olmadığından burayı da bomboş bırakmamak adına bu tarzda küçük ancak programcıya birşeyler katabilecek konulara kısa kısa değinmeye gayret edeceğim.

 

 

Leave a Reply

Your email address will not be published.