Skip to content
Kembali ke Blog
Tutorial

Fixed-Point Q15: Cara Konversi, Pembulatan, dan Overflow

Nilai 0,1 di Q15 tersimpan sebagai 3277, dan 1,0 berbalik jadi -1,0 tanpa saturasi. Baca notasi Q, hitung manual, lalu cek online gratis.

15 menit membaca

Fixed-Point Q15: Cara Konversi, Pembulatan, dan Overflow

Fixed point (bilangan titik tetap) Q15 menyimpan sebuah pecahan sebagai bilangan bulat bertanda 16-bit biasa, dengan skala biner tersirat 2^15 = 32768. Encoding-nya hanya satu perkalian ditambah satu langkah pembulatan:

raw = round(value × 32768)

Decoding-nya satu pembagian:

value = raw ÷ 32768

Itu seluruh aritmetikanya. 0.5 × 32768 = 16384, jadi 0.5 menghuni memori sebagai bilangan bulat 16384, dan 16384 ÷ 32768 mengembalikan tepat 0.5. Formatnya tidak punya medan eksponen dan tidak punya hidden bit. Titik binernya hanyalah kesepakatan antara Anda dan siapa pun yang nanti membaca word tersebut; perangkat kerasnya sendiri tidak pernah melihat apa pun selain sebuah int16.

Rumus sependek itu langsung melahirkan dua konsekuensi, dan keduanya rutin menghabiskan setengah hari kerja orang.

0.1 × 32768 = 3276.8, yang bukan bilangan bulat. Q15 menyimpan 3277, dan nilai yang Anda baca kembali adalah 0.100006103515625, bukan 0.1.

1.0 × 32768 = 32768, satu angka lewat dari bilangan bulat bertanda 16-bit terbesar. Jadi 1.0 sama sekali tidak punya encoding Q15, dan reaksi kode Anda terhadap situasi itu bergantung pada kebijakan yang hampir tak pernah ditulis siapa pun di dalam codebase. Kalau ia melakukan saturasi, hasilnya 0.999969482421875. Kalau ia membungkus (wrap), hasilnya -1.0.

Sisa tulisan ini menangani dua kegagalan tadi: membaca notasi Q ketika dua datasheet saling bertentangan, mengonversi manual di kedua arah, dan mengenali aturan pembulatan serta overflow mana yang diam-diam dipilih toolchain Anda.

Q15, Q1.15, Qm.n: apakah tata letaknya sama?

Tanyakan arti Q15 kepada tiga referensi dan Anda bisa memperoleh tiga jawaban. Ini bukan salah Anda membacanya. Notasi ini memang tidak pernah distandarkan, dan seluruh perselisihannya berkisar pada satu bit.

Cara membaca Qm.n

Bentuk dua angka adalah bentuk yang jujur. Pada Qm.n, n adalah jumlah bit pecahan dan m adalah jumlah bit bulat. Artikel ini menghitung posisi tanda di dalam m, dan pembacaan itulah yang membuat Q16.16 menjadi word 32-bit: 16 bit bulat termasuk tandanya, 16 bit pecahan, skala 2^16 = 65536. Encode 1.5 ke Q16.16 dan hasilnya 1.5 × 65536 = 98304, heksadesimal 0x00018000, tanpa galat pembulatan sedikit pun.

Kerepotan mulai muncul di bit tanda. Sebagian penulis menghitungnya di dalam m, sebagian lagi menambahkannya di atas itu. Menurut konvensi pertama, Q1.15 adalah word 16-bit: satu posisi tanda/bulat plus 15 bit pecahan. Menurut konvensi kedua, label yang sama menggambarkan 17 bit, dan tidak ada mesin yang punya lebar seperti itu.

Mengapa label Q15 yang sama berarti lebar berbeda di dokumen berbeda

Bentuk satu angka Q15 membuang m sepenuhnya dan membiarkan lebarnya tersirat. Dalam praktik DSP, artinya hampir selalu word 16-bit bertanda dalam komplemen dua dengan 15 bit pecahan, dan makna itulah yang disepakati ekosistem TI dan ARM sejak puluhan tahun lalu.

