目的
GoogleTestを使って、浮動小数点数を比較する際の要点を紹介します。
本記事ではFloat型(単精度)について説明しますが、Double型(倍精度)でも考え方は同じです。
#Floatについて
Floatは32bitの内仮数部が24bitしかないため、有効数字の上限は約7桁となります。
参考文献
#テストマクロの種類
GoogleTestに用意されている、値一致比較のマクロは以下の3種類
- EXPECT_EQ(ASSERT_EQ)
- EXPECT_FLOAT_EQ(ASSERT_FLOAT_EQ)
- EXPECT_NEAR(ASSERT_NEAR)
EXPECT_EQは使えない
公式ドキュメントにも記載がある通り、
EXPECT_EQは完全一致比較を行うため、浮動小数点数の比較には使用できません。
よって、下記のテストは失敗します。
TEST(FloatTest, FailureTestForSimpleCalculation){
float a, b;
a = 555555.5;
b = 0.055555;
EXPECT_EQ(555555.555555, a+b); //失敗する
}
----------------------------------------------------------
1/4 Testing: FloatTest.FailureTestForSimpleCalculation
1/4 Test: FloatTest.FailureTestForSimpleCalculation
Command: ".../AllTests" "--gtest_filter=FloatTest.FailureTestForSimpleCalculation"
Directory: .../build/test
"FloatTest.FailureTestForSimpleCalculation" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../googletest/src/gtest_main.cc
Note: Google Test filter = FloatTest.FailureTestForSimpleCalculation
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN ] FloatTest.FailureTestForSimpleCalculation
/.../test/test_float.cpp:10: Failure
Expected equality of these values:
555555.555555
Which is: 555556
a+b
Which is: 555556
[ FAILED ] FloatTest.FailureTestForSimpleCalculation (0 ms)
[----------] 1 test from FloatTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] FloatTest.FailureTestForSimpleCalculation
1 FAILED TEST
<end of output>
Test time = 0.00 sec
----------------------------------------------------------
Test Failed.
"FloatTest.FailureTestForSimpleCalculation" end time: Aug 05 19:11 JST
"FloatTest.FailureTestForSimpleCalculation" time elapsed: 00:00:00
----------------------------------------------------------
単純な演算であればEXPECT_FLOAT_EQが使える
先ほど失敗したテストは、**EXPECT_FLOAT_EQ(ASSERT_FLOAT_EQ)**を使えば等しいと判断されます。
EXPECT_FLOAT_EQは、4ULPs(1ULPs:float型の最下位桁数単位)を基準に、2値の差が許容できる誤差範囲かを判定します。
公式ドキュメント
浮動小数点数の誤差について詳しく知りたい方はこちら
TEST(FloatTest, SuccessfulTestForSimpleCalculation){
float a, b;
a = 555555.5;
b = 0.055555;
EXPECT_FLOAT_EQ(555555.555555, a+b); //成功する
}
2/4 Testing: FloatTest.SuccessfulTestForSimpleCalculation
2/4 Test: FloatTest.SuccessfulTestForSimpleCalculation
Command: "/.../build/test/AllTests" "--gtest_filter=FloatTest.SuccessfulTestForSimpleCalculation"
Directory: /.../build/test
"FloatTest.SuccessfulTestForSimpleCalculation" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../src/gtest_main.cc
Note: Google Test filter = FloatTest.SuccessfulTestForSimpleCalculation
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN ] FloatTest.SuccessfulTestForSimpleCalculation
[ OK ] FloatTest.SuccessfulTestForSimpleCalculation (0 ms)
[----------] 1 test from FloatTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
<end of output>
Test time = 0.00 sec
----------------------------------------------------------
Test Passed.
"FloatTest.SuccessfulTestForSimpleCalculation" end time: Aug 05 19:11 JST
"FloatTest.SuccessfulTestForSimpleCalculation" time elapsed: 00:00:00
----------------------------------------------------------
丸め誤差が発生する演算には、EXPECT_NEARを使おう
**EXPECT_FLOAT_EQ(ASSERT_FLOAT_EQ)**は、繰り返し演算によって発生する、情報落ちには対応できないため、
下記のようなテストは失敗します。
TEST(FloatTest, FailureTestForLossOfTrailingDigits){
float a, b;
a = 155000.0;
b = 0.550000;
//a=100000.0になることを期待した演算
for(int i=0; i<100000; i++){
a -= b;
}
EXPECT_FLOAT_EQ(100000.0, a); //失敗する
}
3/4 Testing: FloatTest.FailureTestForLossOfTrailingDigits
3/4 Test: FloatTest.FailureTestForLossOfTrailingDigits
Command: ".../build/test/AllTests" "--gtest_filter=FloatTest.FailureTestForLossOfTrailingDigits"
Directory: .../build/test
"FloatTest.FailureTestForLossOfTrailingDigits" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../src/gtest_main.cc
Note: Google Test filter = FloatTest.FailureTestForLossOfTrailingDigits
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN ] FloatTest.FailureTestForLossOfTrailingDigits
/.../test/test_float.cpp:35: Failure
Expected equality of these values:
100000.0
Which is: 100000
a
Which is: 100312.5
[ FAILED ] FloatTest.FailureTestForLossOfTrailingDigits (0 ms)
[----------] 1 test from FloatTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] FloatTest.FailureTestForLossOfTrailingDigits
1 FAILED TEST
<end of output>
Test time = 0.00 sec
----------------------------------------------------------
Test Failed.
"FloatTest.FailureTestForLossOfTrailingDigits" end time: Aug 05 19:11 JST
"FloatTest.FailureTestForLossOfTrailingDigits" time elapsed: 00:00:00
----------------------------------------------------------
上記例では、真値に対して312.5もの誤差が発生しています。
丸め誤差を考慮したテストを記述する場合は、**EXPECT_NEAR(ASSERT_NEAR)**を使うと、許容誤差範囲を引数によって指定することができます。
TEST(FloatTest, SuccessfulTestForLossOfTrailingDigits){
float a, b;
a = 155000.0;
b = 0.550000;
//a=100000.0になることを期待した演算
for(int i=0; i<100000; i++){
a -= b;
}
EXPECT_NEAR(1.0e+5, a, 5.0e+2); //成功する
}
4/4 Testing: FloatTest.SuccessfulTestForLossOfTrailingDigits
4/4 Test: FloatTest.SuccessfulTestForLossOfTrailingDigits
Command: "/.../build/test/AllTests" "--gtest_filter=FloatTest.SuccessfulTestForLossOfTrailingDigits"
Directory: /.../build/test
"FloatTest.SuccessfulTestForLossOfTrailingDigits" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../src/gtest_main.cc
Note: Google Test filter = FloatTest.SuccessfulTestForLossOfTrailingDigits
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN ] FloatTest.SuccessfulTestForLossOfTrailingDigits
[ OK ] FloatTest.SuccessfulTestForLossOfTrailingDigits (1 ms)
[----------] 1 test from FloatTest (1 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (1 ms total)
[ PASSED ] 1 test.
<end of output>
Test time = 0.00 sec
----------------------------------------------------------
Test Passed.
"FloatTest.SuccessfulTestForLossOfTrailingDigits" end time: Aug 05 19:11 JST
"FloatTest.SuccessfulTestForLossOfTrailingDigits" time elapsed: 00:00:00
----------------------------------------------------------
End testing: Aug 05 19:11 JST
#まとめ
- 浮動小数数の比較にEXPECT_EQは使えない
- 単純な演算であればEXPECT_FLOAT_EQが使える
- 丸め誤差が発生する演算にはEXPECT_NEARを使おう
#参考文献
http://www.cc.kyoto-su.ac.jp/~yamada/programming/float.html#float
http://opencv.jp/googletestdocs/index.html
https://qiita.com/tobira-code/items/d9c40b650dec40d3c2ed