Skip to content
Bloga Dönün
Eğitimler

Q15 sabit noktalı sayılar: dönüşüm, yuvarlama ve taşma

Q15'te 0,1 değeri 3277 olarak saklanır, 1,0 ise doyum olmadan -1,0'a döner. Q gösterimini okuyup elle dönüştürün, sonucu çevrimiçi ücretsiz doğrulayın.

15 dakika okuma

Q15 sabit noktalı sayılar: dönüşüm, yuvarlama ve taşma

Q15 sabit nokta, bir kesri sıradan bir 16 bitlik işaretli tam sayı olarak saklar; ikilik ölçek örtük biçimde 2^15 = 32768’dir. Kodlama tek bir çarpma ve tek bir yuvarlama adımından oluşur:

raw = round(value × 32768)

Kod çözme ise tek bir bölme:

value = raw ÷ 32768

Aritmetiğin tamamı bu. 0.5 × 32768 = 16384 olduğu için 0.5 bellekte 16384 tam sayısı olarak durur ve 16384 ÷ 32768 tam olarak 0.5 verir. Ne üs alanı vardır ne de gizli bit. İkilik nokta yalnızca sizinle o sözcüğü okuyacak kişi arasındaki bir anlaşma; donanımın gördüğü şey her zaman bir int16.

Bu formülden doğrudan iki sonuç çıkar ve insanların bir öğleden sonrasını kaybettiği yer tam da bu ikisidir.

0.1 × 32768 = 3276.8 ve bu bir tam sayı değil. Q15 3277 değerini saklar, geri okuduğunuz değer de 0.1 değil 0.100006103515625 olur.

1.0 × 32768 = 32768; bu da 16 bitlik işaretli tam sayının en büyüğünden bir fazla. Yani 1.0 değerinin Q15 karşılığı hiç yoktur ve kodunuzun bu durumda ne yapacağı, çoğu kod tabanının hiçbir yere yazmadığı bir politikaya bağlıdır. Doyum uygularsanız 0.999969482421875 elde edersiniz. Sararsanız -1.0.

Kalan her şey bu iki arızadan türüyor. İki veri sayfası birbiriyle çelişirken Q gösterimini nasıl okursunuz, bir değeri iki yönde elle nasıl çevirirsiniz ve araç zinciriniz yuvarlama ile taşma için sizin adınıza hangi kuralı seçmiş?

Q15, Q1.15, Qm.n: aynı yerleşimi mi anlatıyor?

Üç ayrı kaynağa Q15’in ne demek olduğunu sorun, üç ayrı yanıt alabilirsiniz. Gösterim hiçbir zaman standartlaşmadı; üstelik anlaşmazlığın tamamı tek bir bit üzerine.

Qm.n gösterimi nasıl okunur

Dürüst olan, iki sayılı biçim. Qm.n içinde n kesirli bit sayısı, m ise tam sayı bit sayısıdır. Bu yazı işaret basamağını m içinde sayıyor; Q16.16’yı 32 bitlik bir sözcük yapan okuma da bu: işaret dahil 16 tam sayı biti, 16 kesir biti, ölçek 2^16 = 65536. 1.5 değerini Q16.16 ile kodlayın, 1.5 × 65536 = 98304 çıkar, hex karşılığı 0x00018000 olur ve ortada hiç yuvarlama hatası kalmaz.

Sorun işaret bitiyle başlıyor. Kimi yazarlar bu biti m içinde sayar, kimi de üstüne ekler. İlk yaklaşıma göre Q1.15 16 bitlik bir sözcüktür: bir işaret/tam sayı basamağı artı 15 kesir biti. İkinci yaklaşıma göreyse aynı etiket 17 biti tarif eder ki böyle bir makine yok.

Aynı Q15 etiketi neden belgeden belgeye farklı genişlikler anlatıyor

Tek sayılı Q15 biçimi m bileşenini tamamen atar ve genişliği örtük bırakır. DSP pratiğinde neredeyse her zaman 15 kesirli biti olan, işaretli, ikiye tümleyen 16 bitlik bir sözcük anlamına gelir; TI ve ARM ekosistemlerinin onlarca yıl önce üzerinde uzlaştığı anlam da budur.