Tapi Anda akan menemukan penjelasan yang tersalin ke mana-mana dan berbunyi kurang lebih begini: Q15 berarti 15 bit pecahan, jadi kalau kita mendefinisikan bilangan 32-bit, ada 1 bit tanda dan 16 bit bulat. Baca kalimat itu pelan-pelan dan Anda akan melihat ia menghitung bit tanda di atas m, bukan di dalamnya — konvensi yang berlawanan dengan yang artikel ini pakai. Tata letak yang ia gambarkan memang nyata, dan orang biasanya menuliskannya Q16.15. Yang keliru adalah labelnya: word 32-bit dengan 16 bit bulat bukanlah Q15 menurut konvensi mana pun. Cukup banyak blog menyalin ulang paragraf itu sampai sekarang ia mengungguli definisi yang benar di sebagian kueri.

Perlakukan Q15 telanjang di dokumen yang belum Anda kenal sebagai hipotesis, bukan fakta. Ujilah terhadap sesuatu yang bisa Anda ukur: lebar register di memory map, tipe C di header driver, atau satu nilai sampel yang sudah diketahui.

Satu-satunya deskripsi yang bertahan saat bertemu kode orang lain

Tulis tiga hal ini dan ambiguitasnya lenyap:

  • Signedness, alias ada tidaknya tanda: bertanda dengan komplemen dua, atau tak bertanda
  • W, total bit dalam satu word
  • F, jumlah bit pecahan

signed, W=16, F=15 mustahil disalahbaca. Begitu pula unsigned, W=32, F=16. Sisanya bisa Anda turunkan sendiri: skalanya 2^F, resolusinya 2^-F, dan rentangnya adalah rentang bilangan bulat word tersebut dibagi 2^F. Cantumkan ketiga nilai itu di dokumen protokol dan di komentar struct, dan perdebatan ini tidak akan terulang.

Konverter format Q online mencetak signedness, W, F, dan skala di samping setiap hasil dengan alasan yang sama persis. Ketika datasheet menyebut “Q15” sementara parser rekan kerja Anda berkata lain, mendekode satu word yang sudah diketahui menyelesaikan perdebatan itu dalam sekitar sepuluh detik.

Rumus konversi, dikerjakan manual

Kedua arahnya cukup pendek untuk dikerjakan di atas kertas, dan itu penting ketika Anda sedang menatap hex dump di layar osiloskop.

Float ke fixed: round(x × 2^F)

Bawa 0.5 ke Q15.

  1. Skalakan: 0.5 × 32768 = 16384
  2. Bulatkan: sudah bilangan bulat, jadi 16384 tetap
  3. Periksa rentang: 16-bit bertanda menampung -32768 sampai 32767, dan 16384 muat
  4. Simpan: 16384, heksadesimal 0x4000, biner 0100000000000000

Hanya langkah 2 yang bisa membuang informasi, dan hanya langkah 3 yang bisa gagal. Dua tempat itu pula yang menampung hampir semua kejutan dalam fixed point.

Fixed ke float: raw ÷ 2^F

Sekarang arah sebaliknya, berangkat dari tangkapan register yang terbaca 0xC000 pada sebuah medan Q15 bertanda.

  1. Baca teksnya sebagai kode 16-bit tak bertanda: 0xC000 = 49152
  2. Formatnya bertanda dan bit teratasnya menyala, jadi kurangi 2^16: 49152 - 65536 = -16384
  3. Skalakan turun: -16384 ÷ 32768 = -0.5

Langkah 2 inilah yang paling sering orang lewati. Tanpa koreksi komplemen dua, 0xC000 terbaca sebagai +1.5, yang bahkan tidak masuk rentang Q15, dan keganjilan itu sendiri sudah jadi petunjuk cepat. Kalau nilai hasil dekode jatuh di luar rentang formatnya, hampir pasti Anda melupakan langkah tanda.

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

Pemakaian Fraction di sini ada alasannya. Menskalakan lewat float lebih dulu justru memasukkan kembali pembulatan biner tepat pada saat Anda berusaha mengukurnya, sedangkan Fraction(str(x)) membaca literal desimal yang Anda ketik, bukan double terdekat darinya.

