Skip to content
Zurück zum Blog
Tutorials

Q15-Festkommazahlen: Umrechnung, Rundung und Überlauf

0,1 wird in Q15 zu 3277, und 1,0 kippt ohne Sättigung auf -1,0. Q-Notation lesen, von Hand umrechnen und online kostenlos überprüfen.

15 Min. Lesezeit

Q15-Festkommazahlen: Umrechnung, Rundung und Überlauf

Q15-Festkomma speichert einen Bruch als gewöhnliche vorzeichenbehaftete 16-Bit-Ganzzahl mit einem impliziten Binärmaßstab von 2^15 = 32768. Das Kodieren besteht aus einer Multiplikation und einem Rundungsschritt:

raw = round(value × 32768)

Das Dekodieren ist eine Division:

value = raw ÷ 32768

Mehr Arithmetik steckt nicht dahinter. 0.5 × 32768 = 16384, also liegt 0.5 im Speicher als die Ganzzahl 16384, und 16384 ÷ 32768 liefert exakt 0.5. Es gibt kein Exponentenfeld und kein verstecktes Bit. Der Binärpunkt ist eine Vereinbarung zwischen Ihnen und demjenigen, der das Wort ausliest; die Hardware sieht immer nur ein int16.

Aus dieser Formel folgen sofort zwei Dinge, an denen man leicht einen halben Arbeitstag verliert.

0.1 × 32768 = 3276.8, und das ist keine ganze Zahl. Q15 behält 3277, und der Wert, den Sie zurücklesen, ist 0.100006103515625, nicht 0.1.

1.0 × 32768 = 32768, und das ist eins mehr als die größte vorzeichenbehaftete 16-Bit-Ganzzahl. 1.0 hat in Q15 also überhaupt keine Kodierung, und was Ihr Code daraus macht, hängt von einer Richtlinie ab, die in den meisten Codebasen nirgends festgehalten ist. Mit Sättigung bekommen Sie 0.999969482421875, mit Überlauf -1.0.

Beides ist beherrschbar, sobald Sie die Q-Notation auch bei widersprüchlichen Datenblättern lesen können, in beide Richtungen von Hand umrechnen und wissen, welche Rundungs- und Überlaufregel Ihre Toolchain stillschweigend gewählt hat.

Q15, Q1.15, Qm.n: dasselbe Layout?

Fragen Sie drei Quellen, was Q15 bedeutet, und Sie bekommen womöglich drei Antworten. Sie lesen die Quellen dabei nicht falsch: Standardisiert hat diese Notation nie jemand, und der Streit geht um ein einziges Bit.

Qm.n richtig lesen

Die Form mit zwei Zahlen ist die ehrliche. In Qm.n ist n die Anzahl der Nachkommabits und m die Anzahl der Vorkommabits. Dieser Artikel zählt die Vorzeichenstelle zu m; das ist die Lesart, nach der Q16.16 ein 32 Bit breites Wort ergibt: 16 Vorkommabits einschließlich Vorzeichen, 16 Nachkommabits, Maßstab 2^16 = 65536. Kodieren Sie 1.5 in Q16.16, ergibt das 1.5 × 65536 = 98304, hexadezimal 0x00018000, und zwar völlig ohne Rundungsfehler.

Ärger macht das Vorzeichenbit. Manche Autoren zählen es zu m, andere schlagen es obendrauf. Q1.15 ist nach der ersten Konvention ein 16-Bit-Wort: eine Vorzeichen- bzw. Vorkommastelle plus 15 Nachkommabits. Nach der zweiten Konvention beschreibt dasselbe Etikett 17 Bit, und die hat keine Maschine.

Warum dasselbe Etikett Q15 in verschiedenen Dokumenten unterschiedliche Breiten meint

Die einzahlige Form Q15 lässt m ganz weg und hält die Breite implizit. In der DSP-Praxis meint sie fast immer ein vorzeichenbehaftetes 16-Bit-Zweierkomplementwort mit 15 Nachkommabits, und genau darauf haben sich das TI- und das ARM-Ökosystem vor Jahrzehnten geeinigt.