Ama şuna benzer, bolca kopyalanmış açıklamalarla karşılaşacaksınız: Q15, 15 kesirli bit demektir; dolayısıyla 32 bitlik bir sayı tanımlarsak 1 işaret biti ve 16 tam sayı biti olur. Dikkatle okuyun: cümle işaret bitini m içinde değil, m’nin üstüne ekleyerek sayıyor — burada izlenenin öteki uzlaşımı. Tarif ettiği yerleşim gerçek ve normalde Q16.15 diye yazılır. Yanlış olan, üzerindeki etiket: 16 tam sayı biti olan 32 bitlik bir sözcük, iki uzlaşımın hiçbirinde Q15 değildir. Aynı paragraf yeterince çok blogda çoğaltıldığı için artık bazı aramalarda doğru tanımın önüne geçiyor.

Tanımadığınız bir belgede çıplak bir Q15 görürseniz bunu olgu değil, hipotez sayın. Ölçebileceğiniz bir şeyle karşılaştırın: bellek haritasındaki yazmaç genişliği, sürücü başlık dosyasındaki C türü ya da bilinen bir örnek değer.

Başkasının koduyla karşılaşınca ayakta kalan tek tanım

Şu üçünü yazdığınız anda belirsizlik bitiyor:

  • İşaretlilik — işaretli ikiye tümleyen ya da işaretsiz
  • W — sözcükteki toplam bit sayısı
  • F — kesirli bit sayısı

signed, W=16, F=15 yanlış okunamaz. unsigned, W=32, F=16 da öyle. Geri kalan her şey bunlardan türer: ölçek 2^F, çözünürlük 2^-F, aralık ise sözcüğün tam sayı aralığının 2^F’ye bölünmüş halidir. Bu üç değeri protokol belgesine ve struct yorumuna koyun, bu tartışmayı bir daha hiç yaşamayın.

Çevrimiçi Q biçimi dönüştürücü de aynı nedenle her sonucun yanına işaretliliği, W, F ve ölçeği yazar. Bir veri sayfası “Q15” derken meslektaşınızın ayrıştırıcısı başka bir şey söylüyorsa, bilinen tek bir sözcüğün kodunu çözmek meseleyi on saniyede kapatır.

Dönüşüm formülü, elle adım adım

Her iki yön de kâğıt üzerinde yapılacak kadar kısa; osiloskop ekranındaki bir hex dökümüne bakarken bunun değeri büyük.

Kayan noktadan sabit noktaya: round(x × 2^F)

0.5 değerini Q15’e taşıyalım.

  1. Ölçekleyin: 0.5 × 32768 = 16384
  2. Yuvarlayın: zaten tam sayı, 16384 olduğu gibi kalır
  3. Aralığı denetleyin: işaretli 16 bit -32768 ile 32767 arasını tutar, 16384 buraya sığar
  4. Saklayın: 16384, hex 0x4000, ikilik 0100000000000000

Yalnızca 2. adım bilgi kaybedebilir, yalnızca 3. adım başarısız olabilir. Sabit noktada ilginç olan ne varsa bu iki yerden birinde olur.

Sabit noktadan kayan noktaya: raw ÷ 2^F

Şimdi ters yöne gidelim; başlangıç noktamız, işaretli bir Q15 alanında 0xC000 okuyan bir yazmaç yakalaması olsun.

  1. Metni işaretsiz 16 bitlik bir kod olarak ayrıştırın: 0xC000 = 49152
  2. Biçim işaretli ve en üst bit kurulu, o yüzden 2^16 çıkarın: 49152 - 65536 = -16384
  3. Ölçeği düşürün: -16384 ÷ 32768 = -0.5

İnsanların atladığı adım ikincisi. İkiye tümleyen düzeltmesi olmadan 0xC000 değeri +1.5 diye okunur; bu sayı Q15 aralığında bile değildir, işte size bedava bir sağlama. Kodu çözülmüş bir değer biçimin aralığının dışına düşüyorsa, neredeyse kesinlikle işaret adımını unutmuşsunuzdur.

from fractions import Fraction


def encode_q15(x):
    """Decimal value -> signed Q15 stored integer (ties to even)."""
    return round(Fraction(str(x)) * 32768)


def decode_q15(word):
    """Unsigned 16-bit code -> exact Q15 value."""
    if word & 0x8000:
        word -= 0x10000
    return Fraction(word, 32768)


print(encode_q15(0.5))              # 16384
print(encode_q15(0.1))              # 3277
print(float(decode_q15(0xC000)))    # -0.5
print(float(decode_q15(0x0CCD)))    # 0.100006103515625

Fraction burada gerçekten iş yapıyor. Önce bir float üzerinden ölçeklemek, tam da ölçmeye çalıştığınız anda ikilik yuvarlamayı geri getirirdi; Fraction(str(x)) ise yazdığınız ondalık sabiti okur, ona en yakın double’ı değil.