Membaca dan menulis word heksadesimal

Heksadesimal adalah wujud nyata nilai-nilai ini di tampilan register, dan konversinya mekanis: 3277 dalam heksadesimal adalah 0xCCD, lalu Anda genapkan ke W/4 digit sehingga menjadi 0x0CCD. Jangan pernah lewatkan penggenapan itu. Satu word Q15 berisi empat digit heks dan satu word Q31 berisi delapan; membuang nol di depan adalah cara sebuah nilai berakhir salah sejajar di dekoder batch.

Simpan kode mentah dalam heksadesimal tanpa tanda juga. 0x8000 adalah word Q15 paling negatif, bukan +32768, dan menuliskannya sebagai -0x8000 tidak menolong siapa pun. Urutan byte adalah pertanyaan terpisah, sebab format Q mengatur penskalaan numerik dan tidak berkata apa pun soal endianness: dump little-endian dari 0x0CCD tiba sebagai byte CD 0C. Untuk sekadar ganti basis saat membaca dump, konverter basis bilangan menangani biner, oktal, dan heksadesimal tanpa skala maupun lebar bertanda yang menempel.

Q7, Q15, Q31, dan Q16.16: rentang dan resolusi

Setiap angka di tabel ini adalah -2^(W-1) dibagi 2^F di satu ujung dan (2^(W-1) - 1) dibagi 2^F di ujung lain. Nilainya eksak, bukan hasil pembulatan demi tampilan.

FormatLebarBit pecahan FSkala 2^FMinimumMaksimum (eksak)Resolusi
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

Tempelkan salah satu nilai batas itu ke konverter format Q dan ia akan mengembalikan bilangan bulat tersimpan, word heksadesimalnya, dan desimal eksaknya. Itu cara tercepat memastikan sebuah konstanta firmware sebelum Anda mengirimkannya.

Mengapa batas atas rentang format Q15 adalah 0.999969482421875

Rentang format Q15 tidak simetris, dan asimetrinya berasal dari komplemen dua, bukan dari sesuatu yang khas fixed point. Word 16-bit bertanda mencakup bilangan bulat -32768 sampai 32767. Bagi kedua ujungnya dengan 32768 dan rentangnya menjadi -32768/32768 sampai 32767/32768, yaitu -1 sampai 0.999969482421875.

Jadi 1.0 meleset tepat satu LSB. Ini bukan “maksimumnya kira-kira 1.0” atau “1.0 dengan pembulatan” — nilai itu memang tidak punya encoding, dan konverter yang melaporkan 1.0 untuk Q15 tanpa menyebut bahwa ia melakukan saturasi sedang membohongi Anda.

Mengapa -1 termasuk di dalamnya

Banyak referensi menulis rentang Q15 sebagai -1 < X < 0.9999695, dengan kurung terbuka di kedua sisi. Batas bawahnya salah. -32768 ÷ 32768 = -1 persis, jadi -1 punya encoding yang sah, dan kedua ujung [-1, 0.999969482421875] sama-sama nilai yang bisa Anda capai. Anda juga akan menemukan rentang itu tertulis [-1, 1), dan itu pernyataan yang sama, hanya memakai skala penuh sebagai patokannya: 1.0 sendiri memang di luar jangkauan, tapi nilai terbesar yang masih terjangkau adalah 0.999969482421875, bukan sesuatu yang kurang dari itu.

Tanda kurung yang keliru itu punya akibat nyata. -1 justru nilai yang merusak perkalian, sebab -1 × -1 = 1 sementara 1 berada di luar rentang. Siapa pun yang menganggap -1 tak terjangkau tidak akan menulis cabang saturasi yang menangkapnya.

Batas atas terpotong 0.9999695 di referensi yang sama adalah artefak tampilan. Tidak ada yang berulang pada nilai itu: ia 32767/32768, dan 32768 adalah pangkat dua, jadi ekspansi desimalnya berhenti setelah 15 digit di 0.999969482421875.

0.1 tidak muat di Q15

Skalakan dan masalahnya langsung kelihatan: 0.1 × 32768 = 3276.8. Fixed point hanya bisa menyimpan bilangan bulat, jadi ada yang harus mengalah.