Sie finden aber auch vielfach abgeschriebene Erklärungen in der Art: Q15 bedeutet 15 Nachkommabits; definieren wir also eine 32-Bit-Zahl, dann gibt es 1 Vorzeichenbit und 16 Vorkommabits. Lesen Sie diesen Satz genau, dann schlägt er das Vorzeichenbit auf m obendrauf, statt es darin mitzuzählen — also die andere Konvention als die hier verwendete. Die Anordnung, die er beschreibt, ist real und wird üblicherweise Q16.15 geschrieben. Falsch ist das Etikett darauf: Ein 32-Bit-Wort mit 16 Vorkommabits ist unter keiner der beiden Konventionen ein Q15. Diesen Absatz haben so viele Blogs voneinander abgeschrieben, dass er bei manchen Suchanfragen inzwischen über der korrekten Definition steht.

Behandeln Sie ein nacktes Q15 in einem unbekannten Dokument als Hypothese, nicht als Tatsache. Prüfen Sie es gegen etwas Messbares: die Registerbreite in der Memory Map, den C-Typ im Treiber-Header oder einen bekannten Beispielwert.

Die einzige Beschreibung, die den Kontakt mit fremdem Code überlebt

Drei Angaben genügen, damit die Mehrdeutigkeit verschwindet:

  • Vorzeichen — vorzeichenbehaftet im Zweierkomplement oder vorzeichenlos
  • W — Gesamtzahl der Bits im Wort
  • F — Nachkommabits

signed, W=16, F=15 lässt sich nicht falsch lesen. unsigned, W=32, F=16 ebenso wenig. Alles andere leitet sich daraus ab: Der Maßstab ist 2^F, die Auflösung ist 2^-F, und der Bereich ist der Ganzzahlbereich des Wortes geteilt durch 2^F. Schreiben Sie diese drei Werte in das Protokolldokument und in den Kommentar über der Struktur, dann führen Sie diese Diskussion nie wieder.

Der Online-Q-Format-Konverter gibt aus demselben Grund Vorzeichen, W, F und den Maßstab neben jedem Ergebnis aus. Wenn ein Datenblatt „Q15“ sagt und der Parser eines Kollegen etwas anderes, klärt das Dekodieren eines einzigen bekannten Wortes die Sache in ungefähr zehn Sekunden.

Die Umrechnungsformel, von Hand durchgerechnet

Beide Richtungen sind kurz genug für Papier, und das zählt, wenn Sie auf einen Hex-Dump auf dem Oszilloskop starren.

Float zu Festkomma: round(x × 2^F)

Nehmen Sie 0.5 nach Q15.

  1. Skalieren: 0.5 × 32768 = 16384
  2. Runden: bereits ganzzahlig, also bleibt 16384 stehen
  3. Bereichsprüfung: vorzeichenbehaftete 16 Bit fassen -32768 bis 32767, und 16384 passt
  4. Speichern: 16384, hexadezimal 0x4000, binär 0100000000000000

Nur Schritt 2 kann Information verlieren, und nur Schritt 3 kann scheitern. Alles Interessante am Festkomma passiert an einer dieser beiden Stellen.

Festkomma zu Float: raw ÷ 2^F

Jetzt die Gegenrichtung, ausgehend von einem Registerabzug, der 0xC000 in einem vorzeichenbehafteten Q15-Feld zeigt.

  1. Den Text als vorzeichenlosen 16-Bit-Code lesen: 0xC000 = 49152
  2. Das Format ist vorzeichenbehaftet und das oberste Bit ist gesetzt, also 2^16 abziehen: 49152 - 65536 = -16384
  3. Herunterskalieren: -16384 ÷ 32768 = -0.5

Schritt 2 ist der, den man überspringt. Ohne die Zweierkomplement-Korrektur liest sich 0xC000 als +1.5, was für Q15 nicht einmal im Bereich liegt, und daraus wird ein brauchbarer Plausibilitätstest. Wenn ein dekodierter Wert außerhalb des Formatbereichs landet, haben Sie mit ziemlicher Sicherheit den Vorzeichenschritt vergessen.

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 leistet hier echte Arbeit. Würde man zuerst über ein float skalieren, käme die binäre Rundung genau in dem Moment zurück, in dem Sie sie messen wollen. Und Fraction(str(x)) liest das Dezimalliteral, das Sie getippt haben, statt des nächstgelegenen Double.

Das Hex-Wort lesen und schreiben

