Wie kann ich mache Rundungsfehler in Gleitkomma-Arithmetik für inverse trigonometrische (und sqrt) Funktionen (in C)?

StackOverflow https://stackoverflow.com/questions/4171239

Frage

I haben eine ziemlich komplizierte Funktion, die mehrere Doppel Werte annimmt, die zwei Vektoren, die in 3-Raum der Form darstellen (Magnitude, Breite, Länge), wobei Breite und Länge in Radiant sind und einen Winkel. Der Zweck der Funktion ist, den ersten Vektor um die zweite durch den Winkel festgelegt und gibt den resultierenden Vektor zu drehen. Ich habe bereits überprüft, dass der Code logisch korrekt ist und funktioniert.

Der erwartete Zweck der Funktion ist für Grafiken, so doppelte Genauigkeit nicht erforderlich ist; jedoch auf der Zielplattform, trig (und sqrt) Funktionen, die Schwimmer nehmen (sinf, cosf, atan2f, asinf, acosf und sqrtf speziell) Arbeit schneller auf Doppel als auf Schwimmern (wahrscheinlich, weil die Anweisung solche Werte berechnen eigentlich erfordern verdoppeln, wenn ein Schwimmer übergeben wird, muss der Wert Umwandlung in einen doppelt so hoch sein, was es mit mehr Speicher zu einem Bereich Kopieren erfordert - also über Kopf). Als Ergebnis sind alle Variablen in der Funktion mit doppelter Genauigkeit beteiligt.

Hier ist das Problem: Ich versuche, meine Funktion zu optimieren, so dass es mehrmals pro Sekunde aufgerufen werden kann. Ich habe daher die Anrufe zu sin, cos, sqrt, et cetera mit Aufrufen an die Floating-Point-Versionen dieser Funktionen ersetzt, da sie insgesamt in einer 3-4-facher Geschwindigkeit Erhöhung zur Folge hat. Dies funktioniert für fast alle Eingänge; Wenn jedoch die Eingangsvektoren sind in der Nähe mit den Standard-Einheitsvektoren (i, j oder k), Rundungsfehler für die verschiedenen Funktionen aufgebaut genug, um zu bewirken später ruft sqrtf oder inverse trigonometrische Funktionen (asinf, acosf paralleler, atan2f) auf Argumente übergeben, die sind nur knapp außerhalb der Domäne dieser Funktionen.

Also, ich bin mit diesem Dilemma links: entweder ich nur mit doppelter Genauigkeit Funktionen aufrufen können und vermeiden das Problem (und mit einer Grenze von etwa 1.300.000 Vektoroperationen pro Sekunde am Ende), oder ich kann mit etwas einfallen lassen, versuchen andere . Letztendlich würde ich einen Weg, wie die Eingabe in den inversen trigonometrischen Funktionen zu sanieren Fälle kümmern Rand (es ist trivial für so tun für sqrt: nur Gebrauch abs). keine Option ist Branching, als auch nur eine einzige bedingte Anweisung so viel Aufwand fügt hinzu, dass sich alle Performance-Gewinne verloren.

Also, irgendwelche Ideen?

Edit: jemand ausgedrückt Verwirrung über mein mit Doppel gegen Gleitkommaoperationen. Die Funktion ist viel schneller, wenn ich speichern tatsächlich alle meine Werte in doppelter Größe Container (I.E. Doppeltyp-Variablen), als wenn ich speichern sie in float-Größe Container. Allerdings sind Gleitkommagenauigkeit trig Operationen schneller als doppelte Genauigkeit trig Operationen aus offensichtlichen Gründen.

War es hilfreich?

Lösung

Grundsätzlich müssen Sie, dass Ihr Problem löst einen numerisch stabil Algorithmus zu finden. Es gibt keine Generika Lösungen für diese Art der Sache, braucht es für Ihren speziellen Fall getan werden unter Verwendung von Begriffen wie der Konditionszahl wenn die einzelnen Schritte. Und es kann in der Tat unmöglich, wenn das zugrunde liegende Problem ist, selbst schlecht konditioniert.

Andere Tipps

Single precision floating point stellt inhärent Fehler. So müssen Sie Ihre Mathematik bauen, so dass alle Vergleiche haben einen gewissen Grad an „Slop“ durch einen Epsilon-Faktor verwendet wird, und Sie müssen sanitize Eingaben auf Funktionen mit begrenzten Domänen.

Das erstere ist leicht genug, wenn Verzweigung, zB

bool IsAlmostEqual( float a, float b ) { return fabs(a-b) < 0.001f; } // or
bool IsAlmostEqual( float a, float b ) { return fabs(a-b) < (a * 0.0001f); } // for relative error

Aber das ist chaotisch. Klemm Domain-Eingänge ist ein wenig komplizierter, aber besser. Der Schlüssel ist, zu verwenden, Konditionalbewegungs Operatoren , die im allgemeinen do so etwas wie

float ExampleOfConditionalMoveIntrinsic( float comparand, float a, float b ) 
{ return comparand >= 0.0f ? a : b ; }

in einem einzigen op, ohne eine Filiale entstehen.

Diese variieren je nach Architektur. Auf der x87 Gleitkommaeinheit können Sie es mit dem tun FCMOV bedingte-move op , aber das ist ungeschickt, weil sie unter der Bedingung Flags hängen vorher eingestellt werden, so dass es langsam ist. Außerdem gibt es keine konsistenten Compiler intrinsische für cmov. Dies ist einer der Gründe, warum wir Floating-Point für SSE2 skalare mathematische vermeiden x87 wo möglich.

Bedingte Bewegung ist viel besser in SSE unterstützt durch eine Paarung Vergleichsoperator mit einem bitweise AND. Dies ist bevorzugt, auch für skalare Mathematik:

// assuming you've already used _mm_load_ss to load your floats onto registers 
__m128 fsel( __m128 comparand, __m128 a, __m128 b ) 
{
    __m128 zero = {0,0,0,0};
    // set low word of mask to all 1s if comparand > 0
    __m128 mask = _mm_cmpgt_ss( comparand, zero );  
    a = _mm_and_ss( a, mask );    // a = a & mask 
    b = _mm_andnot_ss( mask, b ); // b = ~mask & b
    return _mm_or_ss( a, b );     // return a | b
    }
}

Compiler sind besser, aber nicht großartig, über Emittieren diese Art von Muster für Ternäre wenn SSE2 skalare mathematische aktiviert ist. Sie können mit dem Compiler-Flag /arch:sse2 auf MSVC oder -mfpmath=sse auf GCC tun.

Auf dem PowerPC und viele anderen RISC-Architekturen ist fsel() ein Hardware-Opcode und damit in der Regel ein Compiler intrinsische als auch.

Haben Sie an der Graphics Programmierung Black Book sah oder vielleicht die Berechnungen Aushändigung off auf Ihre GPU?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top