Dengan pembulatan ke terdekat, word yang tersimpan adalah 3277, heksadesimal 0x0CCD. Nilai yang diwakili word itu:

3277 ÷ 32768 = 0.100006103515625

Galat kuantisasi (quantization error) adalah selisih antara yang Anda minta dan yang sanggup diberikan grid:

0.100006103515625 - 0.1 = +0.000006103515625

Kira-kira enam per sejuta, atau seperlima LSB. Di pengatur volume angka sebesar itu tidak berarti apa-apa. Di integrator yang menambahkannya kembali seribu kali per detik, ia berubah menjadi hanyutan tetap sekitar 0.006 per detik pada nilai akumulasinya.

Galat fixed point seragam; galat floating point tidak

Q15 menghamparkan grid berisi 65536 titik yang berjarak tepat 1/32768 di sepanjang [-1, 0.999969482421875]. Jarak di dekat 0.9 sama dengan jarak di dekat 0.0001, sehingga galat absolut kasus terburuk selalu setengah LSB di mana pun. Analisis galatnya jadi membosankan, dan itu justru keuntungan: Anda bisa membatasi noise floor sebuah filter dengan aritmetika yang sanggup diperiksa insinyur junior.

IEEE 754 melakukan kebalikannya. Ia mempertahankan jumlah bit signifikan yang tetap lalu menggeser eksponennya, jadi jarak absolut antara dua double bertetangga membesar seiring besaran, sementara galat relatifnya nyaris konstan. Di dekat 1.0 jarak itu sekitar 2.2e-16; di dekat 1e12 sekitar 0.0001220703125.

Kegagalan yang sama, bentuknya saja berbeda. Sebuah double pun tidak sanggup menampung 0.1. Ia menyimpan 0.1000000000000000055511151231257827021181583404541015625, dan itulah sebabnya 0.1 + 0.2 mengembalikan 0.30000000000000004. Artikel pendamping soal presisi floating point membedah kasus itu sampai tuntas. Fixed point tidak menyelesaikan persoalan pecahan desimal di dalam biner; ia hanya membuat ukuran galatnya bisa diprediksi.

Tiga mode pembulatan, terpaut satu LSB

Aturan pembulatan adalah bagian dari kontrak data, bukan detail implementasi. Dua implementasi yang sama-sama benar tapi berbeda soal pembulatan akan menghasilkan vektor uji yang selamanya berbeda di bit terakhir, dan melacak hal semacam itu adalah pekerjaan yang menyiksa.

ModeAturan10911.744-3276.8
Bulatkan ke terdekat, seri ke genapTitik grid terdekat; setengah yang eksak jatuh ke bilangan bulat genap10912-3277
Potong ke arah nolBuang pecahannya, besarannya hanya bisa menyusut10911-3276
Floor ke arah tak hingga negatifSelalu turun menyusuri garis bilangan10911-3277

Kedua kolom itu menunjukkan mengapa satu contoh tidak pernah cukup. Untuk nilai positif, pemotongan dan floor sepakat. Untuk nilai negatif keduanya terbelah sejauh satu LSB penuh, sebab pemotongan menarik -3276.8 naik ke arah nol sedangkan floor mendorongnya turun.

Contoh hitungan yang hampir selalu keliru

0.333 di Q15 adalah kasus uji yang bagus karena letaknya dekat sebuah batas. 0.333 × 32768 = 10911.744.

  • Potong: 10911, yang terbaca kembali sebagai 0.332977294921875
  • Bulatkan ke terdekat: 10912, yang terbaca kembali sebagai 0.3330078125

Tutorial lama mencetak 10911 lalu melanjutkan tanpa menyebut aturan mana yang menghasilkannya. Bawa angka itu ke codebase yang encoder-nya membulatkan, dan golden vector Anda gagal pada eksekusi pertama, dengan selisih satu hitungan yang lebih mirip salah ketik ketimbang ketidakcocokan kebijakan.