Hexadezimal ist die Form, in der diese Werte tatsächlich in Registeransichten auftauchen, und die Umrechnung ist mechanisch: 3277 lautet hexadezimal 0xCCD, auf W/4 Stellen aufgefüllt 0x0CCD. Füllen Sie immer auf. Ein Q15-Wort hat vier Hexstellen, ein Q31-Wort acht; die führende Null wegzulassen ist der Weg, auf dem ein Wert in einem Batch-Dekoder aus der Ausrichtung gerät.

Halten Sie Rohcodes in Hex außerdem vorzeichenlos. 0x8000 ist das negativste Q15-Wort, nicht +32768, und es als -0x8000 zu schreiben hilft niemandem. Die Bytereihenfolge ist eine eigene Frage, denn das Q-Format legt die numerische Skalierung fest und sagt nichts über Endianness. Ein Little-Endian-Dump von 0x0CCD kommt als Bytefolge CD 0C an. Für reine Basiswechsel beim Lesen eines Dumps behandelt der Zahlensystem-Konverter Binär, Oktal und Hex ohne angehängten Maßstab und ohne vorzeichenbehaftete Breite.

Q7, Q15, Q31 und Q16.16: Bereich und Auflösung

Jede Zahl in dieser Tabelle ist -2^(W-1) geteilt durch 2^F am einen Ende und (2^(W-1) - 1) geteilt durch 2^F am anderen. Die Werte sind exakt, nicht für die Anzeige gerundet.

FormatBreiteNachkommabits FMaßstab 2^FMinimumMaximum (exakt)Auflösung
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

Fügen Sie einen dieser Grenzwerte in den Q-Format-Konverter ein, und er gibt Ihnen die gespeicherte Ganzzahl, das Hex-Wort und die exakte Dezimaldarstellung zurück. Kürzer lässt sich eine Firmware-Konstante nicht prüfen, bevor sie in ein Release geht.

Warum die Obergrenze des Q15-Bereichs 0.999969482421875 ist

Der Q15-Wertebereich ist asymmetrisch, und die Asymmetrie kommt vom Zweierkomplement, nicht von irgendetwas Festkomma-Spezifischem. Ein vorzeichenbehaftetes 16-Bit-Wort deckt die Ganzzahlen -32768 bis 32767 ab. Durch 32768 geteilt, ergeben beide Enden den Bereich -32768/32768 bis 32767/32768, also -1 bis 0.999969482421875.

1.0 fehlt damit um genau ein LSB. Der Wert hat keine Kodierung, weder als „ungefähres Maximum“ noch „mit Rundung“, und ein Konverter, der für Q15 1.0 meldet, ohne die Sättigung dazuzusagen, belügt Sie.

Warum -1 dazugehört

Viele Quellen schreiben den Q15-Bereich als -1 < X < 0.9999695, mit offener Klammer auf beiden Seiten. Die untere Grenze ist falsch. -32768 ÷ 32768 = -1 exakt, -1 ist also darstellbar, und beide Enden von [-1, 0.999969482421875] sind erreichbare Werte. Sie finden den Bereich auch als [-1, 1) geschrieben; das ist dieselbe Aussage, nur auf die Vollaussteuerung bezogen: 1.0 selbst ist unerreichbar, der größte erreichbare Wert ist aber 0.999969482421875 und nicht irgendetwas knapp darunter.

Die Klammer hat praktische Folgen, denn -1 ist der Wert, an dem die Multiplikation zerbricht: -1 × -1 = 1, und 1 liegt außerhalb des Bereichs. Wer -1 für unerreichbar hält, schreibt den Sättigungszweig nicht, der ihn abfängt.

Die abgeschnittene Obergrenze 0.9999695 in denselben Quellen ist ein Anzeigeartefakt. An diesem Wert ist nichts periodisch: Er ist 32767/32768, und 32768 ist eine Zweierpotenz, also endet die Dezimalentwicklung nach 15 Stellen bei 0.999969482421875.

0.1 passt nicht in Q15

Die Skalierung zeigt das Problem sofort: 0.1 × 32768 = 3276.8. Festkomma kann nur Ganzzahlen speichern, also muss etwas nachgeben.

Beim Runden zur nächsten Zahl lautet das gespeicherte Wort 3277, hexadezimal 0x0CCD. Der Wert, den dieses Wort darstellt, ist:

3277 ÷ 32768 = 0.100006103515625

Der Quantisierungsfehler ist die Differenz zwischen dem, was Sie angefordert haben, und dem, was das Raster hergibt:

0.100006103515625 - 0.1 = +0.000006103515625

Das sind rund sechs Millionstel oder ein Fünftel eines LSB. In einer Lautstärkeregelung fällt das nicht auf. In einem Integrator, der den Wert tausendmal pro Sekunde wieder aufaddiert, schon: Dort wächst daraus eine stetige Drift von etwa 0.006 pro Sekunde im akkumulierten Wert.

Der Festkommafehler ist gleichmäßig, der Gleitkommafehler nicht

Q15 legt über [-1, 0.999969482421875] ein Raster aus 65536 Punkten mit exakt 1/32768 Abstand. Der Abstand nahe 0.9 ist derselbe wie der nahe 0.0001, der absolute Fehler im ungünstigsten Fall beträgt also überall ein halbes LSB. Das macht die Fehleranalyse langweilig, und zwar im besten Sinn: Sie können den Rauschteppich eines Filters mit einer Rechnung abschätzen, die auch eine Berufsanfängerin nachrechnet.

IEEE 754 macht das Gegenteil. Es hält die Anzahl der signifikanten Bits fest und verschiebt den Exponenten, sodass der absolute Abstand zwischen benachbarten Doubles mit der Größenordnung wächst, während der relative Fehler nahezu konstant bleibt. Nahe 1.0 beträgt dieser Abstand etwa 2.2e-16, nahe 1e12 etwa 0.0001220703125.

Es ist derselbe Fehler in anderer Form, denn auch ein Double kann 0.1 nicht halten. Es speichert 0.1000000000000000055511151231257827021181583404541015625, weshalb 0.1 + 0.2 das Ergebnis 0.30000000000000004 liefert. Der Begleitartikel zur Gleitkomma-Genauigkeit rechnet diesen Fall Schritt für Schritt durch. Festkomma löst das Problem dezimaler Brüche im Binärsystem nicht; es macht nur die Größe des Fehlers vorhersagbar.

Drei Rundungsmodi, ein LSB auseinander

Die Rundungsregel gehört in den Datenvertrag. Zwei korrekte Implementierungen, die sich bei der Rundung uneinig sind, erzeugen auf Dauer Testvektoren, die sich im letzten Bit unterscheiden, und das aufzuspüren ist elende Arbeit.

ModusRegel10911.744-3276.8
Runden zur nächsten Zahl, Gleichstand zur geradenNächster Rasterpunkt; exakte Hälften gehen zur geraden Ganzzahl10912-3277
Abschneiden Richtung nullNachkommateil verwerfen, der Betrag wird nur kleiner10911-3276
Abrunden Richtung minus unendlichImmer die Zahlengerade hinunter10911-3277

Die beiden Spalten zeigen, warum ein einzelnes Beispiel nie genügt. Bei einem positiven Wert stimmen Abschneiden und Abrunden überein. Bei einem negativen trennen sie sich um ein volles LSB, weil das Abschneiden -3276.8 in Richtung null zieht und das Abrunden es nach unten drückt.

Das durchgerechnete Beispiel, das alle falsch machen

0.333 in Q15 ist ein guter Testfall, weil es dicht an einer Grenze liegt. 0.333 × 32768 = 10911.744.

  • Abschneiden: 10911, was sich als 0.332977294921875 zurücklesen lässt
  • Runden zur nächsten Zahl: 10912, was sich als 0.3330078125 zurücklesen lässt

Ältere Tutorials drucken 10911 und gehen weiter, ohne zu sagen, welche Regel dahintersteckt. Nehmen Sie diese Zahl in eine Codebasis, deren Encoder rundet, und Ihre Golden Vectors fallen im ersten Lauf durch, und zwar mit einem Unterschied von eins, der eher nach Tippfehler aussieht als nach abweichender Richtlinie.

Negative Werte sind die Stelle, an der die drei Regeln auseinandergehen. -0.1 × 32768 = -3276.8 ergibt beim Abschneiden -3276 (Wert -0.0999755859375) und beim Abrunden oder Runden zur nächsten Zahl -3277 (Wert -0.100006103515625). Abschneiden und Runden zur nächsten Zahl behandeln beide Vorzeichen gleich, nämlich ±3276 beziehungsweise ±3277, ein symmetrisches Koeffizientenpaar bleibt also symmetrisch. Beim Abrunden gilt das nicht: Es schickt +0.1 auf 3276, -0.1 dagegen auf -3277, und damit gerät das Paar um genau eins aus der Symmetrie.

