2016-04-24 9 views
1

Angenommen, ich schreibe eine Physik-Engine mit Kollisionserkennung, die Floats verwendet. Eine Methode könnte sein, zu prüfen, ob sich zwei Physikobjekte schneiden oder berühren.Schreiben aussagekräftiger Komponententests für Code mit Gleitkomma-Ungenauigkeiten (z. B. Kollisionserkennung)

class PhysicsObject { 

    Vector3f position; 

    [...] 

    public void isIntersecting(PhysicsObject otherObject) { 
     boolean isTouchingOrIntersecting = [do calculations]; 
     return isTouchingOrIntersecting; 
    } 

} 

Für die Simulation selbst, die Gleitkommagenauigkeit (auch bei Schwimmern verwendet wird) ist gut genug, da alle Ungenauigkeiten nicht sichtbar/bemerkbar sein wird (und sofort in der nächsten Simulationsschritt korrigiert).

Aber wie soll ich meine Komponententests schreiben, besonders für die Grenzfälle? Ich kann Tests mit zwei weit entfernten Objekten schreiben und zwei Objekte, die sich deutlich schneiden, aber wie wäre es, wenn sie sich genau berühren würden? Abhängig von den Float-Werten kann die Methode je nach Art der externen Bedingung (Compiler, Architektur usw.) True oder False zurückgeben.

Oder sollte ich sagen, dass es nicht wichtig ist, welches Ergebnis die Methode in diesem Grenzfall liefert und es daher keinen Sinn macht, einen Unit Test für diesen Fall zu schreiben?

Wenn der Rückgabewert der Methode wieder ein float ist, ist es klar, dass ich relative Fehler und epsilons verwenden sollte. Aber ich benutze dieses Kollisionserkennungsproblem als ein Beispiel für die ganze Klasse ähnlicher Probleme, wenn Gleitkommawerte in ein "genaues" (boolesches, ganzzahliges) Ergebnis übersetzt werden, und wie man diese (im Kontext von 3D-Grafik/Physik) testet Code).

Antwort

2

IEEE-Arithmetik ist nicht stochastisch. Es ist Bit für Bit reproduzierbar. Wenn es also auf einer Maschine und einem Compiler funktioniert, funktioniert es bei anderen mit einem Vorbehalt. Der Wert von FLT_EVAL_METHOD = {0,1,2} wird aufgrund von Unterschieden in der Genauigkeit der Zwischenberechnungen zu unterschiedlichen Ergebnissen führen. Sie können #ifdefs

#if FLT_EVAL_METHOD == 0 
    EXPECT_EQ(answer0, result); 
#elif FLT_EVAL_METHOD == 1 
    EXPECT_EQ(answer1, result); 
#else /* FLT_EVAL_METHOD == 2 */ 
    EXPECT_EQ(answer2, result); 
#endif 

verwenden, um zwischen diesen verschiedenen Ergebnissen zu sortieren. Wenn Sie Gleitkommaberechnungen verwenden, um eine Entscheidung zu treffen, ist dies leider der einzige Weg. Die gute Nachricht ist, dass ein 3-Wege-Schalter ausreicht. Ich habe ein paar Komponententests, die genau das tun, und sie funktionieren einwandfrei auf vielen Computern & Compiler.

Sie können dasselbe für Gleitkommavergleiche tun, aber wenn Sie nur ungefähr Gleichheit interessiert, können Sie etwas wie googletest EXPECT_DOUBLE_EQ (a, b) verwenden, die auf Gleichheit innerhalb 4 ULPs (Einheiten an der letzten Position) prüft). Die Art und Weise, wie dies funktioniert, besteht darin, zunächst den Gleitkommawert als Ganzzahl mit Vorzeichen zu interpretieren (in eine Union umzuwandeln), in eine Über-XX-Ganzzahl umzurechnen (Verschmelzung ± 0) und dann zu prüfen, ob die Differenz < = 4 ist erzeugt ein relatives Maß, das zu einem absoluten Maß nahe Null degradiert.

Stellen Sie sicher, dass Sie ähnliche Arten von Berechnungen verwenden ob und wo es einen Schnittpunkt zu bestimmen ist. Schreiben Sie in der Tat einen Komponententest, um dies sicherzustellen, und prüfen Sie ihn an der Grenze, indem Sie die Position um ± 1, ± 2, ... ULPs anpassen, um sicherzustellen, dass sie konsistent sind. Wenn die Kreuzungsabfrage wahr angibt, aber die Standortberechnungen abweichen, weil Sie keine Schnittmenge finden können, können Sie in einer Endlosschleife enden oder abstürzen.