2016-05-31 11 views
6

kämpfen wir seit einiger Zeit mit einem Unit-Test von mir. Während der Untersuchung haben wir die Grundursache gefunden, die anscheinend der Vergleich in Floats ist (siehe das folgende Code-Snippet, in dem ich die Berechnung vereinfacht habe, aber immer noch fehlschlägt).C++ Float-Vergleich um Null schlägt fehl Mit Gtest

TEST_F(MyFloatTest, thisOneDoesFail) 
{ 
    const float toCompare = 0.2f - 1.0f + 0.9f; 
    EXPECT_FLOAT_EQ(toCompare, 0.1f); 
} 

Das Ergebnis ist:

Aktuell: 0,1 Erwartet: toCompare Welche ist: 0,099999964

einige Hintergrundinformationen in der numerischen Mathematik zu haben, können wir immer noch nicht herausfinden, warum Dieser Test schlägt fehl, während ein benutzerdefinierter Float-Vergleich mit std :: numeric_limits :: epsilon bestanden wurde. Irgendwann begannen wir zu denken, dass GTest falsch ist und wir haben uns damit abgefunden. Es benutzt seltsame Ausdrücke, die wir nicht vollständig erfassen. Was ist noch seltsamer: Der folgende Test besteht, obwohl ich nur hinzufügen, 1:

TEST_F(MyFloatTest, thisOnePasses) 
{ 
    const float toCompare = 1.2f - 1.0f + 0.9f; 
    EXPECT_FLOAT_EQ(toCompare, 1.1f); 
} 

Wir dachten, es könnte ein Problem sein, wenn negative Float-Werte einschließlich, aber der nächste Test geht auch:

TEST_F(MyFloatTest, thisOnePassesAlso) 
{ 
    const float toCompare = 0.2f - 1.0f + 1.9f; 
    EXPECT_FLOAT_EQ(toCompare, 1.1f); 
} 

Also für uns scheint es als das EXPECT_FLOAT_EQ Makro von Gtest hat einfach ein Problem um Null. Kennt jemand dieses Verhalten? Haben Sie jemals ähnliche in Ihrer Umgebung gesehen? (Übrigens: wir verwenden den MSVC2015) Scheitert es aufgrund der in GTest genannten 4 ULP-Genauigkeit nur zufällig? (was uns auch nicht ganz klar ist).

+1

[Dies] (http 0.125f gleich ist://stackoverflow.com/questions/588004/is-floating-point-math-broken?rq=1) könnte helfen – saloomi2012

+1

Was ist mit dem EXPECT_FLOAT_EQ Makro? – gnasher729

Antwort

4

Das Problem ist, dass eine Gleitkomma-Summe mit einem kleinen Wert und großen Zwischenwerten dazu neigt, einen großen relativen Fehler zu haben. Sie reduzieren den Fehler durch

const float toCompare = 0.2f - (1.0f - 0.9f); 

In Ihrem ursprünglichen Code zu schreiben, der größte war Zwischenwert von 0,2 bis 1,0 = -0,8, acht Mal größer als das Endergebnis. Mit dem geänderten Code ist der größte Zwischenwert 0,1, gleich dem Endergebnis. Und wenn Sie Ihre bestandenen Beispielprüfungen überprüfen, haben Sie in jedem Fall keine Zwischenergebnisse, die im Vergleich zum Endergebnis groß sind.

Das Problem ist nicht mit dem Makro EXPECT_FLOAT_EQ, aber mit der Berechnung.

+0

Danke, das Hinzufügen der Klammern hat tatsächlich das Problem gelöst und EXPECT_FLOAT_EQ schlägt nicht mehr fehl! In meinem Fall ist 0.9f jedoch nicht behoben (es wird hier nur aus Gründen der Vereinfachung hinzugefügt), also werde ich einen zweiten Blick in meine Berechnung werfen, aber jetzt weiß ich, wo ich hinschauen soll. – MattW

5

Fällt es aufgrund der in GTest genannten 4 ULP-Genauigkeit nur zufällig aus?

Das scheint mir der Fall.

Versuchen Sie, die folgenden (sehr grob, nicht tragbar!) Testcode:

float toCompare = 0.2f - 1.0f + 0.9f; 
int i = *reinterpret_cast<int*>(&toCompare); 
std::cout << i << '\n'; 
float expected = 0.1f; 
i = *reinterpret_cast<int*>(&expected); 
std::cout << i << '\n'; 

Auf meinem System ist die Ausgabe:

1036831944 
1036831949 

Die Mantissen sind genau 5 ULPs auseinander. Der 4 ULP-Vergleich ist nicht ausreichend für den Fehler der Berechnung.

0.2f - 1.0f ist in Ordnung, es gibt keinen Genauigkeitsfehler überhaupt auf meinem System. Was Sie übrig haben, ist -0.8f + 0.9f.Hier kommt der Fehler (auf meinem System). Ich bin kein Experte genug, um Ihnen zu sagen warum diese Berechnung hat 5 ULP Genauigkeit Fehler.


In Fällen, in denen bestimmte Fehlergrad erwartet wird, verwenden EXPECT_NEAR.

0

Wie ich sehe, ist das Problem Ihre Annahme, dass 0,2f - 1,0 f + 0,9 f gleich 0,1 f ist. Keines von 0.2 0.9 0.1 kann genau wie Floats dargestellt werden (oder als Doppel oder irgendeine andere binäre Gleitkommadarstellung).

0,2f und 0,9f werden in der Tat Annäherungen zu 0,2 und 0,9 sein, und es gibt wenig Grund anzunehmen, dass die Ihre Summe die gleiche Annäherung an 0,1 geben wird, wie durch 0,1f gegeben. Während der relative Fehler in 0,2f und 0,9f alles ungefähr gleich ist, könnte der relative Fehler in der Summe wegen der Aufhebung viel größer sein.

Wenn Sie versuchen, die gleiche Sache mit Zahlen, die genau wie Schwimmer dargestellt werden kann, zum Beispiel 0,25F - 1.0f + 0.875f Sie werden feststellen, dass diese