Nilai negatif justru tempat ketiga aturan itu berpisah. -0.1 × 32768 = -3276.8 menghasilkan -3276 di bawah pemotongan (nilainya -0.0999755859375) dan -3277 di bawah floor maupun nearest (nilainya -0.100006103515625). Pemotongan dan nearest sama-sama memperlakukan kedua tanda secara setara — masing-masing ±3276 dan ±3277 — jadi sepasang koefisien simetris tetap simetris. Floor tidak begitu: ia membawa +0.1 ke 3276 tetapi -0.1 ke -3277, dan pasangan itu keluar timpang sejauh satu hitungan.

Apa yang bahasa Anda lakukan secara default

Tidak ada default yang salah di antara semuanya. Semuanya sekadar berbeda, dan tak satu pun mengumumkan dirinya.

  • C/C++: cast dari floating point ke tipe integer memotong ke arah nol. (int16_t)(0.333f * 32768) memberi 10911.
  • Python: round() bawaan memakai ties-to-even, jadi round(3276.8) adalah 3277 dan round(2.5) adalah 2.
  • JavaScript: Math.round memutus seri ke arah tak hingga positif, dan itu tidak simetris. Math.round(2.5) adalah 3, tetapi Math.round(-2.5) adalah -2.
  • Perangkat keras: banyak jalur multiply-accumulate DSP membulatkan saat penggeseran dan menawarkan nearest-even sebagai bit mode, dan itulah sebabnya model C acuan bisa berselisih dengan silikonnya sampai ada orang yang membaca register mode.

Pilih satu aturan, sebutkan namanya di spesifikasi format tepat di sebelah W dan F, lalu pastikan vektor ujinya ikut membawa aturan itu.

Overflow: saturasi vs wraparound

Overflow Q15 melakukan salah satu dari tiga hal: menolak nilainya, menjepitnya ke 0.999969482421875, atau membungkusnya menjadi -1.0. Mana yang Anda dapatkan adalah kebijakan yang dipilih toolchain, dan opsi ketiga membalik tanda tanpa bersuara.

Ambil 1.0. Penskalaan memberi 1.0 × 32768 = 32768, sedangkan bilangan bulat bertanda 16-bit terbesar adalah 32767. Nilainya keluar rentang tepat sebanyak satu.

KebijakanWord tersimpanNilai saat dibacaWujudnya di hilir
Errortidak adakonversi ditolakBerisik, bisa ditangkap, biasanya tepat untuk perkakas
Saturasi0x7FFF = 327670.999969482421875Tak terbedakan dari 1.0, baik oleh telinga maupun mata
Wrap0x8000 = -32768-1.0Pembalikan tanda pada skala penuh

Baris saturasi kehilangan 0.000030517578125 dan tak seorang pun menyadarinya. Baris wrap mengubah sampel positif skala penuh menjadi sampel negatif skala penuh, dan di jalur audio itu berarti bunyi klik yang terdengar sampai ujung ruangan. Di sebuah control loop, itu berarti perintah skala penuh ke arah yang salah.

Sifat berbahaya dari wraparound adalah ia menghasilkan word yang tampak sah sempurna. 0x8000 adalah encoding Q15 yang legal untuk -1.0. Tidak ada apa pun di hilir yang bisa membedakannya dari sampel -1.0 asli, jadi tidak ada pola khas yang bisa Anda grep setelah kejadian; Anda hanya menemukannya dengan memasang instrumen di titik tempat overflow itu terjadi.

Mengapa silikon DSP mengirim instruksi saturasi

Saturasi adalah perilaku yang diinginkan pemrosesan sinyal, jadi prosesor mengimplementasikannya sendiri alih-alih menyerahkannya ke sebuah cabang. ARM punya QADD/QSUB serta instruksi saturating-shift SSAT/USAT, NEON punya VQADD dan kawan-kawannya, dan x86 SSE punya packed saturating add semacam paddsw. Keluarga C6000 dan C55x dari TI mengekspos saturasi sebagai bit mode pada jalur akumulator.

Di sebuah filter atau mixer, satu sampel yang sesekali terpotong hanyalah distorsi lokal yang kecil. Sampel yang ter-wrap adalah diskontinuitas dengan energi di seluruh spektrum. Perangkat kerasnya memilih agak salah ketimbang salah total sebagai default, tapi itu berlaku hanya kalau Anda mengaktifkannya; aritmetika integer C biasa di chip yang sama tetap membungkus.

