Fixed-Point Arithmetik
-
SeppJ wollte dir klarmachen, daß du
deltaB = (440 * 256) / 44100;
benutzen solltest.
Da 'deltaB' jedoch eine Ganzzahl ist, wird sie nicht ungefähr 2.5 beinhalten, sondern exakt 2
-
OK
stimmt. Aber das löst immer noch nicht mein Problem, dass er die Nachkommastellen nicht bewertet. Wenn ich das Ergebnis shifte, ergibt das nach wie vor 2. Ich möchte aber die 0.5 auch noch mit bewertet haben.
-
cinematix schrieb:
OK
stimmt. Aber das löst immer noch nicht mein Problem, dass er die Nachkommastellen nicht bewertet. Wenn ich das Ergebnis shifte, ergibt das nach wie vor 2. Ich möchte aber die 0.5 auch noch mit bewertet haben.
- Eine Formel wie (440/44100) * 256 oder (440 * 256) / 44100 wird vom Compiler immer ganzzahlmäßig berechnet. Willst Du Kommastellen, dann musst Du das dem Compiler mitteilen:
(440.0/44100.0) * 256.0
Nun gut, alle ".0" sind nicht notwendig, aber sicher ist sicher.
-
Selbst wenn der Compiler eine Kommazahl errechnet, kann er sie einem Integer (deltaB) doch nur als Ganzzahl mitteilen. Sprich: er schneidet die Nachkommastellen ab.
-
Ein Integer speichert ganze Zahlen. Wenn Du da trickreich einen Nachkommaanteil hineinbringen willst, dann musst uns die Tricks mitteilen. Bis jetzt ist nicht klar, wieviele Nachkommastellen auf welche Weise in deltaB gespeichert werden.
-
Bei einem Shift nach links, gehen die linken Bits verloren. Sie kommen auch nicht wieder mit einem Shift nach rechts.
-
Warum willst Du mit Fixkomma arbeiten, wo C doch so schöne Gleitkommaarithmetik bietet:
#include <stdio.h> #include <math.h> int main( void ) { double deltaB = 0; int vorkomma; double nachkomma; deltaB = (440.0/44100.0) * 256.0; printf ("deltaB: %.16f\n",deltaB); vorkomma = fabs (deltaB); nachkomma = deltaB - vorkomma; printf ("vorkomma:%i\n",vorkomma); printf ("nachkomma:%.16f\n",nachkomma); return 0; }
viele grüße
ralph
-
Das ganze gehört zu einem Programm, welches später auf einem Microprozessor laufen soll. Für diesen ist die Verwendung von Floating-Point ungeeignet.
Angenommen ich habe 32 Bits zu Verfügung (zur Übersicht schreibe ich nicht alle Bits), dann wäre die 2.5 ->
00000010.10000000. Wenn ich jetzt 8 Bit nach links shifte, habe ich
1010000000.00000000. Addiere ich jetzt diesen Wert mit sich selbst, habe ich
10100000000.00000000. Das ganze um 8 Bits nach links, dann habe ich
00000101.00000000. Also die 5 und das Ganze unter Berücksichtigung des Nachkommaanteils.Ich hoffe das ist verständlich ausgedrückt
.
-
Ungetestet: Dein Problem ist ja sicherlich, dass 440*256 > 2^16, oder? Falls ja, dann musst du quasi den Datentyp "erweitern" und virtuell mit einem 2x32-Bit Datentypen rechnen. Schönerweise hat der Datentyp (hier: int) ja schon automatisch die passenden 32-Bit und kann 440*256 fassen. Somit kannst du den Vorkommateil als (440*256)/44100 = 2 berechnen. Diese Ergebnis shiftest du dann für dein Endresultat um 16 Bit links. Der Nachkommateil ist der Rest, also (440*256)%44100 = 24440. Der Nachkommateil passt (sofern der Divisor vorher kleiner als 2^16 war) wieder in 16 Bit und du kannst ganz natürlich 24440/44100 berechnen, wie du auch sonst die Division von Fixed-Point-Zahlen durchführt. Das Ergebnis ist konstruktionsbedingt immer < 1, du bekommst also direkt den Nachkommateil.
Ich kenne mich mit Fixed-Point überhaupt nicht aus. Spricht irgendetwas gegen diese naive Herangehensweise?
-
Ich verstehe nicht ganz was du meinst mit dem Rest.
Ich muss das ganze ja so ausrechnen, das er das so rechnet:
2.5
5.0
7.5
10.0
12.5 ...und nur jeweils den Vorkommaanteil nimmt (2,5,7...). Dafür muss der Nachkommaanteil irgendwie mit einbezogen werden.
Laufen würde es, wenn man z.B. rechnet (440 * 256 * 1<<16) / 44100. Nur das passt nicht mehr in den Integer Bereich.
-
cinematix schrieb:
Ich verstehe nicht ganz was du meinst mit dem Rest.
Wie würdest du denn 24440/44100 berechnen?
-
Ich verstehe die Frage bzw. die Aufgabe ehrlich gesagt nicht genau. Ich hätte sie gerne etwas abstrakter formuliert, "was ist das Ziel", nicht "an welchen Details bin ich gescheitert". Das einzige was ich wirklich rauslesen kann: Es handelt sich um eine Audio-Anwendung
-
cinematix schrieb:
Ich verstehe nicht ganz was du meinst mit dem Rest.
Ich muss das ganze ja so ausrechnen, das er das so rechnet:
2.5
5.0
7.5
10.0
12.5 ...und nur jeweils den Vorkommaanteil nimmt (2,5,7...). Dafür muss der Nachkommaanteil irgendwie mit einbezogen werden.
Für die reine Addition brauchst Du die Schieberei nicht:
#include <stdio.h> typedef union { unsigned long ui; unsigned short sh[2]; } Delta; int main( void ) { Delta deltaB = {0}; Delta deltaC = {0}; int i; deltaB.ui = 0x00028000; // 2.5 printf ("deltaB.ui: %08X\n",deltaB.ui); printf ("deltaB.sh[0]: %08X\n",deltaB.sh[0]); // Nachkomma printf ("deltaB.sh[1]: %08X\n",deltaB.sh[1]); // Vorkomma for (i=0; i<10; ++i) { deltaC.ui += deltaB.ui; printf ("deltaC.sh[1]: %u\n",deltaC.sh[1]); // Vorkomma } return 0; }
Laufen würde es, wenn man z.B. rechnet (440 * 256 * 1<<16) / 44100. Nur das passt nicht mehr in den Integer Bereich.
Ich bin mir nicht sicher, ob das stimmt. Der Nachkommaanteil ist binär: 0.5 wird zu 0.1. Das ist nicht ganz einfach zu bestimmen und macht die Quälerei von Gleitkomma ohne FPU aus. Vielleicht kommt mir in absehbarer Zeit noch eine gute Idee.
viele grüße
ralph
-
rkhb schrieb:
Vielleicht kommt mir in absehbarer Zeit noch eine gute Idee.
Hier bitte:
#include <stdio.h> unsigned long nalos1 () { unsigned int freq = 440; unsigned int rate = 44100; unsigned long nach = 0; unsigned long set_bit = (1<<31); unsigned long vor = (freq * 256) / rate; unsigned long rest = (freq * 256) % rate; int i, r; // Nachkommabits von links setzen for (i=0; i<32; ++i) { rest <<= 1; r = rest - rate; if (r >= 0) { nach |= set_bit; rest = r; } set_bit>>=1; } // Nachkomma nach rechts shiften & evtl. aufrunden nach >>= 15; ++nach; nach >>= 1; // Vorkomma dazu: die Fixkommazahl ist fertig nach |= (vor<<16); return nach; } unsigned long nalos2 () { unsigned int freq = 440; unsigned int rate = 44100; unsigned long nach = 0; unsigned long set_bit = (1<<31); unsigned long rest = freq % rate; int i, r; // Nachkommabits von links setzen for (i=0; i<32; ++i) { rest <<= 1; r = rest - rate; if (r >= 0) { nach |= set_bit; rest = r; } set_bit >>= 1; } // Nachkomma so nach rechts shiften, dass Fixkomma (Nachkomma * 256) entsteht nach >>= 7; ++nach; // evtl. Aufrunden nach >>= 1; return nach; } int main ( void ) { unsigned long fix; fix = nalos1(); printf ("nalos1: %08X\n",fix); // 00028DE0 fix = nalos2(); printf ("nalos2: %08X\n",fix); // 00028DE0 return 0; }
Beide Funktionen berechnen eine Nachkommazahl und geben die von Dir gewünschte Fixkommazahl zurück. Die Nachkommazahl beginnt immer beim höchstwertigen Bit (links). Du kannst die Präzision in der Schleife beim i-Wert beeinflussen, z.B.: for (i=0; i<16; ++i) - jetzt ist die Nachkommazahl nur noch 16 Bit weit.
Die Funktion nalos1 berücksichtigt Deine Rechnerei ((440/44100)*256). Das Erstellen der Fixkommazahl beschränkt sich dort auf das Rechtsschieben der Nachkommazahl, damit die vordersten 16 Bit hinten zu liegen kommen, und das Oderieren mit der Vorkommazahl.
Die Funktion nalos2 errechnet die Nachkommazahl aus (440/44100). Um auf die von Dir gewünschte Fixkommazahl (mal 256) zu kommen, muss noch etwas getrickst werden mit der Schieberei.
viele grüße
ralph