Hex sözcüğünü okumak ve yazmak

Bu değerler yazmaç görünümlerinde zaten hex olarak karşınıza çıkar ve dönüşüm mekaniktir: 3277 sayısının hex karşılığı 0xCCD, W/4 basamağa tamamlanınca 0x0CCD. Her zaman tamamlayın. Bir Q15 sözcüğü dört hex basamağıdır, bir Q31 sözcüğü sekiz; baştaki sıfırı atmak, bir değerin toplu kod çözücüde hizadan kaymasının en bilinen yoludur.

Ham kodları hex’te işaretsiz tutun. 0x8000, +32768 değil, en negatif Q15 sözcüğüdür ve onu -0x8000 diye yazmak kimsenin işine yaramaz. Bayt sırası ayrı bir konu: Q biçimi sayısal ölçeği belirler, endianness hakkında hiçbir şey söylemez. 0x0CCD sözcüğünün little-endian dökümü CD 0C baytları olarak gelir. Bir dökümü okurken yalnızca taban değiştirmek istiyorsanız, sayı tabanı dönüştürücü ikilik, sekizlik ve hex arasında ölçek ya da işaretli genişlik iliştirmeden çalışır.

Q7, Q15, Q31 ve Q16.16: aralık ve çözünürlük

Bu tablodaki her sayı, bir uçta -2^(W-1) değerinin 2^F’ye bölümü, diğer uçta (2^(W-1) - 1) değerinin 2^F’ye bölümüdür. Değerler tam, gösterim için yuvarlanmış değil.

BiçimGenişlikKesir biti FÖlçek 2^FEn küçükEn büyük (tam)Çözünürlük
Q787128-1127/128 = 0.99218751/128 = 0.0078125
Q15161532768-132767/32768 = 0.9999694824218751/32768 = 0.000030517578125
Q3132312147483648-1(2^31−1)/2^31 = 0.99999999953433871269226074218751/2^31 ≈ 4.6566128730773926e-10
Q16.16321665536-32768(2^31−1)/65536 = 32767.99998474121093751/65536 = 0.0000152587890625

Tablodaki sınır değerlerinden herhangi birini Q biçimi dönüştürücüye yapıştırın; saklanan tam sayıyı, hex sözcüğü ve tam ondalığı geri yazar. Bir ürün yazılımı sabitini sevkiyattan önce doğrulamanın en hızlı yolu budur.

Q15 aralığının üst ucu neden 0.999969482421875

Q15 aralığı bakışımsızdır ve bu bakışımsızlık sabit noktaya özgü bir şeyden değil, ikiye tümleyenden gelir. İşaretli 16 bitlik bir sözcük -32768 ile 32767 arasındaki tam sayıları kapsar. İki ucu da 32768’e bölün; aralık -32768/32768 ile 32767/32768 arasına, yani -1 ile 0.999969482421875 arasına gelir.

Yani 1.0 tam olarak bir LSB kadar dışarıda kalır. Bu, “en büyük değer yaklaşık 1.0” ya da “yuvarlamayla 1.0” durumu değil; değerin karşılığı hiç yok. Q15 için 1.0 bildirip doyum uyguladığını söylemeyen bir dönüştürücü size yalan söylüyor.

-1 neden aralığın içinde

Epeyce kaynak Q15 aralığını iki ucu da açık biçimde -1 < X < 0.9999695 diye yazar. Alt sınır yanlış. -32768 ÷ 32768 = -1 tam olarak; yani -1 gösterilebilir bir değerdir ve [-1, 0.999969482421875] aralığının iki ucuna da tam olarak ulaşılır. Aynı aralığı [-1, 1) biçiminde yazılmış da göreceksiniz; bu, aynı şeyin tam ölçek üzerinden söylenmiş hali: 1.0 değerinin kendisine ulaşılamaz, ama ulaşılabilen en büyük değer, ondan biraz aşağıdaki belirsiz bir sayı değil, tam olarak 0.999969482421875 sayısıdır.

Bu bir gösterim ayrıntısından fazlası. Çarpmayı bozan değer -1’dir, çünkü -1 × -1 = 1 ve 1 aralığın dışındadır. -1’e hiç ulaşılamayacağını varsayan biri, onu yakalayacak doyum dalını hiç yazmayacaktır.

Aynı kaynaklardaki kırpılmış üst sınır 0.9999695 bir gösterim yan etkisidir. Değerin devreden bir tarafı yok: değer 32767/32768 ve 32768 ikinin kuvveti olduğu için ondalık açılım 15 basamak sonra 0.999969482421875 diye biter.