Mengapa perkalian Q15 butuh geser kanan 15 bit

Kalikan dua word Q15 sebagai bilangan bulat dan hasilnya benar, tapi ia bukan Q15 lagi. Bit pecahan bertambah saat perkalian: Q15 × Q15 menghasilkan Q30.

Kerjakan 0.5 × 0.5, yang jawaban benarnya jelas 0.25:

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

Tafsirkan 268435456 sebagai Q15 dan Anda akan membacanya sebagai 8192.0, meleset sejauh faktor 32768. Faktor itulah seluruh bug-nya, dan itu menjelaskan mengapa filter fixed-point yang “hampir jalan” sering meleset sebesar pangkat dua.

Hasil kalinya juga butuh ruang. Perkalian dua nilai 16-bit menghasilkan sampai 32 bit, jadi nilai antaranya harus int32_t. Mengakumulasi banyak hasil kali menuntut ruang yang lebih lega lagi, dan itulah sebabnya akumulator DSP berukuran 40 bit di komponen seperti C55x.

Bulatkan penggeserannya, jangan sekadar membuang bitnya

>> 15 telanjang membuang 15 bit terbawah, dan bagi nilai bertanda itu berarti pemotongan ke arah tak hingga negatif. Menambahkan setengah LSB dari presisi keluaran lebih dulu akan mengubahnya menjadi pembulatan ke terdekat:

#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;
}

Kompilasi dengan cc -std=c11 -Wall -o q15 q15.c && ./q15, dan program ini mencetak:

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

Baris ketiga adalah kasus -1 yang tadi. -32768 × -32768 = 1073741824, yang bernilai 1.0 di Q30 dan keluar rentang untuk Q15, jadi sat_q15 menjepitnya ke 32767. Buang penjepit itu dan cast ke int16_t akan membungkusnya ke -32768, mengubah -1 × -1 menjadi -1.

Dua baris terakhir menunjukkan selisih pembulatan. 3277 × 3277 = 10738729, dan geser telanjang memberi 327 (0.009979248046875) sementara geser yang dibulatkan memberi 328 (0.010009765625). Hasil kali sebenarnya adalah 0.01, jadi versi yang dibulatkan mendarat lebih dari dua kali lebih dekat. Harganya satu operasi penjumlahan ekstra.

Satu catatan soal penggeserannya sendiri: menggeser kanan bilangan bulat bertanda yang bernilai negatif bersifat implementation-defined di C sebelum C23, walau setiap kompiler yang akan Anda temui melakukan geser aritmetika. Kalau itu membuat Anda kurang tenang, bagi saja dengan 32768 dan biarkan kompiler yang memancarkan penggeserannya, atau lakukan penggeseran pada tipe tak bertanda setelah memberi bias. Panduan operasi bitwise membahas mekanika geser dan mask secara lebih luas.

Penjumlahan menuntut nilai Q yang cocok lebih dulu

Perkalian mengubah nilai Q secara terduga. Penjumlahan sama sekali tidak menoleransi ketidakcocokan: menambahkan word Q7 ke word Q15 menghasilkan omong kosong, sebab kedua operan tidak berbagi skala.

Sejajarkan dulu dengan penggeseran. 0.5 di Q7 adalah 64, dan 64 << 8 adalah 16384, yang berarti 0.5 di Q15. Besar pergeserannya adalah selisih bit pecahan, 15 - 7 = 8.

Menggeser naik itu eksak tapi memakan headroom, karena nilai Q7 yang dipromosikan ke Q15 menuntut wadah yang lebih lebar. Menggeser turun bersifat lossy dan menuntut keputusan pembulatan yang sama seperti pada perkalian. Bagaimanapun caranya, tulis nilai Q setiap hasil antara di komentar. Kode fixed-point yang skalanya hanya hidup di kepala penulisnya akan mustahil dirawat dalam sebulan.

Fixed point Q15 atau floating point IEEE 754: cara memilih