Was Ihre Sprache standardmäßig tut

Jede dieser Voreinstellungen ist für sich vertretbar, und keine davon sagt Ihnen, dass sie gerade greift.

  • C/C++: Ein Cast von Gleitkomma auf einen Ganzzahltyp schneidet Richtung null ab. (int16_t)(0.333f * 32768) ergibt 10911.
  • Python: Das eingebaute round() löst Gleichstände zur geraden Zahl auf, also ist round(3276.8) gleich 3277 und round(2.5) gleich 2.
  • JavaScript: Math.round löst Gleichstände Richtung plus unendlich auf, was nicht symmetrisch ist. Math.round(2.5) ist 3, aber Math.round(-2.5) ist -2.
  • Hardware: Viele DSP-Multiply-Accumulate-Pfade runden beim Schieben und bieten Nearest-Even als Modus-Bit an; deshalb können das C-Referenzmodell und das Silizium so lange auseinanderlaufen, bis jemand das Modusregister ausliest.

Wählen Sie eine Regel, benennen Sie sie in der Formatspezifikation neben W und F, und sorgen Sie dafür, dass die Testvektoren sie mittragen.

Überlauf: Sättigung oder Wraparound

Ein Q15-Überlauf tut eines von drei Dingen: den Wert ablehnen, ihn auf 0.999969482421875 begrenzen oder ihn auf -1.0 umlaufen lassen. Welches davon Sie bekommen, ist eine Richtlinie, die Ihre Toolchain gewählt hat, und die dritte Variante dreht das Vorzeichen lautlos um.

Nehmen Sie 1.0. Die Skalierung ergibt 1.0 × 32768 = 32768, und die größte vorzeichenbehaftete 16-Bit-Ganzzahl ist 32767. Der Wert liegt um genau eins außerhalb des Bereichs.

RichtlinieGespeichertes WortZurückgelesener WertWie es sich weiter hinten zeigt
FehlerkeinesUmrechnung abgelehntLaut und abfangbar, für Werkzeuge meist die richtige Wahl
Sättigen0x7FFF = 327670.999969482421875Von 1.0 weder hörbar noch sichtbar zu unterscheiden
Umlaufen0x8000 = -32768-1.0Vorzeichenumkehr bei Vollaussteuerung

Die Sättigungszeile verliert 0.000030517578125, und niemand merkt es. Die Umlaufzeile macht aus einem positiven Vollausschlag einen negativen, und in einem Audiopfad ist das ein Knacken, das man quer durch den Raum hört. In einem Regelkreis ist es ein Stellbefehl mit vollem Ausschlag in die falsche Richtung.

Die gefährliche Eigenschaft des Wraparound ist, dass er ein vollkommen gültig aussehendes Wort erzeugt. 0x8000 ist eine legale Q15-Kodierung von -1.0. Nichts weiter hinten in der Kette kann es von einem echten -1.0-Sample unterscheiden, im Nachhinein bleibt also keine Signatur, nach der man greppen könnte. Sie finden den Fall nur, indem Sie die Stelle instrumentieren, an der der Überlauf passiert.

Warum DSP-Silizium sättigende Befehle mitbringt

Sättigung ist das Verhalten, das die Signalverarbeitung will, also implementieren Prozessoren sie, statt sie einem Sprungbefehl zu überlassen. ARM hat QADD/QSUB und die sättigenden Schiebebefehle SSAT/USAT, NEON hat VQADD und Verwandte, und x86 SSE hat gepackte sättigende Additionen wie paddsw. Die Familien C6000 und C55x von TI machen die Sättigung als Modus-Bit im Akkumulatorpfad zugänglich.

In einem Filter oder einem Mischer ist ein gelegentlich geclipptes Sample eine kleine örtliche Verzerrung. Ein umgelaufenes Sample ist eine Sprungstelle mit Energie über das gesamte Spektrum. Die Hardware wählt standardmäßig leicht falsch statt katastrophal falsch. Das gilt aber nur, wenn Sie die Sättigung eingeschaltet haben; schlichte C-Ganzzahlarithmetik auf demselben Chip läuft weiterhin um.

Warum eine Q15-Multiplikation eine Rechtsverschiebung um 15 Bit braucht