0.1 sayısı Q15’e sığmaz

Ölçekleyin, sorun anında görünür: 0.1 × 32768 = 3276.8. Sabit nokta yalnızca tam sayı saklayabildiği için bir yerden verilmesi gerekir.

En yakına yuvarlamayla saklanan sözcük 3277, hex 0x0CCD olur. O sözcüğün temsil ettiği değer şudur:

3277 ÷ 32768 = 0.100006103515625

Nicemleme hatası, istediğinizle ızgaranın verebildiği arasındaki farktır:

0.100006103515625 - 0.1 = +0.000006103515625

Bu, yaklaşık altı milyonda bir, yani bir LSB’nin beşte biri kadar. Ses seviyesi denetiminde bunu kimse duymaz. Saniyede bin kez üstüne ekleyen bir integral alıcıda ise birikmiş değerde saniyede kabaca 0.006 kadar istikrarlı bir kaymaya dönüşür.

Sabit noktada hata düzgün dağılır, kayan noktada dağılmaz

Q15, [-1, 0.999969482421875] boyunca tam olarak 1/32768 aralıklı 65536 noktalık bir ızgara serer. 0.9 civarındaki adım, 0.0001 civarındaki adımla aynıdır; yani en kötü durumdaki mutlak hata her yerde yarım LSB’dir. Bu da hata çözümlemesini en güzel anlamda sıkıcı kılar: bir süzgecin gürültü tabanını, yeni başlamış bir mühendisin denetleyebileceği bir aritmetikle sınırlandırabilirsiniz.

IEEE 754 bunun tersini yapar. Anlamlı bit sayısını sabit tutup üssü oynatır; böylece komşu double’lar arasındaki mutlak boşluk büyüklükle birlikte büyürken bağıl hata neredeyse sabit kalır. 1.0 civarında bu boşluk yaklaşık 2.2e-16, 1e12 civarında ise yaklaşık 0.0001220703125’tir.

Aynı arıza, farklı biçim. Bir double da 0.1 sayısını tutamaz. 0.1000000000000000055511151231257827021181583404541015625 saklar; 0.1 + 0.2 işleminin 0.30000000000000004 döndürmesi de buradan geliyor. Bu vaka, kardeş yazı olan kayan nokta hassasiyeti rehberinde adım adım işleniyor. Sabit nokta, ondalık kesirlerin ikilikteki sorununu çözmez; yalnızca hatanın boyutunu öngörülebilir kılar.

Üç yuvarlama kipi, aralarında bir LSB fark

Yuvarlama kuralı veri sözleşmesinin parçasıdır, bir gerçekleştirim ayrıntısı değil. Yuvarlamada anlaşamayan iki doğru gerçekleştirim, sonsuza dek son bitte farklılaşan test vektörleri üretir ve bunun peşine düşmek can sıkıcı bir iştir.

KipKural10911.744-3276.8
En yakına yuvarlama, eşitlikte çifteEn yakın ızgara noktası; tam yarımlar çift tam sayıya gider10912-3277
Sıfıra doğru kesmeKesirli kısmı at, büyüklük yalnızca küçülür10911-3276
Eksi sonsuza doğru yuvarlama (floor)Sayı doğrusunda her zaman aşağı10911-3277

İki sütun, tek örneğin neden asla yetmediğini gösteriyor. Pozitif bir değerde kesme ile floor aynı sonucu verir. Negatif bir değerde tam bir LSB ayrışırlar, çünkü kesme -3276.8 değerini sıfıra doğru yukarı çeker, floor ise aşağı iter.

Herkesin yanlış yaptığı örnek

0.333 sayısı Q15 için iyi bir sınama, çünkü bir sınıra yakın duruyor. 0.333 × 32768 = 10911.744.

  • Kesme: 10911, geri okunuşu 0.332977294921875
  • En yakına yuvarlama: 10912, geri okunuşu 0.3330078125

Eski öğreticiler 10911 yazıp hangi kuralın bu sonucu ürettiğini söylemeden geçer. Bu sayıyı, kodlayıcısı yuvarlama yapan bir kod tabanına götürün; altın test vektörleriniz ilk çalıştırmada patlar ve aradaki bir birimlik fark, bir politika uyuşmazlığından çok yazım hatası gibi görünür.