Keduanya sama-sama sistem nilai tempat berbasis biner, jadi pada prinsipnya tak ada yang unggul soal akurasi. Pilihannya bergantung pada ongkos yang ditagih perangkat keras target dan jaminan apa yang Anda butuhkan.

PertanyaanCondong ke fixed pointCondong ke floating point
Adakah FPU perangkat keras?Tanpa FPU, atau sekadar pustaka soft-floatFPU perangkat keras dengan operasi satu siklus
Seberapa lebar rentang dinamisnya?Diketahui dan terbatas, seperti audio ternormalisasiMembentang banyak orde besaran
Apakah format kabelnya menetapkan skala?Protokol atau register mengunci skala binernyaMedannya memang float sungguhan
Perlukah hasilnya bit-exact di semua build?Ya, bilangan bulat bereproduksi di mana punBoleh bervariasi karena FMA dan optimasi
Apakah memori atau bandwidth ketat?Sampel 16-bit memangkas separuh jejak float 32-bitBukan kendala
Siapa yang merawat kodenya?Tim sudah fasih notasi QTim campuran, bug penskalaan jadi risiko lebih besar

Baris terakhir bukan lelucon. Fixed point memindahkan galat dari runtime ke fase desain, dan itu pertukaran yang bagus hanya kalau memang ada orang yang mengerjakan desainnya. Di Cortex-M4F dengan FPU perangkat keras, float presisi tunggal kerap jadi pilihan yang lebih cepat sekaligus lebih aman, dan refleks lama untuk langsung meraih Q15 adalah kebiasaan warisan dari komponen yang sudah tidak lagi mendominasi.

Di mana IEEE 754 mengambil alih

Pakai floating point ketika word-nya sendiri membawa tanda, eksponen, dan signifikan alih-alih skala tetap. Di titik itulah format Q berhenti berlaku: tak ada satu 2^F untuk dijadikan pembagi, sebab eksponennya berubah per nilai.

Kedua representasi ini terus bertemu dalam praktik. Data sensor tiba sebagai word register Q15, naik pangkat ke float untuk komputasi panjang, lalu pulang sebagai Q15 untuk DAC. Untuk memeriksa paruh floating-point dari jalur itu bit demi bit, konverter IEEE 754 memecah sebuah nilai menjadi tanda, eksponen, dan mantissa lalu mencetak desimal tersimpannya yang eksak — pekerjaan yang sama seperti yang dilakukan konverter format Q untuk skala tetap.

Keduanya berdiri di fondasi yang sama

Format Q, IEEE 754, dan bilangan bulat biasa sama-sama membaca bit yang sama dengan aturan berbeda soal di mana titiknya berada dan boleh tidaknya titik itu bergerak. Kalau bagian nilai tempatnya masih terasa goyah, atau Anda ingin lebih gesit membaca 0x0CCD sebagai 0000 1100 1100 1101 tanpa meraih kalkulator, pengantar tentang konversi biner, heks, dan oktal menutup dasar yang dipakai kedua format tadi.

FAQ fixed point Q15

Apa arti Q15?

Dalam konvensi DSP yang umum, Q15 adalah word 16-bit bertanda dengan komplemen dua dan 15 bit pecahan: satu posisi tanda dan 15 posisi pecahan. Skalanya 2^15 = 32768, resolusinya 1/32768 = 0.000030517578125, dan rentangnya -1 sampai 32767/32768. Karena label Q berbeda-beda antardokumen, pastikan dulu lebar total dan signedness-nya alih-alih memercayai labelnya begitu saja.

Apakah Q15 dan Q1.15 sama?

Keduanya biasanya menggambarkan tata letak 16-bit bertanda yang sama, dengan angka 1 pada Q1.15 menghitung posisi tandanya. Tapi notasinya tidak universal, dan sebagian penulis justru menambahkan bit tanda di atas m alih-alih menghitungnya di dalam. Deskripsi yang bisa diandalkan adalah signedness plus total bit W plus bit pecahan F: untuk tata letak ini, signed, W=16, F=15.

Berapa 0.5 dalam Q15?