Multiplizieren Sie zwei Q15-Wörter als Ganzzahlen, ist das Ergebnis korrekt, aber es ist kein Q15 mehr. Nachkommabits addieren sich bei der Multiplikation: Q15 × Q15 ergibt Q30.

Rechnen Sie 0.5 × 0.5 durch, wo die richtige Antwort offensichtlich 0.25 lautet:

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

Interpretieren Sie 268435456 als Q15, dann lesen Sie 8192.0, und das liegt um den Faktor 32768 daneben. Dieser Faktor ist der ganze Fehler, und er erklärt, warum Festkommafilter, die „fast funktionieren“, oft um eine Zweierpotenz danebenliegen.

Das Produkt braucht außerdem Platz. Zwei 16-Bit-Werte multiplizieren sich auf bis zu 32 Bit, das Zwischenergebnis muss also ein int32_t sein. Viele Produkte aufzuaddieren braucht noch mehr Reserve; deshalb sind DSP-Akkumulatoren auf Bausteinen wie dem C55x 40 Bit breit.

Runden Sie beim Schieben, statt die Bits einfach fallen zu lassen

Ein nacktes >> 15 wirft die unteren 15 Bit weg, und das ist bei vorzeichenbehafteten Werten ein Abrunden Richtung minus unendlich. Addiert man vorher ein halbes LSB der ausgehenden Genauigkeit, wird daraus ein Runden zur nächsten Zahl:

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

Mit cc -std=c11 -Wall -o q15 q15.c && ./q15 übersetzt, gibt das aus:

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

Die dritte Zeile ist der -1-Fall von vorhin. -32768 × -32768 = 1073741824, was in Q30 1.0 ist und für Q15 außerhalb des Bereichs liegt, also begrenzt sat_q15 es auf 32767. Ohne diese Begrenzung lässt der Cast auf int16_t den Wert auf -32768 umlaufen und macht aus -1 × -1 ein -1.

Die letzten beiden Zeilen sind der Rundungsunterschied. 3277 × 3277 = 10738729, und eine nackte Verschiebung ergibt 327 (0.009979248046875), die gerundete dagegen 328 (0.010009765625). Das wahre Produkt ist 0.01, die gerundete Variante landet also mehr als doppelt so nah dran. Das kostet eine zusätzliche Addition.

Ein Vorbehalt zur Verschiebung selbst: Das Rechtsschieben einer negativen vorzeichenbehafteten Ganzzahl ist in C vor C23 implementierungsdefiniert, auch wenn jeder Compiler, dem Sie begegnen werden, arithmetisch schiebt. Falls Ihnen dabei unwohl ist, teilen Sie durch 32768 und lassen Sie den Compiler die Verschiebung erzeugen, oder verschieben Sie auf einem vorzeichenlosen Typ, nachdem Sie einen Bias addiert haben. Die allgemeine Mechanik von Verschiebungen und Masken behandelt der Leitfaden zu den bitweisen Operationen.

Die Addition verlangt zuerst gleiche Q-Werte

Die Multiplikation verändert den Q-Wert vorhersagbar. Die Addition duldet eine Abweichung überhaupt nicht: Ein Q7-Wort zu einem Q15-Wort zu addieren ergibt Unsinn, weil die Operanden keinen gemeinsamen Maßstab haben.

Bringen Sie sie erst mit einer Verschiebung auf eine Linie. 0.5 ist in Q7 64, und 64 << 8 ist 16384, also 0.5 in Q15. Der Verschiebebetrag ist die Differenz der Nachkommabits, 15 - 7 = 8.

Hochschieben ist exakt, kostet aber Reserve, denn ein auf Q15 hochgestufter Q7-Wert braucht den breiteren Behälter. Herunterschieben verliert Information und verlangt dieselbe Rundungsentscheidung wie die Multiplikation. So oder so: Schreiben Sie den Q-Wert jedes Zwischenergebnisses in einen Kommentar. Festkommacode, dessen Maßstäbe nur im Kopf des Autors leben, ist binnen eines Monats nicht mehr wartbar.

Q15-Festkomma oder IEEE-754-Gleitkomma: die Wahl

Beide sind binäre Stellenwertsysteme, keines hat also im Prinzip einen Genauigkeitsvorteil. Die Wahl hängt davon ab, was die Zielhardware kostet und welche Garantien Sie brauchen.

