2010-12-07 8 views
6

Ich möchte eine Hypot2-Berechnung auf einem 16-Bit-Prozessor tun.Wie Hypot2 (x, y) Berechnung, wenn Zahlen überlaufen können

Die Standardformel lautet c = sqrt((a * a) + (b * b)). Das Problem dabei ist, dass es bei großen Eingaben überläuft. Z.B. 200 und 250, multipliziere 200 * 200, um 90.000 zu erhalten, was höher ist als der maximale signierte Wert von 32.767, so dass es überläuft, wie auch b, werden die Zahlen addiert und das Ergebnis kann ebenso nutzlos sein; es könnte sogar eine Fehlerbedingung wegen eines negativen sqrt signalisieren.

In meinem Fall habe ich mit 32-Bit-Zahlen zu tun, aber 32-Bit-Multiplikation auf meinem Prozessor ist sehr schnell, etwa 4 Zyklen. Ich benutze einen dsPIC Mikrocontroller. Ich möchte lieber nicht mit 64-Bit-Zahlen multiplizieren, weil das wertvolles Gedächtnis verschwendet und zweifellos langsamer sein wird. Außerdem habe ich nur sqrt für 32-Bit-Nummern, so dass 64-Bit-Nummern eine andere Funktion benötigen. Also, wie kann ich einen Hypot berechnen, wenn die Werte groß sein können?

Bitte beachten Sie, dass ich nur ganzzahlige Mathematik dafür verwenden kann. Die Verwendung von Fließkomma-Mathe führt zu einem Geschwindigkeitshit, den ich eher vermeiden möchte. Mein Prozessor hat eine schnelle Integer/Fixed-Point atan2-Routine, etwa 130 Zyklen; Könnte ich damit die Länge der Hypotenuse berechnen?

+0

„Mein Prozessor eine schnelle integer/Fixpunkt atan2 Routine, etwa 130 Zyklen hat, konnte ich diese die Hypotenuse Länge berechnen“ dsPIC hat eine atan2 aber keine hypot ?! Ich bin vage vertraut mit PICs und ich bin überrascht, es zu hören. –

+0

@Jason S es ist eine Fixpunkt Software-Routine. –

+0

Könnten Sie einen Link zu ihrer Softwareroutine veröffentlichen? Wenn es CORDIC-bezogen ist, erhalten Sie sowohl den Winkel als auch den Radius aus derselben Routine heraus. –

Antwort

3

Je nachdem, wie viel Genauigkeit Sie benötigen, können Sie die Quadrate und die Quadratwurzeloperation vermeiden. Es gibt einen Abschnitt zu diesem Thema in (Abschnitt 10.2, "High-Speed ​​Vector-Magnitude Approximation", ab Seite 400 in meiner Ausgabe).

Die Näherung ist im wesentlichen:

magnitude = alpha * min + beta * max 

wobei max und min die Maximal- und Minimalabsolutwerte der realen und imaginären Komponenten sind, und alpha und beta sind zwei Konstanten, die ausgewählt werden, eine angemessene Fehlerverteilung zu geben, über den Bereich von Interesse.Diese Konstanten können als Brüche mit einer Potenz von 2 Teilern dargestellt werden, um die Arithmetik einfach/effizient zu halten. In dem Buch schlägt er vor alpha = 15/16, beta = 15/32, und Sie können dann die Formel vereinfachen:

magnitude = (15/16) * (max + min/2) 

die umgesetzt werden könnten wie folgt Integer-Operationen verwenden:

magnitude = 15 * (max + min/2)/16 

und natürlich können wir Schichten für die Divisionen verwenden:

magnitude = (15 * (max + (min >> 1))) >> 4 

Fehler ist +/- 5% über einem Quadranten.

Weitere Informationen zu dieser Technik hier: http://www.dspguru.com/dsp/tricks/magnitude-estimator

+0

Ich lasse jetzt nicht 50 Pfund auf ein Buch fallen (hier ein armer Student), irgendwelche Hinweise aus deiner Ausgabe? –

+0