Dalam Q15, 0.5 tersimpan sebagai bilangan bulat 16384, heksadesimal 0x4000. Aritmetikanya 0.5 × 32768 = 16384, yang sudah berupa bilangan bulat, jadi tak ada pembulatan dan tak ada galat kuantisasi. Dekode baliknya memastikan hal itu: 16384 ÷ 32768 = 0.5 persis.

Berapa nilai maksimum dan minimum Q15?

Minimumnya -1, dan nilai itu termasuk, sebab -32768 ÷ 32768 persis -1. Maksimumnya 32767/32768 = 0.999969482421875. Keduanya sama-sama bisa Anda capai, jadi rentangnya [-1, 0.999969482421875]; 1.0 justru nilai yang jatuh di luarnya. Referensi yang menulis batas bawahnya sebagai interval terbuka itu keliru, dan kekeliruan tersebut menyembunyikan kasus overflow -1 × -1.

Apa yang terjadi saat Q15 overflow?

Tergantung kebijakan yang berlaku. Kebijakan error menolak konversinya. Saturasi menjepit ke ujung terdekat, jadi 1.0 menjadi 0.999969482421875, kehilangan satu LSB yang biasanya tak terdengar. Wraparound menerapkan modulo 2^16, jadi 1.0 terskala menjadi 32768, yang terbaca kembali sebagai -32768 dan karenanya -1.0, yaitu pembalikan tanda sepenuhnya. Perangkat keras DSP memakai saturasi sebagai default persis karena alasan ini, sedangkan aritmetika integer C biasa membungkus.

Mengapa dua bilangan Q15 perlu digeser kanan 15 setelah dikalikan?

Karena bit pecahan bertambah. Q15 × Q15 menghasilkan hasil kali Q30, jadi hasil bilangan bulatnya membawa 30 bit pecahan alih-alih 15. Menggeser kanan sebanyak 15 menyejajarkannya kembali ke Q15: 16384 × 16384 = 268435456, dan 268435456 >> 15 = 8192, yang terdekode menjadi 0.25. Tambahkan 1 << 14 sebelum penggeseran untuk membulatkan ke terdekat alih-alih memotong, dan simpan nilai antaranya dalam int32_t supaya hasil kali 32-bit itu tidak overflow.

Kapan sebaiknya memakai format Q ketimbang IEEE 754?

Pakai format Q ketika sebuah protokol, peta register, algoritma DSP, atau codec sudah mengunci skala biner untuk word bilangan bulat: skalanya bagian dari antarmuka dan Anda tidak berhak memilihnya. Pakai IEEE 754 ketika nilainya menuntut rentang dinamis yang lebar, ketika targetnya punya FPU perangkat keras, atau ketika medannya memang menyimpan tanda, eksponen, dan signifikan. Untuk sekadar ganti basis tanpa skala yang menempel, keduanya tidak berlaku; itu konversi basis biasa.

Versi ringkasnya

Fixed point Q15 adalah satu perkalian, satu keputusan pembulatan, dan satu pemeriksaan rentang. raw = round(value × 32768) saat masuk, value = raw ÷ 32768 saat keluar. Rumusnya sepele; semua kegagalannya bersarang di bagian yang tak seorang pun mendokumentasikannya.

Jadi dokumentasikanlah. Tulis signedness, W, dan F di sebelah setiap label Q alih-alih menganggap labelnya sudah cukup. Sebutkan mode pembulatannya di tempat yang sama, karena pemotongan dan floor terbelah sejauh satu LSB penuh pada nilai negatif. Nyatakan secara eksplisit apakah overflow memicu error, melakukan saturasi, atau membungkus, sebab kasus wrap mengubah 1.0 menjadi -1.0 tanpa meninggalkan bukti apa pun.

Ketika sebuah word register dan lembar kerja tidak sepakat, dekode satu nilai yang sudah diketahui di konverter format Q. Alat itu menampilkan bilangan bulat tersimpan, desimal eksak, galat kuantisasi, dan rentang yang bisa direpresentasikan secara berdampingan, semuanya dihitung di dalam browser. Biasanya itu sudah cukup untuk mengetahui asumsi siapa soal W dan F yang ternyata keliru.

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

Artikel Terkait

Lihat semua artikel