Üç kuralın birbirinden ayrıldığı yer negatifler. -0.1 × 32768 = -3276.8 işlemi kesmeyle -3276 (değer -0.0999755859375), floor ya da en yakına yuvarlamayla -3277 (değer -0.100006103515625) verir. Kesme de en yakına yuvarlama da iki işarete aynı davranır — sırasıyla ±3276 ve ±3277 — yani bakışımlı bir katsayı çifti bakışımlı kalır. Floor öyle yapmaz: +0.1 değerini 3276, -0.1 değerini -3277 yapar; çift de bir birim kadar bakışımsız çıkar.

Diliniz varsayılan olarak ne yapıyor

Dört yaygın ortam dört ayrı kural izliyor. Yanlış olan yok; sorun, hiçbirinin bu kuralı belgesinin ilk sayfasına yazmaması.

  • C/C++: kayan noktadan tam sayı türüne yapılan dönüşüm sıfıra doğru keser. (int16_t)(0.333f * 32768) sonucu 10911 verir.
  • Python: yerleşik round() eşitlikte çifte yuvarlar; yani round(3276.8) sonucu 3277, round(2.5) sonucu 2 olur.
  • JavaScript: Math.round eşitlikleri artı sonsuza doğru kırar, bu da bakışımlı değildir. Math.round(2.5) sonucu 3 ama Math.round(-2.5) sonucu -2.
  • Donanım: birçok DSP çarp-topla yolu kaydırma sırasında yuvarlar ve en yakın-çift kipini bir mod biti olarak sunar; referans C modeli ile silikon, biri o mod yazmacını okuyana kadar birbiriyle anlaşamaz.

Bir kural seçin ve adını biçim belirtiminde W ile F’nin yanına yazın. Test vektörleriniz de aynı kuralla üretilsin.

Taşma: doyum mu, sarma mı

Q15 taşması üç şeyden birini yapar: değeri reddeder, 0.999969482421875 değerine kırpar ya da sararak -1.0 yapar. Hangisini alacağınız araç zincirinizin seçtiği bir politikadır ve üçüncü seçenek işareti sessizce tersine çevirir.

1.0 alın. Ölçekleme 1.0 × 32768 = 32768 verir, işaretli 16 bitlik en büyük tam sayı ise 32767. Değer tam olarak bir birim aralığın dışında.

PolitikaSaklanan sözcükGeri okunan değerAşağı akışta nasıl görünür
Hatayokdönüşüm reddedilirGürültülü, yakalanabilir, araçlar için genelde doğru seçim
Doyum0x7FFF = 327670.999969482421875Kulakla ya da gözle 1.0’dan ayırt edilemez
Sarma0x8000 = -32768-1.0Tam ölçekte işaret tersine döner

Doyum satırı 0.000030517578125 kaybeder ve kimse fark etmez. Sarma satırı tam ölçekli pozitif bir örneği tam ölçekli negatife çevirir; bir ses yolunda bu, odanın öbür ucundan duyulan bir “tık” sesidir. Bir denetim döngüsünde ise ters yönde tam ölçekli bir komuttur.

Sarmanın tehlikeli yanı, kusursuz görünen bir sözcük üretmesi. 0x8000, -1.0 değerinin geçerli bir Q15 kodlamasıdır. Aşağı akıştaki hiçbir şey onu gerçek bir -1.0 örneğinden ayırt edemez; yani olaydan sonra arayabileceğiniz bir imza kalmaz, taşmanın olduğu noktayı ölçümlemedikçe bulamazsınız.

DSP yongaları neden doyumlu komutlarla geliyor

Doyum, sinyal işlemenin istediği davranış olduğu için işlemciler onu bir dallanmaya bırakmak yerine doğrudan gerçekleştirir. ARM’da QADD/QSUB ile SSAT/USAT doyumlu kaydırma komutları var, NEON’da VQADD ve benzerleri, x86 SSE’de ise paddsw gibi paketlenmiş doyumlu toplamalar. TI’nin C6000 ve C55x aileleri doyumu biriktirici yolunda bir mod biti olarak sunar.

Bir süzgeçte ya da bir mikserde ara sıra kırpılan bir örnek, küçük ve yerel bir bozulmadır. Sarmış bir örnek ise enerjisi tüm spektruma yayılan bir süreksizliktir. Donanım biraz yanlışı felaket derecede yanlışa yeğliyor, ama yalnızca siz doyumu etkinleştirdiyseniz. Aynı yongada sıradan C tam sayı aritmetiği hâlâ sarar.

Q15 çarpımı neden 15 bit sağa kaydırma istiyor

İki Q15 sözcüğünü tam sayı olarak çarpın; sonuç doğrudur ama artık Q15 değildir. Çarpma sırasında kesirli bitler toplanır: Q15 × Q15, Q30 verir.