FrageSpricht für FestkommaSpricht für Gleitkomma
Gibt es eine Hardware-FPU?Keine FPU oder eine Soft-Float-BibliothekHardware-FPU mit Ein-Takt-Operationen
Wie groß ist der Dynamikbereich?Bekannt und begrenzt, etwa normalisiertes AudioUmspannt viele Größenordnungen
Legt das Übertragungsformat einen Maßstab fest?Protokoll oder Register fixiert den BinärmaßstabDas Feld ist ein echter Float
Müssen Ergebnisse über Builds hinweg bitgenau sein?Ja, Ganzzahlen reproduzieren sich überallAbweichungen durch FMA und Optimierung sind vertretbar
Ist Speicher oder Bandbreite knapp?16-Bit-Samples halbieren den Platzbedarf von 32-Bit-FloatsKeine Einschränkung
Wer pflegt den Code?Team ist mit Q-Notation bereits vertrautGemischtes Team, Skalierungsfehler sind das größere Risiko

Die letzte Zeile ist ernst gemeint. Festkomma verschiebt den Fehler von der Laufzeit in die Entwurfsphase, und das ist nur dann ein guter Tausch, wenn jemand die Entwurfsarbeit auch macht. Auf einem Cortex-M4F mit Hardware-FPU ist einfach genaues Float oft die schnellere und sicherere Wahl, und der überlieferte Reflex, zu Q15 zu greifen, stammt von Bausteinen, die den Markt längst nicht mehr beherrschen.

Wo IEEE 754 übernimmt

Greifen Sie zu Gleitkomma, wenn das Wort selbst ein Vorzeichen, einen Exponenten und eine Mantisse trägt statt eines festen Maßstabs. Dann gilt das Q-Format nicht mehr: Es gibt kein einzelnes 2^F zum Teilen, weil der Exponent von Wert zu Wert variiert.

Die beiden Darstellungen begegnen sich in der Praxis ständig. Sensordaten kommen als Q15-Registerwörter an, gehen für eine längere Rechnung auf Float und kehren für den DAC nach Q15 zurück. Um die Gleitkommahälfte dieses Pfads Bit für Bit zu inspizieren, zerlegt der IEEE-754-Konverter einen Wert in Vorzeichen, Exponent und Mantisse und gibt die exakt gespeicherte Dezimalzahl aus. Das ist dieselbe Aufgabe, die der Q-Format-Konverter für einen festen Maßstab erledigt.

Beide stehen auf demselben Fundament

Q-Format, IEEE 754 und schlichte Ganzzahlen lesen alle dieselben Bits, nur mit unterschiedlichen Regeln darüber, wo der Punkt sitzt und ob er sich bewegen darf. Wenn der Stellenwertteil wackelig wirkt oder Sie 0x0CCD schneller als 0000 1100 1100 1101 lesen wollen, ohne zum Taschenrechner zu greifen, deckt die Einführung in die Binär-, Hex- und Oktalumrechnung die Grundlagen ab, auf denen beide Formate aufbauen.

FAQ zu Q15-Festkomma

Was bedeutet Q15?

In der gängigen DSP-Konvention ist Q15 ein vorzeichenbehaftetes 16-Bit-Zweierkomplementwort mit 15 Nachkommabits: eine Vorzeichenstelle und 15 Nachkommastellen. Der Maßstab ist 2^15 = 32768, die Auflösung ist 1/32768 = 0.000030517578125, und der Bereich reicht von -1 bis 32767/32768. Weil Q-Etiketten von Dokument zu Dokument variieren, prüfen Sie lieber Gesamtbreite und Vorzeichen, statt dem Etikett allein zu trauen.

Sind Q15 und Q1.15 dasselbe?

Sie beschreiben in der Regel dieselbe vorzeichenbehaftete 16-Bit-Anordnung, wobei die 1 in Q1.15 die Vorzeichenstelle zählt. Die Notation ist aber nicht allgemeingültig, und manche Autoren schlagen das Vorzeichenbit auf m obendrauf, statt es darin mitzuzählen. Die verlässliche Beschreibung ist Vorzeichen plus Gesamtbits W plus Nachkommabits F: für diese Anordnung signed, W=16, F=15.

Was ist 0.5 in Q15?