@Thomas O: Ich habe einige Details zu der obigen Antwort hinzugefügt. –

+0

Ich habe etwas ähnliches mit ~ 0,91 für Alpha und ~ 0,414 für Beta gesehen. Ich denke, ich könnte das mit reiner Integer-Mathematik machen, was wünschenswert wäre. Es würde mich auf 2^27 beschränken, aber das ist okay für meine Bewerbung. –

0

Benötigen Sie volle Präzision? Wenn Sie dies nicht tun, können Sie Ihre Reichweite ein wenig erhöhen, indem Sie einige weniger signifikante Bits verwerfen und danach multiplizieren.

Können a und b alles sein? Wie wäre es mit einer Nachschlagetabelle, wenn Sie nur ein paar a und b haben, die Sie berechnen müssen?

0

Eine einfache Lösung Überlauf zu vermeiden, ist sowohl a und b durch a+b vor Quadrierung zu teilen und dann durch die Quadratwurzel a+b multiplizieren. Oder machen Sie das gleiche mit max(a,b).

0

Sie können ein wenig einfache Algebra tun, um die Ergebnisse wieder in den Bereich zu bringen.

sqrt((a * a) + (b * b)) 
= 2 * sqrt(((a * a) + (b * b))/4) 
= 2 * sqrt((a * a)/4 + (b * b)/4) 
= 2 * sqrt((a/2 * a/2) + (b/2 * b/2)) 
+0

Dies reduziert die Wahrscheinlichkeit eines Überlaufs leicht, aber beseitigt es nicht. Teilen um 2 bewirkt, dass eine Ganzzahl ein Bit weniger verwendet. Quadrieren verdoppelt die Anzahl der Bits. Sie können auch eine abs um a und b werfen, die ein bisschen (fast) abschneidet. Wenn also "b" 32 Bits ist, ist "abs (b)/2" 30 Bits. Quadrierung, die uns 60 Bits gibt, immer noch viel mehr als 32. Wenn die Eingaben kleiner als +/- 131072 gehalten werden könnten, könnte dies jedoch funktionieren. –

+0

@Laurence, der Divisor könnte auf jeden beliebigen Betrag erhöht werden, der notwendig ist, um die Zwischenwerte in Reichweite zu halten. Die Mathematik bleibt gleich. –

+0

Sicher, aber damit es mit beliebigen 32-Bit-Zahlen funktioniert, bedeutet das, dass Sie durch 65536 teilen müssen. Das ist ziemlich ein Verlust der Präzision. Ich nehme an, Sie könnten sich daran anpassen: Verschieben Sie die Zahlen nach rechts, bis beide in 16 Bits passen, und verschieben Sie dann das Ergebnis am Ende zurück, aber sobald es so kompliziert wird, muss ich mich fragen, ob es wirklich billiger ist als 64 Bit Mathematik. –

1

Aniko und John, es scheint mir, dass Sie das OP-Problem nicht angesprochen haben. Wenn a und b ganze Zahlen sind, ist a * a + b * b wahrscheinlich überlaufen, weil Integer-Operationen ausgeführt werden. Die offensichtliche Lösung besteht darin, a und b in Gleitkommawerte zu konvertieren, bevor a * a + b * b berechnet wird. Aber das OP hat uns nicht wissen lassen, welche Sprache wir verwenden sollten, also sind wir ein bisschen festgefahren.

+0

Keine Hardware-FPU; es würde meinem momentan sehr schnellen 100-Zyklen-Hypot2 (der mit kleinen Zahlen arbeitet) zusätzliche 500-1000 Zyklen hinzufügen. Die naheliegende Lösung besteht nicht darin, Fließkommazahl zu verwenden, weil es ein Mikrocontroller ist. –

+0

16-Bit-Prozessoren haben wahrscheinlich keine performanten Gleitkommaoperationen. –

+0

Ich benutze C, aber eine allgemeine Lösung ist bevorzugt. –

2

Da Sie im Grunde keine Multiplikationen ohne Überlauf tun können, werden Sie wahrscheinlich etwas Genauigkeit verlieren.