0.5 × 0.5 üzerinden gidelim; doğru yanıtın 0.25 olduğu açık:

16384 × 16384 = 268435456          ← this is Q30, not Q15
268435456 ÷ 2^30 = 0.25            ← read as Q30, correct
268435456 >> 15 = 8192             ← realign to Q15
8192 ÷ 32768 = 0.25                ← same answer, back in Q15

268435456 sayısını Q15 diye yorumlarsanız 8192.0 okursunuz ki bu 32768 kat sapmadır. Bütün hata bu çarpandan ibaret ve “neredeyse çalışan” sabit noktalı süzgeçlerin neden sık sık ikinin bir kuvveti kadar şaştığını da bu açıklıyor.

Çarpımın ayrıca yere ihtiyacı var. İki 16 bitlik değer çarpıldığında 32 bite kadar çıkar; yani ara değer int32_t olmalıdır. Çok sayıda çarpımı biriktirmek daha da fazla pay ister; C55x gibi parçalarda DSP biriktiricileri 40 bit genişliğinde olduğu için bunu kaldırabiliyor.

Kaydırmayı yuvarlayın, bitleri öylece atmayın

Çıplak bir >> 15 alttaki 15 biti çöpe atar; bu da işaretli değerler için eksi sonsuza doğru kesmedir. Önce çıkış hassasiyetinin yarım LSB’sini eklemek, bunu en yakına yuvarlamaya dönüştürür:

#include <stdint.h>
#include <stdio.h>

static int16_t sat_q15(int32_t v) {
    if (v >  32767) return  32767;
    if (v < -32768) return -32768;
    return (int16_t)v;
}

static int16_t mul_q15(int16_t a, int16_t b) {
    int32_t prod = (int32_t)a * (int32_t)b;   /* Q30 */
    int32_t back = (prod + (1 << 14)) >> 15;  /* round, then Q30 -> Q15 */
    return sat_q15(back);
}

int main(void) {
    printf("0.5*0.5   -> %d\n", mul_q15(16384, 16384));
    printf("0.1*0.1   -> %d\n", mul_q15(3277, 3277));
    printf("-1*-1     -> %d\n", mul_q15(-32768, -32768));
    printf("no-round  -> %d\n", (int)(((int32_t)3277 * 3277) >> 15));
    return 0;
}

cc -std=c11 -Wall -o q15 q15.c && ./q15 ile derlendiğinde çıktı şu olur:

0.5*0.5   -> 8192
0.1*0.1   -> 328
-1*-1     -> 32767
no-round  -> 327

Üçüncü satır, az önceki -1 durumu. -32768 × -32768 = 1073741824, bu da Q30’da 1.0 demek ve Q15 için aralık dışı; sat_q15 bu yüzden değeri 32767 değerine kırpar. Kırpmayı kaldırın; int16_t dönüşümü değeri -32768 olarak sarar ve -1 × -1 işlemi -1 sonucunu verir.

Son iki satır yuvarlama farkını gösteriyor. 3277 × 3277 = 10738729 ve çıplak kaydırma 327 (0.009979248046875), yuvarlamalı kaydırma ise 328 (0.010009765625) verir. Gerçek çarpım 0.01 olduğuna göre yuvarlamalı sürüm iki katından fazla yaklaşıyor. Bunun bedeli tek bir toplama.

Kaydırmanın kendisiyle ilgili bir uyarı: negatif işaretli bir tam sayıyı sağa kaydırmak C23 öncesinde gerçekleştirime bağlıdır, gerçi karşılaşacağınız her derleyici aritmetik kaydırma yapar. Bu içinizi rahatsız ediyorsa 32768 ile bölüp kaydırmayı derleyiciye bırakın ya da kaydırmayı, önce sapma ekleyerek işaretsiz bir tür üzerinde yapın. Kaydırmaların ve maskelerin daha geniş mekaniği bitwise işlemler rehberinde ele alınıyor.

Toplama önce eşleşen Q değerleri ister

Çarpma, Q değerini öngörülebilir biçimde değiştirir. Toplama ise uyuşmazlığa hiç tahammül etmez: bir Q7 sözcüğünü bir Q15 sözcüğüne eklemek saçmalık üretir, çünkü işlenenler aynı ölçeği paylaşmaz.

Önce bir kaydırmayla hizalayın. Q7’de 0.5 değeri 64’tür ve 64 << 8 sonucu 16384, yani Q15’te 0.5. Kaydırma miktarı kesirli bit farkıdır: 15 - 7 = 8.

