2'li sistemde kesirli sayılarla işlem yapmak ve Q formatı

Biraz geyik yapmak istiyorum.

Bilgisayar sistemlerinde 2 tabanının kullanıldığını biliyoruz. MCU dünyasına ilk adımı atanlar integer tipiyle işleme başlarlar.

İnteger sayı tipi demek tam sayı demektir. Örneğin 1,2,3 gibi. Eğer sadece pozitif sayılarla değil aynı zamanda negatif sayılarla da ilgileniyorsak bu durumda işaretli sayı tipini kullanırız. Bu durumda -2, -1 gibi sayılar da işin içine girer.

2'li sistemde işaretli sayılar işaret biti alırlar. Bu en soldaki bittir. Bu bit 0 iken sayımız pozitif,  1 iken sayımız negatif demektir.

Örneğin 8 bitlik veri alanında,

0 sayısı 00000000
1 sayısı  00000001
-1 sayısı ise  11111111 olarak gösterilir.

Negatif sayılarda kafamız karışabilir.  Bu normal çünkü on tabanında günlük hayatta 1 rakamının önüne - koyarak 1 sayısını negatife çevirmiş olurken neden bilgisayar sistemlerinde 00000001 pozitif +1 iken -1 sayısı 10000001 olarak gösterilmiyor?

Bu, sayı formatları ile ilgili bir durum. Eğer bilgisayarlarda sayılar için Sign Magnitude (SM) yani işaret ve büyüklük formatını kullanıyor olsaydık 1000001  ikili sayısı -1 anlamına gelecekti.

Fakat bilgisayar sistemlerinde bu format kullanılmaz. (Kendinizi zorlarsanız kullanabilirsiniz fakat hiç mantıklı değil.)

Sebebi oldukça basit, eğer sayılarımızı SM yöntemi ile gösterirsek işlemcimizin komut setine ilave olarak bu tip sayılar üzerinde aritmetik işlem yapan yeni komutların (toplama, çıkartma vb) da eklenmesi gerekir.

Örneğin -1 + 1 = 0 iken

10000001 (-1)  +  00000001 (+1) toplanırsa 10000010  (-2) elde edilir ki bu yanlış bir sonuçtur.

Bu yüzden bilgisayar sistemlerinde SM metodu kullanılmaz.

İşaretli sayıları ifade etmek için kullanılan bir diğer format ise 2's Complemet dediğimiz 2'ye tümleme tekniğidir.

Bu durumda -1 ve +1 gibi işaretli sayılar için de  normal toplama çıkartma komutları kullanılabilir.

Bu metotta negatif  sayılar sıfır - sayı işlemine tabii tutulur.

Örneğin 0000000 - 00000001 işlemi sonucunda -1 sayısı olan 11111111 sonucunu buluruz.

En soldaki bitimiz 7. sıradadır ve işaret bitidir.  Konumu itibariyle 2^7  128 büyüklüğündedir.

Bunu -128 olarak alın. Şimdi 0...6 bitlerinin değerine bakalım.

1111111=127

-128 + 127 = -1 demektir. İşte 2'ye tümleme yöntemi ile sayıların gösterim mantığı budur.

Sayı pozitif ise işaret biti 0 olarak sayının soluna getir. Sayı negatif ise sayının mutlak değerini sıfırdan çıkart ve sonucun soluna 1 işaret biti koy.

Sayının 1'e tümleyenini (NOT işlemi)  bulup sonuca 1 eklemek de aynı şeydir.

Matematiksel ifade ile 8 bitlik, 2'ye tümleme yöntemiyle gösterilen A sayısı şu şekilde ifade edilir. (A sayısının en düşük biti (LSB) 0. bit, işaret biti ise 7. Bit olarak gösterilecektir.)

A=-A_72^7+A_62^6+A_52^5+A_42^4+A_32^3+A_22^2+A_12^1+A_02^0
Bağıntı 1

Örneğin  A=10010011 sayısı nedir dersek

A=-12^7+02^6+02^5+12^4+02^3+02^2+12^1+12^0
yani -128 + 16 + 2 +1 = -109 diyebiliriz.