Um die Zahlen in einem akzeptablen Bereich zu bekommen, x einige Faktor herausziehen und

c = x*sqrt((a/x)*(a/x) + (b/x)*(b/x)) 

verwenden Wenn x ein gemeinsamer Faktor ist, werden Sie nicht Präzision verlieren, aber wenn es nicht, dann ist werden Sie Präzision verlieren .

Update: Noch besser ist, da Sie einige leichte Arbeit mit 64-Bit-Zahlen zu tun, mit nur einem 64-Bit Außerdem kann man ein winzigen mit nur den Rest dieses Problems in 32-Bit zu tun Verlust der Genauigkeit. Um dies zu tun: Machen Sie die zwei 32-Bit-Multiplikationen, um Ihnen zwei 64-Bit-Zahlen zu geben, fügen Sie diese und dann Bit-Verschiebung wie benötigt, um die Summe zurück auf 32 Bits vor der Wurzel zu nehmen. Wenn Sie Bit immer um 2 Bits verschieben, multiplizieren Sie einfach das Endergebnis mit 2^(die Hälfte der Bitverschiebungen), basierend auf der obigen Regel.Das Abschneiden sollte nur einen sehr geringen Genauigkeitsverlust verursachen, nicht mehr als 2^31 oder 0,00000005% Fehler.

3

Dies ist wörtlich aus this @John D. Cook blog post, daher CW genommen:

Hier ist, wie sqrt(x*x + y*y) ohne Überlauf zu riskieren, zu berechnen.

  1. max = maximum(|x|, |y|)
  2. min = minimum(|x|, |y|)
  3. r = min/max
  4. return max*sqrt(1 + r*r)

Wenn @John D. Koch entlang und Pfosten kommt dies sollte man ihm geben das akzeptieren :)

+0

Ich mag es. Ich werde es versuchen! –

+4

Scheint, dass dies nicht gut mit Integer-Arithmetik funktionieren würde. –

+0

@Mark Ransom, das könnte dann ein Problem sein. –

1

Die Standardformel ist c = sqrt ((a * a) + (b * b)). Das Problem liegt bei großen> Eingängen, die überlaufen.

Die Lösung für Überläufe (abgesehen von einem Fehler) ist, Ihre Zwischenberechnungen zu sättigen.

Berechnen Sie C = a * a + b * b. Wenn a und b 16-Bit-Nummern sind, haben Sie nie einen Überlauf. Wenn es sich um vorzeichenlose Nummern handelt, müssen Sie die Eingaben zuerst nach rechts verschieben, damit die Summe in eine 32-Bit-Zahl passt.

Wenn C> (MAX_RADIUS)^2, MAX_RADIUS zurückgeben, wobei MAX_RADIUS der Maximalwert ist, den Sie tolerieren können, bevor Sie einen Überlauf auslösen.

Ansonsten verwenden Sie entweder sqrt() oder CORDIC algorithm, was die Kosten von Quadratwurzeln zugunsten von Schleifeniteration vermeidet und + Verschiebungen addiert, um die Amplitude des (a, b) Vektors abzurufen.

1

Wenn Sie a und b auf höchstens 7 Bits beschränken können, werden Sie keinen Überlauf erhalten. Sie können eine Zählung-führende-Nullen-Anweisung verwenden, um herauszufinden, wie viele Bits weggeworfen werden sollen.

Angenommen a> = b.

int bits = 16 - count_leading_zeros(a); 
if (bits > 7) { 
    a >>= bits - 7; 
    b >>= bits - 7; 
} 
c = sqrt(a*a + b*b); 
if (bits > 7) { 
    c <<= bits - 7; 
} 

Viele Prozessoren haben diese Anweisung heutzutage, und wenn nicht, können Sie andere fast techniques verwenden.

Obwohl dies nicht die genaue Antwort geben wird, wird es sehr nahe (höchstens ~ 1% niedrig) sein.

+0

+1 Oooh, schlau! – n8wrl

Verwandte Themen