Yukarı kaydırmak tamdır ama pay yer, çünkü Q15’e yükseltilen bir Q7 değeri daha geniş bir kaba ihtiyaç duyar. Aşağı kaydırmak kayıplıdır ve çarpmadakiyle aynı yuvarlama kararını gerektirir. Her iki durumda da her ara değerin Q değerini bir yoruma yazın. Ölçeklerin yalnızca yazarın kafasında yaşadığı sabit noktalı kodun bakımı bir ay içinde imkânsız hale gelir.

Q15 sabit nokta mı, IEEE 754 kayan nokta mı: nasıl seçilir

İkisi de ikilik basamak değeri sistemleridir; yani ilkesel olarak hiçbirinin doğruluk üstünlüğü yoktur. Seçim, hedef donanımın size neye mal olduğuna ve hangi güvencelere ihtiyaç duyduğunuza iner.

SoruSabit noktayı işaret ederKayan noktayı işaret eder
Donanım FPU’su var mı?FPU yok ya da yazılımsal kayan nokta kitaplığı varTek çevrimde işlem yapan donanım FPU’su var
Dinamik aralık ne kadar geniş?Bilinen ve sınırlı, normalize edilmiş ses gibiBirçok büyüklük mertebesine yayılıyor
Hat biçimi bir ölçek tanımlıyor mu?Protokol ya da yazmaç ikilik ölçeği sabitliyorAlan gerçekten bir float
Sonuçların derlemeler arasında bit düzeyinde aynı olması gerekiyor mu?Evet, tam sayılar her yerde aynı sonucu verirFMA ve iyileştirmelerle değişmesi kabul edilebilir
Bellek ya da bant genişliği dar mı?16 bitlik örnekler, 32 bitlik float’ların yerini yarıya indirirKısıt değil
Koda kim bakacak?Q gösterimine zaten hâkim bir ekipKarışık ekip, asıl risk ölçekleme hataları

Son satır şaka değil. Sabit nokta, hatayı çalışma zamanından tasarım aşamasına taşır; bu da yalnızca birileri o tasarım işini gerçekten yapıyorsa iyi bir takastır. Donanım FPU’su olan bir Cortex-M4F üzerinde tek duyarlıklı kayan nokta çoğu zaman hem daha hızlı hem de daha güvenli seçimdir; Q15’e uzanma refleksi ise artık baskın olmayan parçalardan kalma bir alışkanlıktır.

IEEE 754’ün devraldığı yer

Sözcüğün kendisi sabit bir ölçek yerine işaret, üs ve anlamlı kısım taşıyorsa kayan noktaya uzanın. Q biçiminin devreden çıktığı an tam da budur: bölünecek tek bir 2^F yoktur, çünkü üs değerden değere değişir.

İki gösterim pratikte sürekli birbiriyle karşılaşıyor. Algılayıcı verisi Q15 yazmaç sözcükleri olarak gelir, uzun bir hesaplama için float’a yükseltilir ve DAC için Q15 olarak geri döner. Bu yolun kayan noktalı yarısını bit bit incelemek için IEEE 754 dönüştürücü bir değeri işaret, üs ve mantise ayırır ve saklanan tam ondalığı yazar — Q biçimi dönüştürücünün sabit ölçek için yaptığı işin aynısı.

İkisi de aynı temele oturuyor

Q biçimi, IEEE 754 ve düz tam sayılar; hepsi aynı bitleri, noktanın nerede durduğu ve yer değiştirip değiştiremeyeceği konusunda farklı kurallarla okuyor. Basamak değeri kısmı ayağınızın altında sallanıyorsa ya da 0x0CCD sözcüğünü hesap makinesine uzanmadan 0000 1100 1100 1101 diye okumakta hızlanmak istiyorsanız, ikilik, hex ve sekizlik dönüşümü rehberi her iki biçimin de üzerine kurulduğu zemini anlatıyor.

Q15 sabit nokta SSS

Q15 ne demek?

Yaygın DSP uzlaşımında Q15, 15 kesirli biti olan, işaretli, ikiye tümleyen 16 bitlik bir sözcüktür: bir işaret basamağı ve 15 kesir basamağı. Ölçek 2^15 = 32768, çözünürlük 1/32768 = 0.000030517578125, aralık ise -1 ile 32767/32768 arasıdır. Q etiketleri belgeden belgeye değiştiği için etikete güvenmek yerine toplam genişliği ve işaretliliği doğrulayın.

Q15 ile Q1.15 aynı şey mi?