Tabiki A sayısının değerini şöyle de bulabilirsiniz. İşaretsiz olarak düşündüğünüzde 10010011 sayısı 147 demektir. O halde A sayımız 256 - 147 = -109 dur.

Fakat ik yöntemi benimseyin. Zaten biraz kafa yorarsanız aynı işlemin yapıldığını farkedeceksiniz.

Çünkü -128 + (A'nın 7 bitlik değeri) = 256 - (A nın 8 bitlik değeri) dir.

Tamam şu ana kadarkiler bir hatırlatmaydı. Şimdi sadece tam sayılarla değil kesirli sayılarla (Fractional Numbers) da ilgilenelim.

Örneğin 1.5 ya da  2.75 gibi sayıları da kullanmak isteyelim. Hemen float tipi değişkenlere sarılmayın. Float sayılarla işlem yapabilmek için ya floating point işlem yapabilen donanımınız olmalı ya da zamanlama açısından sizi acele etmeye mahkum bırakmayan bir uygulamanız olmalı.

Örneğin sıcaklık kontrolü gibi uygulamalarda servo çevrim süreniz saniyeler hatta dakikalar bazında uzun olabilir ve bu durumda özel donanıma gerek kalmadan floating point sayıları tamamen yazılımla ele alabilirsiniz.

Ancak kontrol algoritmalarının periyodik cevrim suresinin kısa olması gereken durumlarda yazılımla floating sayıların emülasyonu MCUyu inanılmaz yorar hatta size sinyal işlemek için yeterli zaman bırakmaz.

Neyse floating point notasyonu vs bu yazımın konusu değil.

Şimdi devam edelim.

2 li sayı gösterimindeki tam sayıların 10'lu sitemdeki karşılığını bulurken nasıl  bitlerin değerine ve bitin bulunduğu konuma göre hesaplıyorsak kesirli sayıları da bitin değerine ve bulunduğu konuma göre hesaplarız.

Noktanın sağ tarafında kalan bölüm, sayımızın kesir kısmına ait olup kesirli kısmın en sağındaki bit 0. bit, en solundaki de en yüksek numaralı bitimiz olmak üzere, 1 den küçük  A kesirinin A_k , ikili sistemdeki karşılığı, şu şekilde ifade edilir.

A_k=A_72^{-1}+A_62^{-2}+A_52^{-3}+A_42^{-4}+A_32^{-5}+A_22^{-6}+A_12^{-7}+A_02^{-8}
Bağıntı 2

Örneğin 1.5 sayısı nasıl gösterilir.

3 sayısını ikiye bölersek 1.5 sayısı elde ederiz.
Tam ve kesirli kısım için 2 byte veri alanı kullanarak 00000011 >>1  işlemini yaparsak 00000001 10000000 sonucunu buluruz.

A sayımızın tam kısmı A_T=0x01, kesirlik kısmı ise A_K=0x80

Soldaki 8 bitlik sayı 1 tam sayısını,  sağdaki 8 bitlik kesirli sayı ise 80 hex sayısını göstermektedir. Peki 0x80 ne anlama geliyor?

0x80 sayısında A7 biti 1 diğerleri sıfırdır. O halde aşağıdaki bağıntıdan;

A_K=A_72^{-1}+A_62^{-2}+A_52^{-3}+A_42^{-4}+A_32^{-5}+A_22^{-6}+A_12^{-7}+A_02^{-8}

A_K=1*2^{-1}+0*2^{-2}+0*2^{-3}+0*2^{-4}+0*2^{-5}+0*2^{-6}+0*2^{-7}+0 2^{-8}=0.5

İleride değineceğim ama şimdiden bir trik vereyim. Eğer kesirli kısmı integer düşünüp (örneğimizde kesir 0x80=128 idi) bunu 2^8=256 ya bölerseniz doğrudan kesirin değerini bulabilirsiniz. 128/256 = 0.5

Yani bu trik şu anlama gelir. Eğer kesirli kısmı 8 bit ile göstereceksem, bu kesirli sayıyı 8 kere sola kaydırırsam yani 256 ile çarparsam artık bu sayı tam sayı olur. Bu durumda tam sayıyı hesaplayıp gerisin geri 256'ya bölersem sayının kesirli değerini bulmuş olurum. Buna da Bağıntı 3 diyelim

O halde A sayımız A=A_T+A_K=1+0.5=1.5 Hatırlarsanız 3 sayısını saga kaydırdığımızda elde ettiğimiz 3/2 sayısını arıyorduk.

Bu arada, integer sayılarda örneğin 3 sayısını bir sağa kaydırdığımızda 1 sonucunu elde ederken, kaydırıldığı için dışarı atılan (yok olan) bitleri paketleyerek kesirli bölümü elde ettiğimizi fark ettiniz değil mi?

Neyse devam edelim;

Bu durumda 8 bitlik en küçük pozitif sayı olan 00000001 sayımızın 2^{-8}=0.00390655 olduğunu söyleyebiliriz.

Yani binde 4  kadar küçük sayılarla çalışabiliriz.

Eğer kesir kısmını 8 yerine 16 bit alırsak bu durumda en küçük pozitif sayımız 2^{-16}=0.000015 yani yüz binde 2 gibi küçük bir sayı sözkonusudur. Binde 1 civarı doğruluk için 16 bit fazlasıyla yeterlidir.

Örneğin  nominal akımı 10Amper olan bir motorun akımında, 1mA lik değişimi ifade etmek için 1/10000 için n=\frac{log(10^4)}{log(2)}=13.2  n=14 bit yeterlidir.

16 bitlik kesir pek çok  uygulama için yeterli doğruluğu verecektir.

Devam edelim ve negatif kesirli sayılardaki duruma bakalım.

Negatif kesirli sayılar da aynen negatif tam sayılar gibidir.

Örneğin  0.5 sayısını ele alalım. 4 bit tam kısım için 4 bit de kesirli kısım için kullanılsın.

Bu durumda 0.25 sayısı  1/4 yani 1>>2 den yazalım.

A=1_{10} = 0001.0000_2  iki bit sağa kaydırırsak 0.25_{10} = 0000.0100_2 buluruz.  Sağlama yapma açısından tam kısım A_T=0 kesirli kısım A_K=1*2^{-2}=0.25 (Bağıntı 2 kullanıldı) O halde sayımız 0.25dir.

ya da  kesirli kısım 0100 ikili sayısı decimal 4 demek  4*2^{-4} 0.25 demektir. (Bağıntı 3 kullanıldı)

O halde +0.25_10=0000.0100_2

Şimdi de -0.25 sayısını bulalım.

Verilen bir decimal kesirli sayının nasıl ikili sisteme çevrileceğini açıklamadım ama bu kuralı  Bağıntı 2'den  kendiniz de çıkartabilirsiniz. İpucu vereyim, Bağıntı 2 nin sol tarafını 2 ile çarparsanız sonucun tam kısmı 0 yada 1 olur. Sağ tarafı da 2 ile çarptığınızda A_n bitlerinden sadece bir tanesi bu şartı sağlar.

-0.25 sayısı  -1/4 yani -1>>2 den yazalım.

A=-1_{10} = 1111.0000_2  iki bit sağa kaydırırsak -0.25_{10} = 1111.1100_2 buluruz.  Sağlama yapma açısından tam kısım A_T=-1 kesirli kısım A_K=1*2^{-1}+1*2^{-2}=0.75 (Bağıntı 2 kullanıldı)

-1 sayısı 1111 iken bunu 2 bit sağa kaydırınca neden 0011 yazmadınız da 1111 yazdınız sorusunun cevabı işaretli sayılar sağa kaydırılırken işaret biti her defasında yerinde kalır da ondan. 

Hoppalaaa neden kesir kısmı 0.25 değil de 0.75 çıktı diyebilirsiniz. Bu soruyu birazdan cevaplayacağım.

-0.25_{10} = 1111.1100_2 Bu sayıyı 16 ile çarparsak 11111100 sayısını elde ederiz. Bu işaretsiz sayı tam sayı olarak 0xFC yani decimal 252 dir. Yani işaretli sayı olarak da -4 demektir.  -4 sayısını 16 ya bölersek -0.25 buluruz. (Bağıntı 3 kullanıldı)

Şimdi üstteki soruyu cevaplayalım.

İşaretli sayılarda sayının değeri Tam kısım + Kesirli kısım toplamından hesaplanır.

-0.25 sayısında tam kısmımız -1, kesirli kısmımız ise 0.75 idi. Bunları toplarsak -0.25 sonucunu buluruz.

Ama şöyle de düşünebilirsiniz. 0.25 = (0.010) al inversini, bulduk (1.101) ekle 1,  (1.110)

Negatif kesirli sayılarda neden böyle oldu kafanız karışmasın. Bizler SM gösterime alışık olup günlük hayatta bu formatı kullandığımız için negatif ikili sayılara bakarak doğrudan sonucu görmekte zorlanırız ara hesap yapma durumunda kalırız.

Aşağıdaki yazıyı **** ibaresine kadar okumak zorunda değilsiniz.

Mesela negatif sayılarla ilgili bir özellik  den bahsedeyim. Aslında bahsedeceğim yukarıdaki Bağıntı 1'i farklı şekilde okumaktan ibaret.

8 bitlik  11001111 negatif tam sayısı  0xCF dir.  Yani  Sıfır - 0xCF=0x31 yani -49 Decimal demektir.

11001111 sayısını 0xC0 negatif sayısı ile 0x0F pozitif sayısının toplamı olarak düşünebilirsiniz. Yani -64  + 15 = -49

11001111 sayısını negatif 0x80 sayısı ile 0x4F sayısının toplamı olarak da düşünebilirsiniz.

-128 + 79 = - 49

Dediğim gibi Bağıntı 1'i şekilden şekilde sokmak mümkün.

Neyse devam edelim.

****

Şu ana kadar öğrendiklerimiz ışığında 8 bitlik bir A sayısının değerini farklı tam ve kesir bit sayıları için bulabiliriz.

Örneğin 8 bitlik 0xC7 sayısının 1 biti tam sayı için, 7 biti de kesir için ayrılmışsa değeri nedir?

A=1.1000111_B A=-1 + 0.5 + 0.03125 + 0.015625 + 0.007812.5 =-0.445315 (Bağıntı 2 den)

Yada hızlıca (0x00-0xC7)/0x80=0x39/0x80=57/128=-0.445315 (Bağıntı 3 den)

Örneğin 8 bitlik 0xC7 sayısının 2 biti tam sayı için, 6 biti de kesir için ayrılmışsa değeri nedir?

A=11.000111_B A=-1 + 0.06225 + 0.03125 + 0.015625 =-0.890875 (Bağıntı 2 den)

Ya da  0x100 - 0xC7= 0x39        - 57/64=-0.890875 (Baağıntı 3 den)

cekmece

Nedir bu Q

Gördüğünüz gibi 8 bitlik bir sayı aynı zamanda tam ve kesir içeriyorsa kaç bitinin tam kısma, kaç bitinin kesir kısma ayrıldığını bilmemiz gerekir. Bundan sonra Q2.6 görürseniz bunun 2 biti işaret biti dahil olmak üzere tam sayı, 6 biti de kesir olan 8 bitlik bir paket sayı olduğunu düşünmeniz gerekir. Bazen 2.6 olarak da gösterilse de bu 2.6 ondalıklı sayısı ile karışabileceğinden Q2.6 anlaşılır olacaktır.

Q4.4 formatındaki iki sayının toplamını garantilemek için Q5.4 alana ihtiyaç duyulacaktır.

Çünkü iki sayı toplandığında elde edilecek sonuç, atanacak değişkene sığmayabilir. Örneğin 127 tam sayısı ile 1 tam sayısı toplanırsa sonuç +128 tam sayısı olur ve bu 8 bitle gösterilemez. (8 bite yazılan 128 sayısı (0x80) -128 anlamına gelir. )

8 bit işlemci ile çalışıyorsak Q5.4 byte kavramına yakışmadığından (9. biti işleyebilmek için 2. bir byte alana daha ihtiyaç var) Q12.4 daha yakışıklı olur. Yada Q5.4 yerine Q8.8 de yakışıklı olacaktır. Fakat Q6.10, Q7.9, Q8.8, Q9.7, Q10.6, Q11.5 gibi daha pek çok seçeneğimiz de var. Çünkü bunlar, kesir için ayrılan bit sayısı ile tam sayı için ayrılan bit sayısı toplamını 16 yapan seçeneklerdir.

Peki hangi Q seçilmelidir.

Eğer yapılacak işlemlerde tam sayı kısmı en fazla +127 olacaksa Q8.8 seçilebilir. Fakat +127 den daha büyük çıkacaksa Q9.7 yada daha çok tam sayı biti içeren seçenekler seçilebilir.

Bu karar, fix point aritmetiğinde karar verilmesi gereken en can alıcı kısımdır.

Overflow

Taşma (OVERFLOW)

İşaretsiz sayılarda toplama yada çıkartma işlemi yaparken sonuç sayıları saklayan değişkene sığmayacak kadar büyükse Cary durumu oluşur.

Toplama işleminde Cary değeri bir başka değişkene eklendiğinde bu artık sizin veri bitlerinizin uzantısıdır. Örneğin 8 bittlik değişkenimiz varsa ve 0xFF ile 0x01 sayısını toplarsak  sonuç 0x00 ve Cary=1 olur. Cary değerini bir değişkene yazarsak 0x01 bunun yanına da asıl değişkenimizin aldığı 0x00 verisini yazarsak 16 bit anlamında 0x0100 değerini elde ederiz ki bu da 256 değeridir. Yani 255 + 1 sonucunu saklar.

İşaretli sayılarda Cary'nin yaptığı içi Overflow durumu üstlenir. Cary 2.planda kalır.

Örneğin 8 bitlik +127 ve +2 sayılarını toplarsak 0x7F + 0x02 = 0x81 sonucu oluşur ve Overflow flağımız aktifleşir (1) ve Cary flağımız sıfırlanır.

0x81, 8 bitlik anlamda -127 demektir. Halbuki biz +127+2 işlemini yaptığımızda sonucun +129 olmasını bekleriz.

Eğer Overflow 1 ise Cary bitini ikinci bir değişkene atarak 0x00 değeri ele ederiz. Bunun yanına da elde ettiğimiz toplama sonucunu (0x81) yazarsak 16 bitlik 0x0081 değerini elde ederiz bunun 10 lu sistemdeki karşılığı da +129 dur.

O halde negatif sayılarla işlem yaparsak bir gözümüz daima Overflow flağının üstünde olmalıdır.

Benzer durum çıkartma işleminde de aynıdır. Terk fark, bazı işlemciler  çıkartma işleminde Barrow durumunu invert edilmiş halde kullanır buna komut setinden bakıp dikkat etmeniz gerekir.

 SatureSaturasyon  (Saturation)

Bazen overflow durumu oluştuğunda sayıyı daha fazla bitle ifade etmek yerine sayımızı en büyük değerde sınırlarız.  Örneğin 16 bitlik değişkendeki 0x7FFF (32767 ondalık) sayısını bir artırırsak 0x8000 değeri oluşur ve overflow flağı aktifleşir. Saturasyon aritmetiği yaparken böyle durumlarda sonuç registerine 0x7FFF yazarak sonucu sature ederiz. Burada kabul edilebilir bir hata yapılmıştır fakat, kabul edilemeyecek olan, sonucu 0x8000 olarak kullanmaktır.

yaymak

İşaret bitini yaymak (SIGN EXTEND)

Q4.4 notasyonunda  -0.5 sayısı 1111.1000 (0xC8) olarak gösterilir.

Bu 8 bitlik 0xC8 sayısını 16 bitlik bir değişkene yazmak istersek Si dili bunu zaten otomatik olarak yapar. Fakat arka planda ne olduğunu bilmek önemlidir.

Eğer 0xC8 sayısını 16 bit değişkene 0x00C8 olarak atarsanız korkunç bir hata yapılmış olur. Çünkü 0x00C8 pozitif bir sayıdır. Ne yapmamız lazım? Bunu 0xFFC8 olarak atmamız lazım. Yani yapılan iş 0xC8 sayısının 7. biti olan işaret bitini 8..15 bitlerin tamamına yazmaktan ibarettir. Buna işaret bitini yayma işlemi denir.

kaydırmak

Kaydırmak (SHIFT)

A>>3  A sayısını 3 kez sağa kaydır demektir.  İşaretsiz sayıları kaydırırken lojik sağa kaydırma  komutu kullanılır. Fakat işaretli bir sayıyı matematik anlamda sağa kaydırarak 2'ye böleceksek Aritmetik Sağa Kaydır komutu kullanmalıyız.

Örneğin 8 bitle tanımlı -8 tam sayısı 0xF8 olup bunu matematk anlamda 2 ye böleceksek 1 kez sağa kaydırmamız gerekir. Bu da 0xFC demektir. Yani yapılan iş işaret biti yerinde kalacak şekilde lojik olarak sağa kaydırmaktır. Eğer 0xF8 verisini lojik sağa kaydırmış olsaydık 0x7C sonucunu bulurduk.

Sola kaydırma 2 ile çarpma anlamına gelir. Bunun için aritmetik yada lojik kaydırma komutu tanımlaması yoktur her ikisi de aynı anlama gelir.

Fakat şunu aklınızdan çıkartmayın. Örneğin 8 bitlik  bir sayıyı 2 ile çarpmak üzere sola kaydırıp bunu 16 bitlik alana yazmanız gerekiyorsa 8 bit verinin işaret bitini alıp önce üst 16 bite yayıp ardından da 8 bitlik veriyi sola kaydırmanız gerekmektedir. Tabiki bu işlem işlemcinin komut setindeki bazı komutlarla kısa yoldan da yapılabilir.

Örneğin 8 bitlik 0x80 (-128 sayısını) sola kaydırıp sonucu 16 bit değişkene yazacaksak sonucun 0x0100 değil 0xFF00 olacağını unutmayın. Bu duruma Sign extend başlığında da değinmiştik.

MuhafızlarMuhafızlar (Guardian Bits)

Bazı işlemcilerde özellikle de DSP çiplerinde Accumulator genişliği, çipin veri genişliğinden daha büyük olur. Örneğin 16 bit işlemcinin Accumulator registeri 18 bit olabilir. Siz bu ekstra 2 bite değer yükleyemezsiniz fakat örneğin 0x7FFF +1 işlemi sonucunda Accumulator 0x18000 değerini alır. Daha sonra da bu değerden 0x02 çıkartıp 0x7FFE sonucuna ulaşabilirsiniz. Bu özelliğin olmadığı bir işlemcilerde bu tip durumlara önlemleri kendiniz almak zorundasınız.

Varsa işlemcinin bu ilave bitlerine guard bit denir. Değişken içinde sol taraftaki bitlerde fazladan yerimiz varsa zaten guard bitler kendiliğinden oluşur.

O yüzden mesela Q10.8 ya da Q12.6 işimizi görüyorsa Q12.6 bize 2 gardiyan bit sağladığından tercih sebebi olmalıdır.

Ne dersiniz artık Q notasyonunda tanımlı sayılarla toplama işlemi yapalım mı?

8 bitlik alanda saklanan  Q4.4 formatındaki -5.25  ile Q6.2 formatındaki 0.5 sayılarını toplayalım

-5.25 + 0.5 = -4.75 sonucunu bulmamız gerekir.

0xAC (-5.25)+ 0x02 (0.5)= 0xAE (-5.125) Sonuçtan da göreceğiniz üzere burada ölümcül bir hata yaptık. Ne yaptık? Noktanın pozisyonuna dikkat etmeden topladık.

(Q4.4) -5.25 = 0xAC
(Q6.2) 0.5 = 0x02

sayıları toplanacaksa önce bunların noktaları aynı hizaya gelecek şekilde günlük hayatta yaptığımız gibi kaydırma yapılmalıdır.

(Q6.2) 0.5 = 0x02 sayısını Q4.4 yapmak için iki bit sola kaydırırız ve 0x08 (Q4.4) elde ederiz.

Şimdi 0xAC ile 0x08 yi toplarsak 0xB4 sonucu elde edilir. Bu sayının  ondalık karşılığı 0xB=-(16-11)=-5

0x04 de 0.25 dir. toplarsak  -5+0.25=-4.75 buluruz.

Yada daha kısa yoldan 0xB4= -(256 -(11*16 + 4)) /16 =-4.75

Gördüğünüz gibi sayıları toplarken önce kaydırma ardından da toplama işlemi yaptık. Bu yüzden DSP işlemcilerde ya da DSP komutu barındıran MCUlarda tek clockta verileri okuyup kaydıran ya da toplarken verilerden birisini kaydırıp öyle toplayan ya da rama sonucu yazarken kaydırıp yazan komut setleri Q notasyonundaki işlemleri hızlandırmak adına vardır.

Fixpoint DSP = Hızlı Q matematiği yapabilen işlemci dersek yanlış olmaz.

Yeri gelmişken Fix point demek noktanın yeri sabit demektir. Floating point işlemlerde noktanın yeri kayar.  Fakat biz her ne kadar fixpoint aritmetiği yapıyor olsak da biz de sık sık Q formatını değiştirebiliriz. O yüzden ben fix point sayı demekten hoşlanmıyorum. Q notasyonundaki sayılar demeyi daha doğru buluyorum.

Q notasyonundan bahsederken hiç Q15 den bahsetmedik. Q15 özel bir durumdur. 16 bitlik veri alanı olduğunu ve bunun 15 bitinin kesire ayrıldığını söyler.  geriye kalan 16. bit ise işaret bitimizdir. Bu durumda  Q15 formatında sadece -1...+1 aralığındaki sayılarla uğraşabiliriz demektir.

Normalde Q1.15 ile Q15 aynı anlama gelir ve Q15 formatı çok faydalıdır.

Q15 formatındaki sayıları çarpmak

Q15 değişkenler  -1...+1 aralığındaki sayıları kapsar ve Q15 formatındaki iki sayının çarpımı Q30 sonucunu verir. 32 Bit registerde 30 bit kesir olduğuna göre 2 tane işaret biti vardır.

Bu durumda 32 bitlik sonucu 1 kez sola kaydırıp fazla olan işaret bitini atar ve soldaki 16 biti alırsak sonucu tekrardan Q15 formatına çevirmiş oluruz.

Örneğin 0.5 x 0.75 sayılarını çarpalım.

0.5 = 0x4000
0.75 = 0x6000

olduğuna göre 0x4000 * 0x6000  = 0x18000000 olur.
Bunu sola kaydırırsak 0x3000000 buluruz. üst 16 biti alırsak 0x3000 sonucu elde edilir.

Bu sayının ondalık karşılığı nedir?  A15=0 A14 =0 A13=1 A12=1

Yani + 2^-2 + 2^-3=0.375.  Biz neyi çarpmıştık 0.5 * 0.75 sayılarını bu çarpım sonucu da zaten 0.375 dir.

Görüldüğü üzere Q15 verilerini çarpmak çok az çaba gerektirir ve bu yüzden Q15, DSP algoritmalarında çok tercih edilir. Overflow oldu mu olmadı mı diye bakmaya bile gerek yoktur çünkü olmaz.

Q15 formatı kullanmazsanız çarpma işlemi ardından da overflow testi yapmanız gerekecektir.

Son olarak harmonik osilatörün fark denklemlerini  fix point olarak bir de floting point değişkenler kullanarak STM32F103C8 işlemcisinde hesaplatıp elde edeceğimiz osilasyon frekansına bakacağız.

Belki de bakmayız....

Bu yazı 2- Arm ve Asm, 7- Matematik kategorisine gönderilmiş. Kalıcı bağlantıyı yer imlerinize ekleyin.