0.5 ist in Q15 die gespeicherte Ganzzahl 16384, hexadezimal 0x4000. Die Rechnung lautet 0.5 × 32768 = 16384, und das ist bereits eine ganze Zahl, also gibt es keine Rundung und keinen Quantisierungsfehler. Das Dekodieren bestätigt es: 16384 ÷ 32768 = 0.5 exakt.

Was sind der größte und der kleinste Wert von Q15?

Das Minimum ist -1, und es gehört dazu, weil -32768 ÷ 32768 exakt -1 ergibt. Das Maximum ist 32767/32768 = 0.999969482421875. Beide Werte sind erreichbar, der Bereich ist also [-1, 0.999969482421875]; 1.0 ist der Wert, der außerhalb liegt. Quellen, die die untere Grenze als offenes Intervall schreiben, liegen falsch, und dieser Fehler verdeckt den Überlauffall -1 × -1.

Was passiert bei einem Q15-Überlauf?

Das hängt von der geltenden Richtlinie ab. Eine Fehlerrichtlinie lehnt die Umrechnung ab. Die Sättigung begrenzt auf den nächstgelegenen Endpunkt, aus 1.0 wird also 0.999969482421875, ein Verlust von einem LSB, der meist unhörbar bleibt. Der Wraparound rechnet modulo 2^16, 1.0 skaliert also auf 32768, was sich als -32768 und damit als -1.0 zurücklesen lässt, also eine vollständige Vorzeichenumkehr. DSP-Hardware sättigt aus genau diesem Grund standardmäßig, schlichte C-Ganzzahlarithmetik läuft dagegen um.

Warum brauchen zwei Q15-Zahlen nach der Multiplikation eine Rechtsverschiebung um 15?

Weil sich die Nachkommabits addieren. Q15 × Q15 erzeugt ein Q30-Produkt, das ganzzahlige Ergebnis trägt also 30 statt 15 Nachkommabits. Eine Rechtsverschiebung um 15 richtet es wieder auf Q15 aus: 16384 × 16384 = 268435456, und 268435456 >> 15 = 8192, was zu 0.25 dekodiert. Addieren Sie vor der Verschiebung 1 << 14, um zur nächsten Zahl zu runden statt abzuschneiden, und halten Sie das Zwischenergebnis in einem int32_t, damit das 32-Bit-Produkt nicht überläuft.

Wann sollte ich Q-Format statt IEEE 754 verwenden?

Verwenden Sie das Q-Format, wenn ein Protokoll, ein Registerlayout, ein DSP-Algorithmus oder ein Codec bereits einen Binärmaßstab für ein Ganzzahlwort festgelegt hat. Der Maßstab ist dann Teil der Schnittstelle, und Sie haben keine Wahl. Verwenden Sie IEEE 754, wenn der Wert einen großen Dynamikbereich braucht, wenn das Ziel eine Hardware-FPU hat oder wenn das Feld tatsächlich Vorzeichen, Exponent und Mantisse speichert. Für einen reinen Basiswechsel ohne angehängten Maßstab gilt keines von beiden; das ist gewöhnliche Zahlensystemumrechnung.

Die Kurzfassung

Q15-Festkomma besteht aus einer Multiplikation, einer Rundungsentscheidung und einer Bereichsprüfung. raw = round(value × 32768) hinein, value = raw ÷ 32768 heraus. Die Formel ist trivial; die Fehler stecken alle in den Teilen, die niemand dokumentiert.

Also dokumentieren Sie sie. Schreiben Sie Vorzeichen, W und F neben jedes Q-Etikett, statt anzunehmen, das Etikett genüge. Benennen Sie den Rundungsmodus an derselben Stelle, denn Abschneiden und Abrunden trennen sich bei negativen Werten um ein volles LSB. Sagen Sie ausdrücklich, ob ein Überlauf einen Fehler auslöst, sättigt oder umläuft, denn der Umlauffall macht aus 1.0 ein -1.0 und hinterlässt keine Spur.

Wenn ein Registerwort und eine Tabellenkalkulation sich widersprechen, dekodieren Sie einen bekannten Wert im Q-Format-Konverter. Er zeigt die gespeicherte Ganzzahl, die exakte Dezimalzahl, den Quantisierungsfehler und den darstellbaren Bereich nebeneinander, alles im Browser berechnet. Das genügt meist, um zu klären, wessen Annahme über W und F falsch war.

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

Verwandte Artikel

Alle Artikel anzeigen