Genellikle aynı işaretli 16 bitlik yerleşimi anlatırlar; Q1.15 içindeki 1 işaret basamağını sayar. Ama gösterim evrensel değil ve bazı yazarlar işaret bitini m içinde saymak yerine üstüne ekler. Güvenilir tanım, işaretlilik artı toplam bit W artı kesirli bit F’dir: bu yerleşim için signed, W=16, F=15.

Q15’te 0.5 kaça karşılık gelir?

Q15’te 0.5 değeri, bellekte saklanan 16384 tam sayısıdır; hex karşılığı 0x4000. Aritmetik şu: 0.5 × 32768 = 16384; bu zaten bir tam sayı olduğundan ne yuvarlama ne de nicemleme hatası olur. Kod çözme bunu doğrular: 16384 ÷ 32768 = 0.5, tam olarak.

Q15’in en büyük ve en küçük değerleri nedir?

En küçüğü -1 ve aralığa dahildir, çünkü -32768 ÷ 32768 tam olarak -1 eder. En büyüğü 32767/32768 = 0.999969482421875. İkisine de tam olarak ulaşılır; yani aralık [-1, 0.999969482421875] ve dışarıda kalan değer 1.0. Alt sınırı açık aralık olarak yazan kaynaklar yanılıyor; bu hata -1 × -1 taşma durumunu da gizliyor.

Q15 taştığında ne olur?

Yürürlükteki politikaya bağlı. Hata politikası dönüşümü reddeder. Doyum en yakın uca kırpar; 1.0 böylece 0.999969482421875 olur, yani genellikle duyulmayan bir LSB’lik kayıp. Sarma ise 2^16 modunu uygular: 1.0 ölçeklendiğinde 32768 olur, bu da -32768 diye geri okunur, yani -1.0. İşaret tam tersine dönmüştür. DSP donanımı bu yüzden varsayılan olarak doyum uygular, ama sıradan C tam sayı aritmetiği sarar.

İki Q15 sayısı çarpıldıktan sonra neden 15 bit sağa kaydırılır?

Çünkü kesirli bitler toplanır. Q15 × Q15, Q30 bir çarpım üretir; yani tam sayı sonucu 15 yerine 30 kesirli bit taşır. 15 bit sağa kaydırmak onu Q15’e yeniden hizalar: 16384 × 16384 = 268435456 ve 268435456 >> 15 = 8192, bu da 0.25 olarak çözülür. Kesme yerine en yakına yuvarlamak için kaydırmadan önce 1 << 14 ekleyin ve 32 bitlik çarpım taşmasın diye ara değeri int32_t içinde tutun.

IEEE 754 yerine ne zaman Q biçimini kullanmalıyım?

Bir protokol, yazmaç haritası, DSP algoritması ya da kodek bir tam sayı sözcüğü için ikilik ölçeği zaten sabitlemişse Q biçimini kullanın; ölçek arayüzün parçasıdır ve seçme hakkınız yoktur. Değer geniş bir dinamik aralık gerektiriyorsa, hedefte donanım FPU’su varsa ya da alan gerçekten işaret, üs ve anlamlı kısım saklıyorsa IEEE 754’ü kullanın. Ölçek iliştirilmemiş düz bir taban değişiminde ikisi de geçerli değildir; o iş sıradan taban dönüşümüdür.

Kısa özet

Q15 sabit nokta bir çarpma, bir yuvarlama kararı ve bir aralık denetiminden ibaret. Girişte raw = round(value × 32768), çıkışta value = raw ÷ 32768. Formül önemsiz; arızaların tamamı kimsenin belgelemediği yerlerde yaşıyor.

O halde belgeleyin. Etiketin tek başına yettiğini varsaymak yerine her Q etiketinin yanına işaretliliği, W ve F’yi yazın. Yuvarlama kipini de aynı yere adıyla yazın, çünkü kesme ile floor negatiflerde tam bir LSB ayrışır. Taşmanın hata mı verdiğini, doyum mu uyguladığını, yoksa sarıp sarmadığını açıkça söyleyin, çünkü sarma durumu 1.0 değerini -1.0 yapar ve arkasında hiçbir kanıt bırakmaz.

Bir yazmaç sözcüğü ile bir hesap tablosu birbiriyle çelişiyorsa, bilinen tek bir değeri Q biçimi dönüştürücüde çözün — saklanan tam sayıyı, tam ondalığı, nicemleme hatasını ve gösterilebilir aralığı yan yana gösterir, hepsi de tarayıcıda hesaplanır. W ve F konusunda kimin varsayımının yanlış olduğunu anlamaya bu genelde yeter.

Etiketler: fixed-point dsp embedded q-format number-representation